chat.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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 evil:true */
  5. define(["require", "jquery", "util", "session", "ui", "templates", "playback", "storage", "peers", "windowing"], function (require, $, util, session, ui, templates, playback, storage, peers, windowing) {
  6. var chat = util.Module("chat");
  7. var assert = util.assert;
  8. var Walkabout;
  9. session.hub.on("chat", function (msg) {
  10. ui.chat.text({
  11. text: msg.text,
  12. peer: msg.peer,
  13. // FIXME: a little unsure of trusting this (maybe I should prefix it?)
  14. messageId: msg.messageId,
  15. notify: true
  16. });
  17. saveChatMessage({
  18. text: msg.text,
  19. date: Date.now(),
  20. peerId: msg.peer.id,
  21. messageId: msg.messageId
  22. });
  23. });
  24. // FIXME: this doesn't really belong in this module:
  25. session.hub.on("bye", function (msg) {
  26. ui.chat.leftSession({
  27. peer: msg.peer,
  28. declinedJoin: msg.reason == "declined-join"
  29. });
  30. });
  31. chat.submit = function (message) {
  32. var parts = message.split(/ /);
  33. if (parts[0].charAt(0) == "/") {
  34. var name = parts[0].substr(1).toLowerCase();
  35. var method = commands["command_" + name];
  36. if (method) {
  37. method.apply(null, parts.slice(1));
  38. return;
  39. }
  40. }
  41. var messageId = session.clientId + "-" + Date.now();
  42. session.send({
  43. type: "chat",
  44. text: message,
  45. messageId: messageId
  46. });
  47. ui.chat.text({
  48. text: message,
  49. peer: peers.Self,
  50. messageId: messageId,
  51. notify: false
  52. });
  53. saveChatMessage({
  54. text: message,
  55. date: Date.now(),
  56. peerId: peers.Self.id,
  57. messageId: messageId
  58. });
  59. };
  60. var commands = {
  61. command_help: function () {
  62. var msg = util.trim(templates("help"));
  63. ui.chat.system({
  64. text: msg
  65. });
  66. },
  67. command_test: function (args) {
  68. if (! Walkabout) {
  69. require(["walkabout"], (function (WalkaboutModule) {
  70. Walkabout = WalkaboutModule;
  71. this.command_test(args);
  72. }).bind(this));
  73. return;
  74. }
  75. args = util.trim(args || "").split(/\s+/g);
  76. if (args[0] === "" || ! args.length) {
  77. if (this._testCancel) {
  78. args = ["cancel"];
  79. } else {
  80. args = ["start"];
  81. }
  82. }
  83. if (args[0] == "cancel") {
  84. ui.chat.system({
  85. text: "Aborting test"
  86. });
  87. this._testCancel();
  88. this._testCancel = null;
  89. return;
  90. }
  91. if (args[0] == "start") {
  92. var times = parseInt(args[1], 10);
  93. if (isNaN(times) || ! times) {
  94. times = 100;
  95. }
  96. ui.chat.system({
  97. text: "Testing with walkabout.js"
  98. });
  99. var tmpl = $(templates("walkabout"));
  100. var container = ui.container.find(".togetherjs-test-container");
  101. container.empty();
  102. container.append(tmpl);
  103. container.show();
  104. var statusContainer = container.find(".togetherjs-status");
  105. statusContainer.text("starting...");
  106. this._testCancel = Walkabout.runManyActions({
  107. ondone: function () {
  108. statusContainer.text("done");
  109. statusContainer.one("click", function () {
  110. container.hide();
  111. });
  112. this._testCancel = null;
  113. },
  114. onstatus: function (status) {
  115. var note = "actions: " + status.actions.length + " running: " +
  116. (status.times - status.remaining) + " / " + status.times;
  117. statusContainer.text(note);
  118. }
  119. });
  120. return;
  121. }
  122. if (args[0] == "show") {
  123. if (this._testShow.length) {
  124. this._testShow.forEach(function (item) {
  125. if (item) {
  126. item.remove();
  127. }
  128. }, this);
  129. this._testShow = [];
  130. } else {
  131. var actions = Walkabout.findActions();
  132. actions.forEach(function (action) {
  133. this._testShow.push(action.show());
  134. }, this);
  135. }
  136. return;
  137. }
  138. if (args[0] == "describe") {
  139. Walkabout.findActions().forEach(function (action) {
  140. ui.chat.system({
  141. text: action.description()
  142. });
  143. }, this);
  144. return;
  145. }
  146. ui.chat.system({
  147. text: "Did not understand: " + args.join(" ")
  148. });
  149. },
  150. _testCancel: null,
  151. _testShow: [],
  152. command_clear: function () {
  153. ui.chat.clear();
  154. },
  155. command_exec: function () {
  156. var expr = Array.prototype.slice.call(arguments).join(" ");
  157. var result;
  158. // We use this to force global eval (not in this scope):
  159. var e = eval;
  160. try {
  161. result = e(expr);
  162. } catch (error) {
  163. ui.chat.system({
  164. text: "Error: " + error
  165. });
  166. }
  167. if (result !== undefined) {
  168. ui.chat.system({
  169. text: "" + result
  170. });
  171. }
  172. },
  173. command_record: function () {
  174. ui.chat.system({
  175. text: "When you see the robot appear, the recording will have started"
  176. });
  177. window.open(
  178. session.recordUrl(), "_blank",
  179. "left,width=" + ($(window).width() / 2));
  180. },
  181. playing: null,
  182. command_playback: function (url) {
  183. if (this.playing) {
  184. this.playing.cancel();
  185. this.playing.unload();
  186. this.playing = null;
  187. ui.chat.system({
  188. text: "playback cancelled"
  189. });
  190. return;
  191. }
  192. if (! url) {
  193. ui.chat.system({
  194. text: "Nothing is playing"
  195. });
  196. return;
  197. }
  198. var logLoader = playback.getLogs(url);
  199. logLoader.then(
  200. (function (logs) {
  201. if (! logs) {
  202. ui.chat.system({
  203. text: "No logs found."
  204. });
  205. return;
  206. }
  207. logs.save();
  208. this.playing = logs;
  209. logs.play();
  210. }).bind(this),
  211. function (error) {
  212. ui.chat.system({
  213. text: "Error fetching " + url + ":\n" + JSON.stringify(error, null, " ")
  214. });
  215. });
  216. windowing.hide("#togetherjs-chat");
  217. },
  218. command_savelogs: function (name) {
  219. session.send({
  220. type: "get-logs",
  221. forClient: session.clientId,
  222. saveAs: name
  223. });
  224. function save(msg) {
  225. if (msg.request.forClient == session.clientId && msg.request.saveAs == name) {
  226. storage.set("recording." + name, msg.logs).then(function () {
  227. session.hub.off("logs", save);
  228. ui.chat.system({
  229. text: "Saved as local:" + name
  230. });
  231. });
  232. }
  233. }
  234. session.hub.on("logs", save);
  235. },
  236. command_baseurl: function (url) {
  237. if (! url) {
  238. storage.get("baseUrlOverride").then(function (b) {
  239. if (b) {
  240. ui.chat.system({
  241. text: "Set to: " + b.baseUrl
  242. });
  243. } else {
  244. ui.chat.system({
  245. text: "No baseUrl override set"
  246. });
  247. }
  248. });
  249. return;
  250. }
  251. url = url.replace(/\/*$/, "");
  252. ui.chat.system({
  253. text: "If this goes wrong, do this in the console to reset:\n localStorage.setItem('togetherjs.baseUrlOverride', null)"
  254. });
  255. storage.set("baseUrlOverride", {
  256. baseUrl: url,
  257. expiresAt: Date.now() + (1000 * 60 * 60 * 24)
  258. }).then(function () {
  259. ui.chat.system({
  260. text: "baseUrl overridden (to " + url + "), will last for one day."
  261. });
  262. });
  263. },
  264. command_config: function (variable, value) {
  265. if (! (variable || value)) {
  266. storage.get("configOverride").then(function (c) {
  267. if (c) {
  268. util.forEachAttr(c, function (value, attr) {
  269. if (attr == "expiresAt") {
  270. return;
  271. }
  272. ui.chat.system({
  273. text: " " + attr + " = " + JSON.stringify(value)
  274. });
  275. });
  276. ui.chat.system({
  277. text: "Config expires at " + (new Date(c.expiresAt))
  278. });
  279. } else {
  280. ui.chat.system({
  281. text: "No config override"
  282. });
  283. }
  284. });
  285. return;
  286. }
  287. if (variable == "clear") {
  288. storage.set("configOverride", undefined);
  289. ui.chat.system({
  290. text: "Clearing all overridden configuration"
  291. });
  292. return;
  293. }
  294. console.log("config", [variable, value]);
  295. if (! (variable && value)) {
  296. ui.chat.system({
  297. text: "Error: must provide /config VAR VALUE"
  298. });
  299. return;
  300. }
  301. try {
  302. value = JSON.parse(value);
  303. } catch (e) {
  304. ui.chat.system({
  305. text: "Error: value (" + value + ") could not be parsed: " + e
  306. });
  307. return;
  308. }
  309. if (! TogetherJS._defaultConfiguration.hasOwnProperty(variable)) {
  310. ui.chat.system({
  311. text: "Warning: variable " + variable + " is unknown"
  312. });
  313. }
  314. storage.get("configOverride").then(function (c) {
  315. c = c || {};
  316. c[variable] = value;
  317. c.expiresAt = Date.now() + (1000 * 60 * 60 * 24);
  318. storage.set("configOverride", c).then(function () {
  319. ui.chat.system({
  320. text: "Variable " + variable + " = " + JSON.stringify(value) + "\nValue will be set for one day."
  321. });
  322. });
  323. });
  324. }
  325. };
  326. // this section deal with saving/restoring chat history as long as session is alive
  327. var chatStorageKey = "chatlog";
  328. var maxLogMessages = 100;
  329. function saveChatMessage(obj) {
  330. assert(obj.peerId);
  331. assert(obj.messageId);
  332. assert(obj.date);
  333. assert(typeof obj.text == "string");
  334. loadChatLog().then(function (log) {
  335. for (var i = log.length - 1; i >= 0; i--) {
  336. if (log[i].messageId === obj.messageId) {
  337. return;
  338. }
  339. }
  340. log.push(obj);
  341. if (log.length > maxLogMessages) {
  342. log.splice(0, log.length - maxLogMessages);
  343. }
  344. storage.tab.set(chatStorageKey, log);
  345. });
  346. }
  347. function loadChatLog() {
  348. return storage.tab.get(chatStorageKey, []);
  349. }
  350. session.once("ui-ready", function () {
  351. loadChatLog().then(function (log) {
  352. if (! log) {
  353. return;
  354. }
  355. for (var i = 0; i < log.length; i++) {
  356. // peers should already be loaded from sessionStorage by the peers module
  357. var currentPeer = peers.getPeer(log[i].peerId, null, true);
  358. if (!currentPeer) {
  359. // sometimes peers go away
  360. continue;
  361. }
  362. ui.chat.text({
  363. text: log[i].text,
  364. date: log[i].date,
  365. peer: currentPeer,
  366. messageId: log[i].messageId
  367. });
  368. }
  369. });
  370. });
  371. //delete chat log
  372. session.on("close", function(){
  373. storage.tab.set(chatStorageKey, undefined);
  374. });
  375. return chat;
  376. });