You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

201 lines
7.0 KiB

  1. define([
  2. '/api/config?cb=' + Math.random().toString(16).substring(2),
  3. '/common/messages.js',
  4. '/common/toolbar.js',
  5. '/common/chainpad.js',
  6. '/bower_components/jquery/dist/jquery.min.js',
  7. '/bower_components/tweetnacl/nacl-fast.min.js',
  8. '/common/otaml.js'
  9. ], function (Config, Messages) {
  10. var Nacl = window.nacl;
  11. var $ = jQuery;
  12. var ChainPad = window.ChainPad;
  13. var Otaml = window.Otaml;
  14. var Toolbar = window.Toolbar;
  15. var module = { exports: {} };
  16. var parseKey = function (str) {
  17. var array = Nacl.util.decodeBase64(str);
  18. var hash = Nacl.hash(array);
  19. return { lookupKey: hash.subarray(32), cryptKey: hash.subarray(0,32) };
  20. };
  21. var genKey = function () {
  22. return Nacl.util.encodeBase64(Nacl.randomBytes(18));
  23. };
  24. var userName = function () {
  25. return 'Other-' + Nacl.util.encodeBase64(Nacl.randomBytes(8));
  26. };
  27. var sheetToJson = function (ifrWindow) {
  28. var xx = ifrWindow.sh[0].jS
  29. var m = [];
  30. for (var i = 0; i < xx.spreadsheets.length; i++) {
  31. m[i]=[];
  32. var sheet = xx.spreadsheets[i];
  33. for (var j = 1; j < sheet.length; j++) {
  34. m[i][j]=[];
  35. var row = sheet[j];
  36. for (var k = 1; k < row.length; k++) {
  37. var col = row[k];
  38. m[i][j][k] = { value: col.value, formula: col.formula };
  39. }
  40. }
  41. }
  42. return m;
  43. };
  44. var jsonToSheet = function (ifrWindow, json) {
  45. var xx = ifrWindow.sh[0].jS;
  46. for (var i = 0; i < xx.spreadsheets.length; i++) {
  47. var sheet = xx.spreadsheets[i];
  48. for (var j = 1; j < sheet.length; j++) {
  49. var row = sheet[j];
  50. for (var k = 1; k < row.length; k++) {
  51. var col = row[k];
  52. var jcol = json[i][j][k];
  53. if (jcol.value === col.value && jcol.formula === col.formula) { continue; }
  54. col.value = jcol.value;
  55. col.formula = jcol.formula;
  56. col.displayValue();
  57. }
  58. }
  59. }
  60. };
  61. var encryptStr = function (str, key) {
  62. var array = Nacl.util.decodeUTF8(str);
  63. var nonce = Nacl.randomBytes(24);
  64. var packed = Nacl.secretbox(array, nonce, key);
  65. if (!packed) { throw new Error(); }
  66. return Nacl.util.encodeBase64(nonce) + "|" + Nacl.util.encodeBase64(packed);
  67. };
  68. var decryptStr = function (str, key) {
  69. var arr = str.split('|');
  70. if (arr.length !== 2) { throw new Error(); }
  71. var nonce = Nacl.util.decodeBase64(arr[0]);
  72. var packed = Nacl.util.decodeBase64(arr[1]);
  73. var unpacked = Nacl.secretbox.open(packed, nonce, key);
  74. if (!unpacked) { throw new Error(); }
  75. return Nacl.util.encodeUTF8(unpacked);
  76. };
  77. // this is crap because of bencoding messages... it should go away....
  78. var splitMessage = function (msg, sending) {
  79. var idx = 0;
  80. var nl;
  81. for (var i = ((sending) ? 0 : 1); i < 3; i++) {
  82. nl = msg.indexOf(':',idx);
  83. idx = nl + Number(msg.substring(idx,nl)) + 1;
  84. }
  85. return [ msg.substring(0,idx), msg.substring(msg.indexOf(':',idx) + 1) ];
  86. };
  87. var encrypt = function (msg, key) {
  88. var spl = splitMessage(msg, true);
  89. var json = JSON.parse(spl[1]);
  90. // non-patches are not encrypted.
  91. if (json[0] !== 2) { return msg; }
  92. json[1] = encryptStr(JSON.stringify(json[1]), key);
  93. var res = JSON.stringify(json);
  94. return spl[0] + res.length + ':' + res;
  95. };
  96. var decrypt = function (msg, key) {
  97. var spl = splitMessage(msg, false);
  98. var json = JSON.parse(spl[1]);
  99. // non-patches are not encrypted.
  100. if (json[0] !== 2) { return msg; }
  101. if (typeof(json[1]) !== 'string') { throw new Error(); }
  102. json[1] = JSON.parse(decryptStr(json[1], key));
  103. var res = JSON.stringify(json);
  104. return spl[0] + res.length + ':' + res;
  105. };
  106. var applyChange = function(ctx, oldval, newval) {
  107. if (oldval === newval) return;
  108. var commonStart = 0;
  109. while (oldval.charAt(commonStart) === newval.charAt(commonStart)) {
  110. commonStart++;
  111. }
  112. var commonEnd = 0;
  113. while (oldval.charAt(oldval.length - 1 - commonEnd) === newval.charAt(newval.length - 1 - commonEnd) &&
  114. commonEnd + commonStart < oldval.length && commonEnd + commonStart < newval.length) {
  115. commonEnd++;
  116. }
  117. if (oldval.length !== commonStart + commonEnd) {
  118. ctx.remove(commonStart, oldval.length - commonStart - commonEnd);
  119. }
  120. if (newval.length !== commonStart + commonEnd) {
  121. ctx.insert(commonStart, newval.slice(commonStart, newval.length - commonEnd));
  122. }
  123. };
  124. $(function () {
  125. if (window.location.href.indexOf('#') === -1) {
  126. window.location.href = window.location.href + '#' + genKey();
  127. }
  128. $(window).on('hashchange', function() {
  129. window.location.reload();
  130. });
  131. var $sheetJson = $('#sheet-json');
  132. var ifrw = $('iframe')[0].contentWindow;
  133. var sheetEvent = function (realtime) {
  134. var sheetJson = JSON.stringify(sheetToJson(ifrw));
  135. applyChange(realtime, realtime.getUserDoc(), sheetJson);
  136. $sheetJson.text(sheetJson);
  137. };
  138. var eventPending = false;
  139. var realtimeEvent = function (realtime) {
  140. if (eventPending) { return; }
  141. eventPending = true;
  142. setTimeout(function () {
  143. eventPending = false;
  144. try{
  145. var data = window.data = realtime.getUserDoc();
  146. $sheetJson.text(data);
  147. var json = JSON.parse(data);
  148. jsonToSheet(ifrw, json);
  149. }catch(e) { console.log(e.stack); }
  150. }, 0);
  151. };
  152. var key = parseKey(window.location.hash.substring(1));
  153. var channel = Nacl.util.encodeBase64(key.lookupKey).substring(0,10);
  154. var myUserName = userName();
  155. var socket = new WebSocket(Config.websocketURL);
  156. socket.onopen = function () {
  157. var realtime = ChainPad.create(
  158. myUserName, 'x', channel, '', { transformFunction: Otaml.transform });
  159. socket.onmessage = function (evt) {
  160. var message = decrypt(evt.data, key.cryptKey);
  161. realtime.message(message);
  162. };
  163. realtime.onMessage(function (message) {
  164. message = encrypt(message, key.cryptKey);
  165. try {
  166. socket.send(message);
  167. } catch (e) {
  168. console.log(e.stack);
  169. }
  170. });
  171. ifrw.sh.on('sheetCellEdited', function () { sheetEvent(realtime); });
  172. sheetEvent(realtime);
  173. realtime.onPatch(function () { realtimeEvent(realtime); });
  174. ifrw.$('.jSTitle').html('');
  175. Toolbar(ifrw.$, ifrw.$('.jSTitle'), Messages, myUserName, realtime);
  176. realtime.start();
  177. };
  178. });
  179. });