togetherjs.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. /*jshint scripturl:true */
  5. (function () {
  6. var defaultConfiguration = {
  7. // Disables clicks for a certain element.
  8. // (e.g., 'canvas' would not show clicks on canvas elements.)
  9. // Setting this to true will disable clicks globally.
  10. dontShowClicks: false,
  11. // Experimental feature to echo clicks to certain elements across clients:
  12. cloneClicks: false,
  13. // Enable Mozilla or Google analytics on the page when TogetherJS is activated:
  14. // FIXME: these don't seem to be working, and probably should be removed in favor
  15. // of the hub analytics
  16. enableAnalytics: false,
  17. // The code to enable (this is defaulting to a Mozilla code):
  18. analyticsCode: "UA-35433268-28",
  19. // The base URL of the hub (gets filled in below):
  20. hubBase: null,
  21. // A function that will return the name of the user:
  22. getUserName: null,
  23. // A function that will return the color of the user:
  24. getUserColor: null,
  25. // A function that will return the avatar of the user:
  26. getUserAvatar: null,
  27. // The siteName is used in the walkthrough (defaults to document.title):
  28. siteName: null,
  29. // Whether to use the minimized version of the code (overriding the built setting)
  30. useMinimizedCode: undefined,
  31. // Append cache-busting queries (useful for development!)
  32. cacheBust: true,
  33. // Any events to bind to
  34. on: {},
  35. // Hub events to bind to
  36. hub_on: {},
  37. // Enables the alt-T alt-T TogetherJS shortcut; however, this setting
  38. // must be enabled early as TogetherJSConfig_enableShortcut = true;
  39. enableShortcut: false,
  40. // The name of this tool as provided to users. The UI is updated to use this.
  41. // Because of how it is used in text it should be a proper noun, e.g.,
  42. // "MySite's Collaboration Tool"
  43. toolName: null,
  44. // Used to auto-start TogetherJS with a {prefix: pageName, max: participants}
  45. // Also with findRoom: "roomName" it will connect to the given room name
  46. findRoom: null,
  47. // If true, starts TogetherJS automatically (of course!)
  48. autoStart: false,
  49. // If true, then the "Join TogetherJS Session?" confirmation dialog
  50. // won't come up
  51. suppressJoinConfirmation: false,
  52. // If true, then the "Invite a friend" window won't automatically come up
  53. suppressInvite: false,
  54. // A room in which to find people to invite to this session,
  55. inviteFromRoom: null,
  56. // This is used to keep sessions from crossing over on the same
  57. // domain, if for some reason you want sessions that are limited
  58. // to only a portion of the domain:
  59. storagePrefix: "togetherjs",
  60. // When true, we treat the entire URL, including the hash, as the identifier
  61. // of the page; i.e., if you one person is on `http://example.com/#view1`
  62. // and another person is at `http://example.com/#view2` then these two people
  63. // are considered to be at completely different URLs
  64. includeHashInUrl: false,
  65. // When true, the WebRTC-based mic/chat will be disabled
  66. disableWebRTC: false,
  67. // When true, youTube videos will synchronize
  68. youtube: true,
  69. // Ignores the following console messages, disables all messages if set to true
  70. ignoreMessages: ["cursor-update", "keydown", "scroll-update"],
  71. // Ignores the following forms (will ignore all forms if set to true):
  72. ignoreForms: [":password"],
  73. // When undefined, attempts to use the browser's language
  74. lang: undefined,
  75. fallbackLang: "en-US"
  76. };
  77. var styleSheet = "/togetherjs/togetherjs.css";
  78. var baseUrl = "";
  79. if (baseUrl == "__" + "baseUrl__") {
  80. // Reset the variable if it doesn't get substituted
  81. baseUrl = "";
  82. }
  83. // Allow override of baseUrl (this is done separately because it needs
  84. // to be done very early)
  85. if (window.TogetherJSConfig && window.TogetherJSConfig.baseUrl) {
  86. baseUrl = window.TogetherJSConfig.baseUrl;
  87. }
  88. if (window.TogetherJSConfig_baseUrl) {
  89. baseUrl = window.TogetherJSConfig_baseUrl;
  90. }
  91. defaultConfiguration.baseUrl = baseUrl;
  92. // True if this file should use minimized sub-resources:
  93. var min = "yes" == "__" + "min__" ? false : "yes" == "yes";
  94. var baseUrlOverride = localStorage.getItem("togetherjs.baseUrlOverride");
  95. if (baseUrlOverride) {
  96. try {
  97. baseUrlOverride = JSON.parse(baseUrlOverride);
  98. } catch (e) {
  99. baseUrlOverride = null;
  100. }
  101. if ((! baseUrlOverride) || baseUrlOverride.expiresAt < Date.now()) {
  102. // Ignore because it has expired
  103. localStorage.removeItem("togetherjs.baseUrlOverride");
  104. } else {
  105. baseUrl = baseUrlOverride.baseUrl;
  106. var logger = console.warn || console.log;
  107. logger.call(console, "Using TogetherJS baseUrlOverride:", baseUrl);
  108. logger.call(console, "To undo run: localStorage.removeItem('togetherjs.baseUrlOverride')");
  109. }
  110. }
  111. var configOverride = localStorage.getItem("togetherjs.configOverride");
  112. if (configOverride) {
  113. try {
  114. configOverride = JSON.parse(configOverride);
  115. } catch (e) {
  116. configOverride = null;
  117. }
  118. if ((! configOverride) || configOverride.expiresAt < Date.now()) {
  119. localStorage.removeItem("togetherjs.configOverride");
  120. } else {
  121. var shownAny = false;
  122. for (var attr in configOverride) {
  123. if (! configOverride.hasOwnProperty(attr)) {
  124. continue;
  125. }
  126. if (attr == "expiresAt" || ! configOverride.hasOwnProperty(attr)) {
  127. continue;
  128. }
  129. if (! shownAny) {
  130. console.warn("Using TogetherJS configOverride");
  131. console.warn("To undo run: localStorage.removeItem('togetherjs.configOverride')");
  132. }
  133. window["TogetherJSConfig_" + attr] = configOverride[attr];
  134. console.log("Config override:", attr, "=", configOverride[attr]);
  135. }
  136. }
  137. }
  138. var version = "unknown";
  139. // FIXME: we could/should use a version from the checkout, at least
  140. // for production
  141. var cacheBust = "";
  142. if ((! cacheBust) || cacheBust == "") {
  143. cacheBust = Date.now() + "";
  144. } else {
  145. version = cacheBust;
  146. }
  147. // Make sure we have all of the console.* methods:
  148. if (typeof console == "undefined") {
  149. console = {};
  150. }
  151. if (! console.log) {
  152. console.log = function () {};
  153. }
  154. ["debug", "info", "warn", "error"].forEach(function (method) {
  155. if (! console[method]) {
  156. console[method] = console.log;
  157. }
  158. });
  159. if (! baseUrl) {
  160. var scripts = document.getElementsByTagName("script");
  161. for (var i=0; i<scripts.length; i++) {
  162. var src = scripts[i].src;
  163. if (src && src.search(/togetherjs(-min)?.js(\?.*)?$/) !== -1) {
  164. baseUrl = src.replace(/\/*togetherjs(-min)?.js(\?.*)?$/, "");
  165. console.warn("Detected baseUrl as", baseUrl);
  166. break;
  167. } else if (src && src.search(/togetherjs-min.js(\?.*)?$/) !== -1) {
  168. baseUrl = src.replace(/\/*togetherjs-min.js(\?.*)?$/, "");
  169. console.warn("Detected baseUrl as", baseUrl);
  170. break;
  171. }
  172. }
  173. }
  174. if (! baseUrl) {
  175. console.warn("Could not determine TogetherJS's baseUrl (looked for a <script> with togetherjs.js and togetherjs-min.js)");
  176. }
  177. function addStyle() {
  178. var existing = document.getElementById("togetherjs-stylesheet");
  179. if (! existing) {
  180. var link = document.createElement("link");
  181. link.id = "togetherjs-stylesheet";
  182. link.setAttribute("rel", "stylesheet");
  183. link.href = baseUrl + styleSheet +
  184. (cacheBust ? ("?bust=" + cacheBust) : '');
  185. document.head.appendChild(link);
  186. }
  187. }
  188. function addScript(url) {
  189. var script = document.createElement("script");
  190. script.src = baseUrl + url +
  191. (cacheBust ? ("?bust=" + cacheBust) : '');
  192. document.head.appendChild(script);
  193. }
  194. var TogetherJS = window.TogetherJS = function TogetherJS(event) {
  195. var session;
  196. if (TogetherJS.running) {
  197. session = TogetherJS.require("session");
  198. session.close();
  199. return;
  200. }
  201. TogetherJS.startup.button = null;
  202. try {
  203. if (event && typeof event == "object") {
  204. if (event.target && typeof event) {
  205. TogetherJS.startup.button = event.target;
  206. } else if (event.nodeType == 1) {
  207. TogetherJS.startup.button = event;
  208. } else if (event[0] && event[0].nodeType == 1) {
  209. // Probably a jQuery element
  210. TogetherJS.startup.button = event[0];
  211. }
  212. }
  213. } catch (e) {
  214. console.warn("Error determining starting button:", e);
  215. }
  216. if (window.TowTruckConfig) {
  217. console.warn("TowTruckConfig is deprecated; please use TogetherJSConfig");
  218. if (window.TogetherJSConfig) {
  219. console.warn("Ignoring TowTruckConfig in favor of TogetherJSConfig");
  220. } else {
  221. window.TogetherJSConfig = TowTruckConfig;
  222. }
  223. }
  224. if (window.TogetherJSConfig && (! window.TogetherJSConfig.loaded)) {
  225. TogetherJS.config(window.TogetherJSConfig);
  226. window.TogetherJSConfig.loaded = true;
  227. }
  228. // This handles loading configuration from global variables. This
  229. // includes TogetherJSConfig_on_*, which are attributes folded into
  230. // the "on" configuration value.
  231. var attr;
  232. var attrName;
  233. var globalOns = {};
  234. for (attr in window) {
  235. if (attr.indexOf("TogetherJSConfig_on_") === 0) {
  236. attrName = attr.substr(("TogetherJSConfig_on_").length);
  237. globalOns[attrName] = window[attr];
  238. } else if (attr.indexOf("TogetherJSConfig_") === 0) {
  239. attrName = attr.substr(("TogetherJSConfig_").length);
  240. TogetherJS.config(attrName, window[attr]);
  241. } else if (attr.indexOf("TowTruckConfig_on_") === 0) {
  242. attrName = attr.substr(("TowTruckConfig_on_").length);
  243. console.warn("TowTruckConfig_* is deprecated, please rename", attr, "to TogetherJSConfig_on_" + attrName);
  244. globalOns[attrName] = window[attr];
  245. } else if (attr.indexOf("TowTruckConfig_") === 0) {
  246. attrName = attr.substr(("TowTruckConfig_").length);
  247. console.warn("TowTruckConfig_* is deprecated, please rename", attr, "to TogetherJSConfig_" + attrName);
  248. TogetherJS.config(attrName, window[attr]);
  249. }
  250. }
  251. // FIXME: copy existing config?
  252. // FIXME: do this directly in TogetherJS.config() ?
  253. // FIXME: close these configs?
  254. var ons = TogetherJS.config.get("on");
  255. for (attr in globalOns) {
  256. if (globalOns.hasOwnProperty(attr)) {
  257. // FIXME: should we avoid overwriting? Maybe use arrays?
  258. ons[attr] = globalOns[attr];
  259. }
  260. }
  261. TogetherJS.config("on", ons);
  262. for (attr in ons) {
  263. TogetherJS.on(attr, ons[attr]);
  264. }
  265. var hubOns = TogetherJS.config.get("hub_on");
  266. if (hubOns) {
  267. for (attr in hubOns) {
  268. if (hubOns.hasOwnProperty(attr)) {
  269. TogetherJS.hub.on(attr, hubOns[attr]);
  270. }
  271. }
  272. }
  273. if (!TogetherJS.config.close('cacheBust')) {
  274. cacheBust = '';
  275. delete TogetherJS.requireConfig.urlArgs;
  276. }
  277. if (! TogetherJS.startup.reason) {
  278. // Then a call to TogetherJS() from a button must be started TogetherJS
  279. TogetherJS.startup.reason = "started";
  280. }
  281. // FIXME: maybe I should just test for TogetherJS.require:
  282. if (TogetherJS._loaded) {
  283. session = TogetherJS.require("session");
  284. addStyle();
  285. session.start();
  286. return;
  287. }
  288. // A sort of signal to session.js to tell it to actually
  289. // start itself (i.e., put up a UI and try to activate)
  290. TogetherJS.startup._launch = true;
  291. addStyle();
  292. var minSetting = TogetherJS.config.get("useMinimizedCode");
  293. TogetherJS.config.close("useMinimizedCode");
  294. if (minSetting !== undefined) {
  295. min = !! minSetting;
  296. }
  297. var requireConfig = TogetherJS._extend(TogetherJS.requireConfig);
  298. var deps = ["session", "jquery"];
  299. var lang = TogetherJS.getConfig("lang");
  300. // [igoryen]: We should generate this value in Gruntfile.js, based on the available translations
  301. var availableTranslations = {
  302. "en-US": true,
  303. "en": "en-US",
  304. "es": "es-BO",
  305. "es-BO": true,
  306. "ru": true,
  307. "ru-RU": "ru",
  308. "pl": "pl-PL",
  309. "pl-PL": true,
  310. "de-DE": true,
  311. "de": "de-DE"
  312. };
  313. if(lang === undefined) {
  314. // BCP 47 mandates hyphens, not underscores, to separate lang parts
  315. lang = navigator.language.replace(/_/g, "-");
  316. }
  317. if (/-/.test(lang) && !availableTranslations[lang]) {
  318. lang = lang.replace(/-.*$/, '');
  319. }
  320. if (!availableTranslations[lang]) {
  321. lang = TogetherJS.config.get("fallbackLang");
  322. } else if (availableTranslations[lang] !== true) {
  323. lang = availableTranslations[lang];
  324. }
  325. TogetherJS.config("lang", lang);
  326. var localeTemplates = "templates-" + lang;
  327. deps.splice(0, 0, localeTemplates);
  328. function callback(session, jquery) {
  329. TogetherJS._loaded = true;
  330. if (! min) {
  331. TogetherJS.require = require.config({context: "togetherjs"});
  332. TogetherJS._requireObject = require;
  333. }
  334. }
  335. if (! min) {
  336. if (typeof require == "function") {
  337. if (! require.config) {
  338. console.warn("The global require (", require, ") is not requirejs; please use togetherjs-min.js");
  339. throw new Error("Conflict with window.require");
  340. }
  341. TogetherJS.require = require.config(requireConfig);
  342. }
  343. }
  344. if (typeof TogetherJS.require == "function") {
  345. // This is an already-configured version of require
  346. TogetherJS.require(deps, callback);
  347. } else {
  348. requireConfig.deps = deps;
  349. requireConfig.callback = callback;
  350. if (! min) {
  351. window.require = requireConfig;
  352. }
  353. }
  354. if (min) {
  355. addScript("/togetherjs/togetherjsPackage.js");
  356. } else {
  357. addScript("/togetherjs/libs/require.js");
  358. }
  359. };
  360. TogetherJS.pageLoaded = Date.now();
  361. TogetherJS._extend = function (base, extensions) {
  362. if (! extensions) {
  363. extensions = base;
  364. base = {};
  365. }
  366. for (var a in extensions) {
  367. if (extensions.hasOwnProperty(a)) {
  368. base[a] = extensions[a];
  369. }
  370. }
  371. return base;
  372. };
  373. TogetherJS._startupInit = {
  374. // What element, if any, was used to start the session:
  375. button: null,
  376. // The startReason is the reason TogetherJS was started. One of:
  377. // null: not started
  378. // started: hit the start button (first page view)
  379. // joined: joined the session (first page view)
  380. reason: null,
  381. // Also, the session may have started on "this" page, or maybe is continued
  382. // from a past page. TogetherJS.continued indicates the difference (false the
  383. // first time TogetherJS is started or joined, true on later page loads).
  384. continued: false,
  385. // This is set to tell the session what shareId to use, if the boot
  386. // code knows (mostly because the URL indicates the id).
  387. _joinShareId: null,
  388. // This tells session to start up immediately (otherwise it would wait
  389. // for session.start() to be run)
  390. _launch: false
  391. };
  392. TogetherJS.startup = TogetherJS._extend(TogetherJS._startupInit);
  393. TogetherJS.running = false;
  394. TogetherJS.requireConfig = {
  395. context: "togetherjs",
  396. baseUrl: baseUrl + "/togetherjs",
  397. urlArgs: "bust=" + cacheBust,
  398. paths: {
  399. jquery: "libs/jquery-1.11.1.min",
  400. walkabout: "libs/walkabout/walkabout",
  401. esprima: "libs/walkabout/lib/esprima",
  402. falafel: "libs/walkabout/lib/falafel",
  403. tinycolor: "libs/tinycolor",
  404. whrandom: "libs/whrandom/random"
  405. }
  406. };
  407. TogetherJS._mixinEvents = function (proto) {
  408. proto.on = function on(name, callback) {
  409. if (typeof callback != "function") {
  410. console.warn("Bad callback for", this, ".once(", name, ", ", callback, ")");
  411. throw "Error: .once() called with non-callback";
  412. }
  413. if (name.search(" ") != -1) {
  414. var names = name.split(/ +/g);
  415. names.forEach(function (n) {
  416. this.on(n, callback);
  417. }, this);
  418. return;
  419. }
  420. if (this._knownEvents && this._knownEvents.indexOf(name) == -1) {
  421. var thisString = "" + this;
  422. if (thisString.length > 20) {
  423. thisString = thisString.substr(0, 20) + "...";
  424. }
  425. console.warn(thisString + ".on('" + name + "', ...): unknown event");
  426. if (console.trace) {
  427. console.trace();
  428. }
  429. }
  430. if (! this._listeners) {
  431. this._listeners = {};
  432. }
  433. if (! this._listeners[name]) {
  434. this._listeners[name] = [];
  435. }
  436. if (this._listeners[name].indexOf(callback) == -1) {
  437. this._listeners[name].push(callback);
  438. }
  439. };
  440. proto.once = function once(name, callback) {
  441. if (typeof callback != "function") {
  442. console.warn("Bad callback for", this, ".once(", name, ", ", callback, ")");
  443. throw "Error: .once() called with non-callback";
  444. }
  445. var attr = "onceCallback_" + name;
  446. // FIXME: maybe I should add the event name to the .once attribute:
  447. if (! callback[attr]) {
  448. callback[attr] = function onceCallback() {
  449. callback.apply(this, arguments);
  450. this.off(name, onceCallback);
  451. delete callback[attr];
  452. };
  453. }
  454. this.on(name, callback[attr]);
  455. };
  456. proto.off = proto.removeListener = function off(name, callback) {
  457. if (this._listenerOffs) {
  458. // Defer the .off() call until the .emit() is done.
  459. this._listenerOffs.push([name, callback]);
  460. return;
  461. }
  462. if (name.search(" ") != -1) {
  463. var names = name.split(/ +/g);
  464. names.forEach(function (n) {
  465. this.off(n, callback);
  466. }, this);
  467. return;
  468. }
  469. if ((! this._listeners) || ! this._listeners[name]) {
  470. return;
  471. }
  472. var l = this._listeners[name], _len = l.length;
  473. for (var i=0; i<_len; i++) {
  474. if (l[i] == callback) {
  475. l.splice(i, 1);
  476. break;
  477. }
  478. }
  479. };
  480. proto.emit = function emit(name) {
  481. var offs = this._listenerOffs = [];
  482. if ((! this._listeners) || ! this._listeners[name]) {
  483. return;
  484. }
  485. var args = Array.prototype.slice.call(arguments, 1);
  486. var l = this._listeners[name];
  487. l.forEach(function (callback) {
  488. callback.apply(this, args);
  489. }, this);
  490. delete this._listenerOffs;
  491. if (offs.length) {
  492. offs.forEach(function (item) {
  493. this.off(item[0], item[1]);
  494. }, this);
  495. }
  496. };
  497. return proto;
  498. };
  499. /* This finalizes the unloading of TogetherJS, including unloading modules */
  500. TogetherJS._teardown = function () {
  501. var requireObject = TogetherJS._requireObject || window.require;
  502. // FIXME: this doesn't clear the context for min-case
  503. if (requireObject.s && requireObject.s.contexts) {
  504. delete requireObject.s.contexts.togetherjs;
  505. }
  506. TogetherJS._loaded = false;
  507. TogetherJS.startup = TogetherJS._extend(TogetherJS._startupInit);
  508. TogetherJS.running = false;
  509. };
  510. TogetherJS._mixinEvents(TogetherJS);
  511. TogetherJS._knownEvents = ["ready", "close"];
  512. TogetherJS.toString = function () {
  513. return "TogetherJS";
  514. };
  515. var defaultHubBase = "https://togetherjs-hub.glitch.me/";
  516. if (defaultHubBase == "__" + "hubUrl"+ "__") {
  517. // Substitution wasn't made
  518. defaultHubBase = "https://hub.togetherjs.mozillalabs.com";
  519. }
  520. defaultConfiguration.hubBase = defaultHubBase;
  521. TogetherJS._configuration = {};
  522. TogetherJS._defaultConfiguration = {
  523. // Disables clicks for a certain element.
  524. // (e.g., 'canvas' would not show clicks on canvas elements.)
  525. // Setting this to true will disable clicks globally.
  526. dontShowClicks: false,
  527. // Experimental feature to echo clicks to certain elements across clients:
  528. cloneClicks: false,
  529. // Enable Mozilla or Google analytics on the page when TogetherJS is activated:
  530. // FIXME: these don't seem to be working, and probably should be removed in favor
  531. // of the hub analytics
  532. enableAnalytics: false,
  533. // The code to enable (this is defaulting to a Mozilla code):
  534. analyticsCode: "UA-35433268-28",
  535. // The base URL of the hub
  536. hubBase: defaultHubBase,
  537. // A function that will return the name of the user:
  538. getUserName: null,
  539. // A function that will return the color of the user:
  540. getUserColor: null,
  541. // A function that will return the avatar of the user:
  542. getUserAvatar: null,
  543. // The siteName is used in the walkthrough (defaults to document.title):
  544. siteName: null,
  545. // Whether to use the minimized version of the code (overriding the built setting)
  546. useMinimizedCode: undefined,
  547. // Any events to bind to
  548. on: {},
  549. // Hub events to bind to
  550. hub_on: {},
  551. // Enables the alt-T alt-T TogetherJS shortcut; however, this setting
  552. // must be enabled early as TogetherJSConfig_enableShortcut = true;
  553. enableShortcut: false,
  554. // The name of this tool as provided to users. The UI is updated to use this.
  555. // Because of how it is used in text it should be a proper noun, e.g.,
  556. // "MySite's Collaboration Tool"
  557. toolName: null,
  558. // Used to auto-start TogetherJS with a {prefix: pageName, max: participants}
  559. // Also with findRoom: "roomName" it will connect to the given room name
  560. findRoom: null,
  561. // If true, starts TogetherJS automatically (of course!)
  562. autoStart: false,
  563. // If true, then the "Join TogetherJS Session?" confirmation dialog
  564. // won't come up
  565. suppressJoinConfirmation: false,
  566. // If true, then the "Invite a friend" window won't automatically come up
  567. suppressInvite: false,
  568. // A room in which to find people to invite to this session,
  569. inviteFromRoom: null,
  570. // This is used to keep sessions from crossing over on the same
  571. // domain, if for some reason you want sessions that are limited
  572. // to only a portion of the domain:
  573. storagePrefix: "togetherjs",
  574. // When true, we treat the entire URL, including the hash, as the identifier
  575. // of the page; i.e., if you one person is on `http://example.com/#view1`
  576. // and another person is at `http://example.com/#view2` then these two people
  577. // are considered to be at completely different URLs
  578. includeHashInUrl: false,
  579. // The language to present the tool in, such as "en-US" or "ru-RU"
  580. // Note this must be set as TogetherJSConfig_lang, as it effects the loader
  581. // and must be set as soon as this file is included
  582. lang: null
  583. };
  584. // FIXME: there's a point at which configuration can't be updated
  585. // (e.g., hubBase after the TogetherJS has loaded). We should keep
  586. // track of these and signal an error if someone attempts to
  587. // reconfigure too late
  588. TogetherJS.getConfig = function (name) { // rename into TogetherJS.config.get()?
  589. var value = TogetherJS._configuration[name];
  590. if (value === undefined) {
  591. if (! TogetherJS._defaultConfiguration.hasOwnProperty(name)) {
  592. console.error("Tried to load unknown configuration value:", name);
  593. }
  594. value = TogetherJS._defaultConfiguration[name];
  595. }
  596. return value;
  597. };
  598. TogetherJS._defaultConfiguration = defaultConfiguration;
  599. TogetherJS._configTrackers = {};
  600. TogetherJS._configClosed = {};
  601. /* TogetherJS.config(configurationObject)
  602. or: TogetherJS.config(configName, value)
  603. Adds configuration to TogetherJS. You may also set the global variable TogetherJSConfig
  604. and when TogetherJS is started that configuration will be loaded.
  605. Unknown configuration values will lead to console error messages.
  606. */
  607. TogetherJS.config = function (name, maybeValue) {
  608. var settings;
  609. if (arguments.length == 1) {
  610. if (typeof name != "object") {
  611. throw new Error('TogetherJS.config(value) must have an object value (not: ' + name + ')');
  612. }
  613. settings = name;
  614. } else {
  615. settings = {};
  616. settings[name] = maybeValue;
  617. }
  618. var i;
  619. var tracker;
  620. var attr;
  621. for (attr in settings) {
  622. if (settings.hasOwnProperty(attr)) {
  623. if (TogetherJS._configClosed[attr] && TogetherJS.running) {
  624. throw new Error("The configuration " + attr + " is finalized and cannot be changed");
  625. }
  626. }
  627. }
  628. for (attr in settings) {
  629. if (! settings.hasOwnProperty(attr)) {
  630. continue;
  631. }
  632. if (attr == "loaded" || attr == "callToStart") {
  633. continue;
  634. }
  635. if (! TogetherJS._defaultConfiguration.hasOwnProperty(attr)) {
  636. console.warn("Unknown configuration value passed to TogetherJS.config():", attr);
  637. }
  638. var previous = TogetherJS._configuration[attr];
  639. var value = settings[attr];
  640. TogetherJS._configuration[attr] = value;
  641. var trackers = TogetherJS._configTrackers[name] || [];
  642. var failed = false;
  643. for (i=0; i<trackers.length; i++) {
  644. try {
  645. tracker = trackers[i];
  646. tracker(value, previous);
  647. } catch (e) {
  648. console.warn("Error setting configuration", name, "to", value,
  649. ":", e, "; reverting to", previous);
  650. failed = true;
  651. break;
  652. }
  653. }
  654. if (failed) {
  655. TogetherJS._configuration[attr] = previous;
  656. for (i=0; i<trackers.length; i++) {
  657. try {
  658. tracker = trackers[i];
  659. tracker(value);
  660. } catch (e) {
  661. console.warn("Error REsetting configuration", name, "to", previous,
  662. ":", e, "(ignoring)");
  663. }
  664. }
  665. }
  666. }
  667. };
  668. TogetherJS.config.get = function (name) {
  669. var value = TogetherJS._configuration[name];
  670. if (value === undefined) {
  671. if (! TogetherJS._defaultConfiguration.hasOwnProperty(name)) {
  672. console.error("Tried to load unknown configuration value:", name);
  673. }
  674. value = TogetherJS._defaultConfiguration[name];
  675. }
  676. return value;
  677. };
  678. TogetherJS.config.track = function (name, callback) {
  679. if (! TogetherJS._defaultConfiguration.hasOwnProperty(name)) {
  680. throw new Error("Configuration is unknown: " + name);
  681. }
  682. callback(TogetherJS.config.get(name));
  683. if (! TogetherJS._configTrackers[name]) {
  684. TogetherJS._configTrackers[name] = [];
  685. }
  686. TogetherJS._configTrackers[name].push(callback);
  687. return callback;
  688. };
  689. TogetherJS.config.close = function (name) {
  690. if (! TogetherJS._defaultConfiguration.hasOwnProperty(name)) {
  691. throw new Error("Configuration is unknown: " + name);
  692. }
  693. TogetherJS._configClosed[name] = true;
  694. return this.get(name);
  695. };
  696. TogetherJS.reinitialize = function () {
  697. if (TogetherJS.running && typeof TogetherJS.require == "function") {
  698. TogetherJS.require(["session"], function (session) {
  699. session.emit("reinitialize");
  700. });
  701. }
  702. // If it's not set, TogetherJS has not been loaded, and reinitialization is not needed
  703. };
  704. TogetherJS.refreshUserData = function () {
  705. if (TogetherJS.running && typeof TogetherJS.require == "function") {
  706. TogetherJS.require(["session"], function (session) {
  707. session.emit("refresh-user-data");
  708. });
  709. }
  710. };
  711. // This should contain the output of "git describe --always --dirty"
  712. // FIXME: substitute this on the server (and update make-static-client)
  713. TogetherJS.version = version;
  714. TogetherJS.baseUrl = baseUrl;
  715. TogetherJS.hub = TogetherJS._mixinEvents({});
  716. TogetherJS._onmessage = function (msg) {
  717. var type = msg.type;
  718. if (type.search(/^app\./) === 0) {
  719. type = type.substr("app.".length);
  720. } else {
  721. type = "togetherjs." + type;
  722. }
  723. msg.type = type;
  724. TogetherJS.hub.emit(msg.type, msg);
  725. };
  726. TogetherJS.send = function (msg) {
  727. if (! TogetherJS.require) {
  728. throw "You cannot use TogetherJS.send() when TogetherJS is not running";
  729. }
  730. var session = TogetherJS.require("session");
  731. session.appSend(msg);
  732. };
  733. TogetherJS.shareUrl = function () {
  734. if (! TogetherJS.require) {
  735. return null;
  736. }
  737. var session = TogetherJS.require("session");
  738. return session.shareUrl();
  739. };
  740. var listener = null;
  741. TogetherJS.listenForShortcut = function () {
  742. console.warn("Listening for alt-T alt-T to start TogetherJS");
  743. TogetherJS.removeShortcut();
  744. listener = function listener(event) {
  745. if (event.which == 84 && event.altKey) {
  746. if (listener.pressed) {
  747. // Second hit
  748. TogetherJS();
  749. } else {
  750. listener.pressed = true;
  751. }
  752. } else {
  753. listener.pressed = false;
  754. }
  755. };
  756. TogetherJS.once("ready", TogetherJS.removeShortcut);
  757. document.addEventListener("keyup", listener, false);
  758. };
  759. TogetherJS.removeShortcut = function () {
  760. if (listener) {
  761. document.addEventListener("keyup", listener, false);
  762. listener = null;
  763. }
  764. };
  765. TogetherJS.config.track("enableShortcut", function (enable, previous) {
  766. if (enable) {
  767. TogetherJS.listenForShortcut();
  768. } else if (previous) {
  769. TogetherJS.removeShortcut();
  770. }
  771. });
  772. TogetherJS.checkForUsersOnChannel = function (address, callback) {
  773. if (address.search(/^https?:/i) === 0) {
  774. address = address.replace(/^http/i, 'ws');
  775. }
  776. var socket = new WebSocket(address);
  777. var gotAnswer = false;
  778. socket.onmessage = function (event) {
  779. var msg = JSON.parse(event.data);
  780. if (msg.type != "init-connection") {
  781. console.warn("Got unexpected first message (should be init-connection):", msg);
  782. return;
  783. }
  784. if (gotAnswer) {
  785. console.warn("Somehow received two responses from channel; ignoring second");
  786. socket.close();
  787. return;
  788. }
  789. gotAnswer = true;
  790. socket.close();
  791. callback(msg.peerCount);
  792. };
  793. socket.onclose = socket.onerror = function () {
  794. if (! gotAnswer) {
  795. console.warn("Socket was closed without receiving answer");
  796. gotAnswer = true;
  797. callback(undefined);
  798. }
  799. };
  800. };
  801. // It's nice to replace this early, before the load event fires, so we conflict
  802. // as little as possible with the app we are embedded in:
  803. var hash = location.hash.replace(/^#/, "");
  804. var m = /&?togetherjs=([^&]*)/.exec(hash);
  805. if (m) {
  806. TogetherJS.startup._joinShareId = m[1];
  807. TogetherJS.startup.reason = "joined";
  808. var newHash = hash.substr(0, m.index) + hash.substr(m.index + m[0].length);
  809. location.hash = newHash;
  810. }
  811. if (window._TogetherJSShareId) {
  812. // A weird hack for something the addon does, to force a shareId.
  813. // FIXME: probably should remove, it's a wonky feature.
  814. TogetherJS.startup._joinShareId = window._TogetherJSShareId;
  815. delete window._TogetherJSShareId;
  816. }
  817. function conditionalActivate() {
  818. if (window.TogetherJSConfig_noAutoStart) {
  819. return;
  820. }
  821. // A page can define this function to defer TogetherJS from starting
  822. var callToStart = window.TogetherJSConfig_callToStart;
  823. if (! callToStart && window.TowTruckConfig_callToStart) {
  824. callToStart = window.TowTruckConfig_callToStart;
  825. console.warn("Please rename TowTruckConfig_callToStart to TogetherJSConfig_callToStart");
  826. }
  827. if (window.TogetherJSConfig && window.TogetherJSConfig.callToStart) {
  828. callToStart = window.TogetherJSConfig.callToStart;
  829. }
  830. if (callToStart) {
  831. // FIXME: need to document this:
  832. callToStart(onload);
  833. } else {
  834. onload();
  835. }
  836. }
  837. // FIXME: can we push this up before the load event?
  838. // Do we need to wait at all?
  839. function onload() {
  840. if (TogetherJS.startup._joinShareId) {
  841. TogetherJS();
  842. } else if (window._TogetherJSBookmarklet) {
  843. delete window._TogetherJSBookmarklet;
  844. TogetherJS();
  845. } else {
  846. // FIXME: this doesn't respect storagePrefix:
  847. var key = "togetherjs-session.status";
  848. var value = sessionStorage.getItem(key);
  849. if (value) {
  850. value = JSON.parse(value);
  851. if (value && value.running) {
  852. TogetherJS.startup.continued = true;
  853. TogetherJS.startup.reason = value.startupReason;
  854. TogetherJS();
  855. }
  856. } else if (window.TogetherJSConfig_autoStart ||
  857. (window.TogetherJSConfig && window.TogetherJSConfig.autoStart)) {
  858. TogetherJS.startup.reason = "joined";
  859. TogetherJS();
  860. }
  861. }
  862. }
  863. conditionalActivate();
  864. // FIXME: wait until load event to double check if this gets set?
  865. if (window.TogetherJSConfig_enableShortcut) {
  866. TogetherJS.listenForShortcut();
  867. }
  868. // For compatibility:
  869. window.TowTruck = TogetherJS;
  870. })();