@tiddlywiki/mws 0.0.21 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +42 -9
  2. package/dist/metafile-esm.json +1 -0
  3. package/dist/mws.d.ts +1 -0
  4. package/dist/mws.js +34710 -0
  5. package/dist/mws.js.map +1 -0
  6. package/editions/mws-docs/tiddlers/$__StoryList.tid +1 -2
  7. package/editions/mws-docs/tiddlers/2025-02-18 - User Management.md.meta +2 -2
  8. package/editions/mws-docs/tiddlers/2025-04-14 - client plugins /342/234/224/357/270/217.md" +31 -0
  9. package/editions/mws-docs/tiddlers/2025-04-14 - client plugins /342/234/224/357/270/217.md.meta" +5 -0
  10. package/editions/mws-docs/tiddlers/{2025-05-04.md → 2025-05-04 - event hooks /342/234/224/357/270/217.md" } +7 -1
  11. package/editions/mws-docs/tiddlers/2025-05-04 - event hooks /342/234/224/357/270/217.md.meta" +5 -0
  12. package/editions/mws-docs/tiddlers/{2025-05-06.md → 2025-05-06 - syncer design.md } +0 -4
  13. package/editions/mws-docs/tiddlers/2025-05-06 - syncer design.md.meta +5 -0
  14. package/editions/mws-docs/tiddlers/Allowed Methods.tid +8 -0
  15. package/editions/mws-docs/tiddlers/Application Access & Security.md +14 -84
  16. package/editions/mws-docs/tiddlers/Application Access & Security.md.meta +1 -1
  17. package/editions/mws-docs/tiddlers/Body Format.md +19 -0
  18. package/editions/mws-docs/tiddlers/Body Format.md.meta +4 -0
  19. package/editions/mws-docs/tiddlers/Checklists and Features.md +0 -3
  20. package/editions/mws-docs/tiddlers/Checklists and Features.md.meta +1 -1
  21. package/editions/mws-docs/tiddlers/Client Plugins.md +17 -0
  22. package/editions/mws-docs/tiddlers/Client Plugins.md.meta +5 -0
  23. package/editions/mws-docs/tiddlers/HTTP API.md +4 -3
  24. package/editions/mws-docs/tiddlers/HTTP API.md.meta +1 -1
  25. package/editions/mws-docs/tiddlers/HelloThere.tid +2 -2
  26. package/editions/mws-docs/tiddlers/Installation.tid +2 -3
  27. package/editions/mws-docs/tiddlers/MWS Architecture.tid +6 -1
  28. package/editions/mws-docs/tiddlers/Multi-user Trust.md +12 -0
  29. package/editions/mws-docs/tiddlers/Multi-user Trust.md.meta +5 -0
  30. package/editions/mws-docs/tiddlers/Notes.tid +5 -3
  31. package/editions/mws-docs/tiddlers/Oracle Attacks.md +29 -0
  32. package/editions/mws-docs/tiddlers/Oracle Attacks.md.meta +5 -0
  33. package/editions/mws-docs/tiddlers/Reference.md +1 -0
  34. package/editions/mws-docs/tiddlers/Reference.md.meta +1 -1
  35. package/editions/mws-docs/tiddlers/Request Handling.md +53 -0
  36. package/editions/mws-docs/tiddlers/Request Handling.md.meta +5 -0
  37. package/editions/mws-docs/tiddlers/Roadmap.tid +7 -0
  38. package/editions/mws-docs/tiddlers/Route Definers.md +206 -0
  39. package/editions/mws-docs/tiddlers/Route Definers.md.meta +5 -0
  40. package/editions/mws-docs/tiddlers/Security.md +75 -0
  41. package/editions/mws-docs/tiddlers/Security.md.meta +5 -0
  42. package/editions/mws-docs/tiddlers/Server Plugins.md +7 -0
  43. package/editions/mws-docs/tiddlers/Server Plugins.md.meta +5 -0
  44. package/editions/mws-docs/tiddlers/Startup Cache.md +28 -0
  45. package/editions/mws-docs/tiddlers/Startup Cache.md.meta +5 -0
  46. package/editions/mws-docs/tiddlers/TiddlerFields.md +5 -0
  47. package/editions/mws-docs/tiddlers/TiddlerFields.md.meta +4 -0
  48. package/editions/mws-docs/tiddlers/Usage.tid +2 -2
  49. package/editions/mws-docs/tiddlers/system/$__mws_cssvar-macro.tid +10 -0
  50. package/editions/mws-docs/tiddlers/system/$__palettes_MUI +143 -0
  51. package/editions/mws-docs/tiddlers/system/$__palettes_MUI.meta +7 -0
  52. package/editions/mws-docs/tiddlers/system/$__palettes_MUI_styles.css +567 -0
  53. package/editions/mws-docs/tiddlers/system/$__palettes_MUI_styles.css.meta +6 -0
  54. package/editions/mws-docs/tiddlers/system/$__theme.tid +6 -0
  55. package/editions/mws-docs/tiddlers/system/SplashScreen.tid +21 -0
  56. package/editions/mws-docs/tiddlers/system/StaticBanner.tid +1 -1
  57. package/editions/mws-docs/tiddlers/system/Styles.tid +13 -0
  58. package/editions/mws-docs/tiddlers/system/palette.tid +4 -1
  59. package/editions/mws-docs/tiddlers/system/sidebarlayout.tid +4 -1
  60. package/editions/mws-docs/tiddlers/x-mws-tiddler.md +35 -0
  61. package/editions/mws-docs/tiddlers/x-mws-tiddler.md.meta +5 -0
  62. package/mws.run.mjs +30 -0
  63. package/package.json +47 -36
  64. package/plugins/client/package-lock.json +46 -83
  65. package/plugins/client/package.json +1 -6
  66. package/plugins/client/plugin.info +2 -2
  67. package/plugins/client/src/fflate.d.ts +1501 -0
  68. package/plugins/client/src/multiwikiclientadaptor.ts +768 -317
  69. package/plugins/client/src/mws.d.ts +4 -0
  70. package/plugins/client/src/types.d.ts +3928 -0
  71. package/plugins/client/tiddlers/en-US.multids +3 -0
  72. package/plugins/client/tiddlers/status/SaveWikiButtonTemplate.tid +3 -0
  73. package/plugins/client/tiddlers/status/icon-cloud.tid +4 -0
  74. package/plugins/client/tiddlers/status/info-segment.tid +8 -0
  75. package/plugins/client/tiddlers/status/readonly-styles.tid +27 -0
  76. package/plugins/client/tiddlers/status/save-offline.tid +7 -0
  77. package/plugins/client/tiddlers/status/save-wiki-button.tid +26 -0
  78. package/plugins/client/tiddlers/status/styles.tid +44 -0
  79. package/plugins/client/tiddlers/status/syncer-actions-copy-logs.tid +6 -0
  80. package/plugins/client/tiddlers/status/syncer-actions-login-status.tid +9 -0
  81. package/plugins/client/tiddlers/status/syncer-actions-login.tid +8 -0
  82. package/plugins/client/tiddlers/status/syncer-actions-logout.tid +8 -0
  83. package/plugins/client/tiddlers/status/syncer-actions-refresh.tid +9 -0
  84. package/plugins/client/tiddlers/status/syncer-actions-save-snapshot.tid +9 -0
  85. package/plugins/client/tiddlers/status/tags-syncerdropdown.tid +2 -0
  86. package/plugins/client/tiddlers/syncer/multiwikiclientadaptor.js +640 -0
  87. package/plugins/client/tiddlers/syncer/syncer.js +831 -0
  88. package/plugins/client/tsconfig.json +19 -16
  89. package/plugins/filter-ast-to-sql/example-run-series.ts +146 -0
  90. package/plugins/server/SplashScreen.tid +150 -0
  91. package/plugins/server/fflate.js +9 -0
  92. package/plugins/server/plugin.info +8 -0
  93. package/plugins/server/readme +1 -0
  94. package/plugins/server/url-polyfill.js +294 -0
  95. package/prisma/client/client.d.ts +1 -0
  96. package/prisma/client/client.js +3 -0
  97. package/prisma/client/default.d.ts +1 -0
  98. package/prisma/client/default.js +3 -0
  99. package/prisma/client/edge.d.ts +1 -0
  100. package/prisma/client/edge.js +297 -0
  101. package/prisma/client/index-browser.js +275 -0
  102. package/prisma/client/index.d.ts +17770 -0
  103. package/prisma/client/index.js +324 -0
  104. package/prisma/client/package.json +173 -0
  105. package/prisma/client/query_engine_bg.js +811 -0
  106. package/prisma/client/query_engine_bg.wasm +0 -0
  107. package/prisma/client/runtime/edge-esm.js +10000 -0
  108. package/prisma/client/runtime/edge.js +10036 -0
  109. package/prisma/client/runtime/index-browser.d.ts +370 -0
  110. package/prisma/client/runtime/index-browser.js +2300 -0
  111. package/prisma/client/runtime/library.d.ts +3681 -0
  112. package/prisma/client/runtime/library.js +10604 -0
  113. package/prisma/client/runtime/react-native.js +10629 -0
  114. package/prisma/client/runtime/wasm-compiler-edge.js +11238 -0
  115. package/prisma/client/runtime/wasm-engine-edge.js +7801 -0
  116. package/prisma/client/schema.prisma +156 -0
  117. package/prisma/client/wasm-edge-light-loader.mjs +4 -0
  118. package/prisma/client/wasm-worker-loader.mjs +4 -0
  119. package/prisma/client/wasm.d.ts +1 -0
  120. package/prisma/client/wasm.js +304 -0
  121. package/prisma/global.ts +53 -0
  122. package/prisma/migrations/{20250406213424_init → 20250606001949_init}/migration.sql +46 -48
  123. package/prisma/migrations/20250613000526_remove_group/migration.sql +22 -0
  124. package/prisma/migrations/20250620014502_settings/migration.sql +5 -0
  125. package/prisma/migrations/20250707205229_recipes/migration.sql +20 -0
  126. package/prisma/migrations/migration_lock.toml +1 -1
  127. package/prisma/notes.md +1 -2
  128. package/prisma/schema.prisma +52 -49
  129. package/prisma/test.sqlite +0 -0
  130. package/prisma/test.sqlite-journal +0 -0
  131. package/public/react-admin/android-chrome-192x192.png +0 -0
  132. package/public/react-admin/android-chrome-512x512.png +0 -0
  133. package/public/react-admin/apple-touch-icon.png +0 -0
  134. package/{react-user-mgmt/public/esm-QKDFEP3B.js → public/react-admin/esm-L4WLVWJN.js} +1 -1
  135. package/public/react-admin/esm-L4WLVWJN.js.map +7 -0
  136. package/public/react-admin/esm-QMNGI3LF.js +3 -0
  137. package/public/react-admin/favicon-16x16.png +0 -0
  138. package/public/react-admin/favicon-32x32.png +0 -0
  139. package/public/react-admin/favicon.ico +0 -0
  140. package/public/react-admin/index.html +36 -0
  141. package/public/react-admin/main.js +268 -0
  142. package/public/react-admin/main.js.map +7 -0
  143. package/public/react-admin/site.webmanifest +20 -0
  144. package/public/react-admin/stats.json +1 -0
  145. package/scripts.mjs +134 -25
  146. package/dist/server.d.ts +0 -945
  147. package/dist/server.js +0 -20775
  148. package/dist/server.js.map +0 -1
  149. package/editions/mws-docs/tiddlers/2025-04-03 - Phases of Development.md +0 -64
  150. package/editions/mws-docs/tiddlers/2025-04-03 - Phases of Development.md.meta +0 -5
  151. package/editions/mws-docs/tiddlers/2025-04-13 - Plugins.md +0 -18
  152. package/editions/mws-docs/tiddlers/2025-04-13 - Plugins.md.meta +0 -5
  153. package/editions/mws-docs/tiddlers/2025-05-04.md.meta +0 -5
  154. package/editions/mws-docs/tiddlers/2025-05-06.md.meta +0 -5
  155. package/editions/mws-docs/tiddlers/Moduler Design.md +0 -29
  156. package/editions/mws-docs/tiddlers/Moduler Design.md.meta +0 -5
  157. package/editions/mws-docs/tiddlers/Request Server.md +0 -20
  158. package/editions/mws-docs/tiddlers/Request Server.md.meta +0 -5
  159. package/editions/mws-docs/tiddlers/system/$__palettes_MUI-Dark +0 -173
  160. package/editions/mws-docs/tiddlers/system/$__palettes_MUI-Dark.meta +0 -6
  161. package/editions/mws-docs/tiddlers/system/$__palettes_MUI-Light +0 -172
  162. package/editions/mws-docs/tiddlers/system/$__palettes_MUI-Light.meta +0 -6
  163. package/plugins/client/tiddlers/config-tiddlers-filter.tid +0 -2
  164. package/plugins/client/tiddlers/config-use-server-sent-events.tid +0 -2
  165. package/plugins/client/tiddlers/info-segment.tid +0 -8
  166. package/plugins/client/tiddlers/multiwikiclientadaptor.js +0 -444
  167. package/prisma/migrations/20250407002306_disconnect_user_tables/migration.sql +0 -52
  168. package/prisma/old-db.sql +0 -144
  169. package/react-user-mgmt/README.md +0 -50
  170. package/react-user-mgmt/eslint.config.js +0 -28
  171. package/react-user-mgmt/package-lock.json +0 -2738
  172. package/react-user-mgmt/package.json +0 -31
  173. package/react-user-mgmt/public/index.html +0 -1
  174. package/react-user-mgmt/public/main.js +0 -295
  175. package/react-user-mgmt/public/main.js.map +0 -7
  176. package/react-user-mgmt/public/stats.json +0 -1
  177. package/react-user-mgmt/tsconfig.json +0 -40
  178. package/react-user-mgmt/types.d.ts +0 -2
  179. package/tiddlywiki-types/boot/boot.d.ts +0 -378
  180. package/tiddlywiki-types/boot/bootprefix.d.ts +0 -110
  181. package/tiddlywiki-types/core/modules/filters.d.ts +0 -107
  182. package/tiddlywiki-types/core/modules/tiddler.d.ts +0 -47
  183. package/tiddlywiki-types/core/modules/wiki-bulkops.d.ts +0 -36
  184. package/tiddlywiki-types/core/modules/wiki.d.ts +0 -208
  185. package/tiddlywiki-types/index.d.ts +0 -7
  186. package/tiddlywiki-types/package.json +0 -3
  187. /package/plugins/client/tiddlers/{sidebarsegment.tid → status/sidebarsegment.tid} +0 -0
  188. /package/plugins/client/tiddlers/{GettingStarted.tid → syncer/GettingStarted.tid} +0 -0
  189. /package/plugins/client/tiddlers/{config-incoming-updates-filter.tid → syncer/config-incoming-updates-filter.tid} +0 -0
  190. /package/plugins/client/tiddlers/{readme.tid → syncer/readme.tid} +0 -0
  191. /package/{react-user-mgmt/public → public/react-admin}/chunk-YALWN5OT.js +0 -0
  192. /package/{react-user-mgmt/public → public/react-admin}/chunk-YALWN5OT.js.map +0 -0
  193. /package/{react-user-mgmt/public/esm-QKDFEP3B.js.map → public/react-admin/esm-QMNGI3LF.js.map} +0 -0
  194. /package/{react-user-mgmt/public → public/react-admin}/favicon.png +0 -0
  195. /package/{react-user-mgmt/public → public/react-admin}/missing-favicon.png +0 -0
  196. /package/{react-user-mgmt/public → public/react-admin}/motovun-jack.jpg +0 -0
  197. /package/{react-user-mgmt/public → public/react-admin}/mws-icon.png +0 -0
  198. /package/{react-user-mgmt/public → public/react-admin}/stats.css +0 -0
  199. /package/{react-user-mgmt/public → public/react-admin}/stats.html +0 -0
  200. /package/{react-user-mgmt/public → public/react-admin}/stats.js +0 -0
  201. /package/{react-user-mgmt/public → public/react-admin}/stats.svg +0 -0
  202. /package/{react-user-mgmt/public → public/react-admin}/vite.svg +0 -0
@@ -0,0 +1,640 @@
1
+ /*\
2
+ title: $:/plugins/mws/client/tiddlywebadaptor.js
3
+ type: application/javascript
4
+ module-type: syncadaptor
5
+
6
+ A sync adaptor module for synchronising with MultiWikiServer-compatible servers.
7
+
8
+ It has three key areas of concern:
9
+
10
+ * Basic operations like put, get, and delete a tiddler on the server
11
+ * Real time updates from the server (handled by SSE)
12
+ * Bags and recipes, which are unknown to the syncer
13
+
14
+ A key aspect of the design is that the syncer never overlaps basic server operations; it waits for the
15
+ previous operation to complete before sending a new one.
16
+
17
+ \*/
18
+ // the blank line is important, and so is the following use strict
19
+ "use strict";
20
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
21
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
22
+ return new (P || (P = Promise))(function (resolve, reject) {
23
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
24
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
25
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
26
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
27
+ });
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ var CONFIG_HOST_TIDDLER = "$:/config/multiwikiclient/host", DEFAULT_HOST_TIDDLER = "$protocol$//$host$/", MWC_STATE_TIDDLER_PREFIX = "$:/state/multiwikiclient/", BAG_STATE_TIDDLER = "$:/state/multiwikiclient/tiddlers/bag", REVISION_STATE_TIDDLER = "$:/state/multiwikiclient/tiddlers/revision", CONNECTION_STATE_TIDDLER = "$:/state/multiwikiclient/connection", INCOMING_UPDATES_FILTER_TIDDLER = "$:/config/multiwikiclient/incoming-updates-filter", ENABLE_SSE_TIDDLER = "$:/config/multiwikiclient/use-server-sent-events", IS_DEV_MODE_TIDDLER = `$:/state/multiwikiclient/dev-mode`, ENABLE_GZIP_STREAM_TIDDLER = `$:/state/multiwikiclient/gzip-stream`;
31
+ var SERVER_NOT_CONNECTED = "NOT CONNECTED", SERVER_CONNECTING_SSE = "CONNECTING SSE", SERVER_CONNECTED_SSE = "CONNECTED SSE", SERVER_POLLING = "SERVER POLLING";
32
+ class MultiWikiClientAdaptor {
33
+ constructor(options) {
34
+ this.triggerPoll = null;
35
+ this.name = "multiwikiclient";
36
+ this.supportsLazyLoading = true;
37
+ this.error = null;
38
+ this.wiki = options.wiki;
39
+ this.host = this.getHost();
40
+ this.recipe = this.wiki.getTiddlerText("$:/config/multiwikiclient/recipe");
41
+ this.useServerSentEvents = this.wiki.getTiddlerText(ENABLE_SSE_TIDDLER) === "yes";
42
+ this.isDevMode = this.wiki.getTiddlerText(IS_DEV_MODE_TIDDLER) === "yes";
43
+ this.useGzipStream = this.wiki.getTiddlerText(ENABLE_GZIP_STREAM_TIDDLER) === "yes";
44
+ this.last_known_revision_id = this.wiki.getTiddlerText("$:/state/multiwikiclient/recipe/last_revision_id", "0");
45
+ this.outstandingRequests = Object.create(null); // Hashmap by title of outstanding request object: {type: "PUT"|"GET"|"DELETE"}
46
+ this.lastRecordedUpdate = Object.create(null); // Hashmap by title of last recorded update via SSE: {type: "update"|"detetion", revision_id:}
47
+ this.logger = new $tw.utils.Logger("MultiWikiClientAdaptor");
48
+ this.isLoggedIn = false;
49
+ this.isReadOnly = false;
50
+ this.offline = false;
51
+ this.username = "";
52
+ // Compile the dirty tiddler filter
53
+ this.incomingUpdatesFilterFn = this.wiki.compileFilter(this.wiki.getTiddlerText(INCOMING_UPDATES_FILTER_TIDDLER));
54
+ this.setConnectionStatus(SERVER_NOT_CONNECTED);
55
+ }
56
+ setConnectionStatus(status) {
57
+ this.serverUpdateConnectionStatus = status;
58
+ this.wiki.addTiddler({
59
+ title: CONNECTION_STATE_TIDDLER,
60
+ text: status
61
+ });
62
+ }
63
+ setLoggerSaveBuffer(loggerForSaving) {
64
+ this.logger.setSaveBuffer(loggerForSaving);
65
+ }
66
+ isReady() {
67
+ return true;
68
+ }
69
+ subscribe(callback) {
70
+ // TODO: not working at the moment for some reason
71
+ console.log("Subscribing to server updates");
72
+ this.triggerPoll = callback;
73
+ this.connectRecipeEvents();
74
+ }
75
+ getHost() {
76
+ var text = this.wiki.getTiddlerText(CONFIG_HOST_TIDDLER, DEFAULT_HOST_TIDDLER), substitutions = [
77
+ { name: "protocol", value: document.location.protocol },
78
+ { name: "host", value: document.location.host },
79
+ { name: "pathname", value: document.location.pathname }
80
+ ];
81
+ for (var t = 0; t < substitutions.length; t++) {
82
+ var s = substitutions[t];
83
+ text = $tw.utils.replaceString(text, new RegExp("\\$" + s.name + "\\$", "mg"), s.value);
84
+ }
85
+ return text;
86
+ }
87
+ getTiddlerInfo(tiddler) {
88
+ var title = tiddler.fields.title, revision = this.wiki.extractTiddlerDataItem(REVISION_STATE_TIDDLER, title), bag = this.wiki.extractTiddlerDataItem(BAG_STATE_TIDDLER, title);
89
+ if (revision && bag) {
90
+ return {
91
+ title: title,
92
+ revision: revision,
93
+ bag: bag
94
+ };
95
+ }
96
+ else {
97
+ return undefined;
98
+ }
99
+ }
100
+ getTiddlerBag(title) {
101
+ return this.wiki.extractTiddlerDataItem(BAG_STATE_TIDDLER, title);
102
+ }
103
+ getTiddlerRevision(title) {
104
+ return this.wiki.extractTiddlerDataItem(REVISION_STATE_TIDDLER, title);
105
+ }
106
+ setTiddlerInfo({ title, revision_id, bag_name }) {
107
+ this.wiki.setText(BAG_STATE_TIDDLER, null, title, bag_name, { suppressTimestamp: true });
108
+ this.wiki.setText(REVISION_STATE_TIDDLER, null, title, revision_id, { suppressTimestamp: true });
109
+ }
110
+ removeTiddlerInfo({ title, revision_id, bag_id }) {
111
+ this.wiki.setText(BAG_STATE_TIDDLER, null, title, undefined, { suppressTimestamp: true });
112
+ this.wiki.setText(REVISION_STATE_TIDDLER, null, title, undefined, { suppressTimestamp: true });
113
+ }
114
+ /*
115
+ Get the current status of the server connection
116
+ */
117
+ getStatus(callback) {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ var _a, _b, _c, _d;
120
+ const [ok, error, data] = yield this.recipeRequest({
121
+ key: "handleGetRecipeStatus",
122
+ method: "GET",
123
+ url: "/status",
124
+ });
125
+ if (!ok && (data === null || data === void 0 ? void 0 : data.status) === 0) {
126
+ this.error = "The webpage is forbidden from contacting the server.";
127
+ this.isLoggedIn = false;
128
+ this.isReadOnly = true;
129
+ this.username = "(offline)";
130
+ this.offline = true;
131
+ }
132
+ else if (ok) {
133
+ this.error = null;
134
+ const status = data.responseJSON;
135
+ this.isReadOnly = (_a = status === null || status === void 0 ? void 0 : status.isReadOnly) !== null && _a !== void 0 ? _a : true;
136
+ this.isLoggedIn = (_b = status === null || status === void 0 ? void 0 : status.isLoggedIn) !== null && _b !== void 0 ? _b : false;
137
+ this.username = (_c = status === null || status === void 0 ? void 0 : status.username) !== null && _c !== void 0 ? _c : "(anon)";
138
+ this.offline = false;
139
+ }
140
+ else {
141
+ this.error = (_d = error.message) !== null && _d !== void 0 ? _d : `${error}`;
142
+ }
143
+ if (callback) {
144
+ callback(
145
+ // Error
146
+ this.error,
147
+ // Is logged in
148
+ this.isLoggedIn,
149
+ // Username
150
+ this.username,
151
+ // Is read only
152
+ this.isReadOnly,
153
+ // Is anonymous
154
+ // no idea what this means, always return false
155
+ false);
156
+ }
157
+ });
158
+ }
159
+ /*
160
+ Get details of changed tiddlers from the server
161
+ */
162
+ getUpdatedTiddlers(syncer, callback) {
163
+ return __awaiter(this, void 0, void 0, function* () {
164
+ if (this.offline)
165
+ return callback(null);
166
+ if (!this.bags || this.serverUpdateConnectionStatus === SERVER_NOT_CONNECTED)
167
+ yield this.pollServer();
168
+ const { modifications, deletions, last_revision } = this.updateBags();
169
+ this.last_known_revision_id = last_revision;
170
+ callback(null, { modifications, deletions });
171
+ });
172
+ }
173
+ connectRecipeEvents() {
174
+ const event = new EventSource(this.host + "recipe/" + encodeURIComponent(this.recipe) + "/events?first-event-id=" + encodeURIComponent(this.last_known_revision_id));
175
+ this.setConnectionStatus(SERVER_CONNECTING_SSE);
176
+ let syncTimeout = null;
177
+ const debounceSyncFromServer = () => {
178
+ if (syncTimeout) {
179
+ clearTimeout(syncTimeout);
180
+ }
181
+ syncTimeout = setTimeout(() => {
182
+ var _a;
183
+ syncTimeout = null;
184
+ (_a = this.triggerPoll) === null || _a === void 0 ? void 0 : _a.call(this);
185
+ }, 100);
186
+ };
187
+ const onUpdate = (e) => {
188
+ if (!this.bags)
189
+ return console.log("No bags loaded, cannot process updates");
190
+ const updates = JSON.parse(e.data);
191
+ const existBags = new Map(this.bags.map(b => [b.bag_name, b.tiddlers]));
192
+ updates.forEach(e => {
193
+ const existBag = existBags.get(e.bag.bag_name);
194
+ if (!existBag)
195
+ return;
196
+ const existTids = new Map(existBag.map(t => [t.title, t]));
197
+ e.bag.tiddlers.forEach(tid => {
198
+ const existTid = existTids.get(tid.title);
199
+ if (existTid) {
200
+ Object.assign(existTid, tid);
201
+ }
202
+ else {
203
+ existBag.push(tid);
204
+ }
205
+ });
206
+ });
207
+ debounceSyncFromServer();
208
+ };
209
+ // event.addEventListener("tiddler.save", onEvent);
210
+ // event.addEventListener("tiddler.delete", onEvent);
211
+ event.addEventListener("tiddler.since-last-event", onUpdate);
212
+ event.addEventListener("test", console.log);
213
+ event.addEventListener("open", () => {
214
+ this.setConnectionStatus(SERVER_CONNECTED_SSE);
215
+ console.log("Connected to server events");
216
+ });
217
+ event.addEventListener("error", (e) => {
218
+ if (event.readyState === EventSource.CLOSED) {
219
+ this.setConnectionStatus(SERVER_NOT_CONNECTED);
220
+ console.error("Server events connection closed");
221
+ }
222
+ else if (event.readyState === EventSource.CONNECTING) {
223
+ this.setConnectionStatus(SERVER_CONNECTING_SSE);
224
+ console.warn("Connecting to server events");
225
+ }
226
+ else {
227
+ console.error("Error in server events connection", e);
228
+ }
229
+ });
230
+ }
231
+ pollServer() {
232
+ return __awaiter(this, void 0, void 0, function* () {
233
+ const [ok, err, result] = yield this.recipeRequest({
234
+ key: "handleGetBags",
235
+ url: "/bags",
236
+ method: "GET",
237
+ });
238
+ if (!ok)
239
+ throw err;
240
+ if (!result.responseJSON)
241
+ throw new Error("no result returned");
242
+ this.bags = yield Promise.all(result.responseJSON.map(e => this.recipeRequest({
243
+ key: "handleGetBagState",
244
+ url: "/bags/" + encodeURIComponent(e.bag_name) + "/state",
245
+ method: "GET",
246
+ queryParams: { include_deleted: "yes" },
247
+ }).then(([ok, err, f]) => {
248
+ if (!ok)
249
+ throw err;
250
+ if (!f.responseJSON)
251
+ throw new Error("no result returned");
252
+ return Object.assign(Object.assign({}, f.responseJSON), { position: e.position });
253
+ })));
254
+ this.bags.sort((a, b) => b.position - a.position);
255
+ });
256
+ }
257
+ updateBags() {
258
+ if (!this.bags)
259
+ throw new Error("No bags loaded, call pollServer first");
260
+ const modified = new Set(), deleted = new Set();
261
+ const incomingTitles = new Set(this.bags.map(
262
+ // get the titles in each layer that aren't deleted
263
+ e => e.tiddlers.filter(f => !f.is_deleted).map(f => f.title)
264
+ // and flatten them for Set
265
+ ).flat());
266
+ let last_revision = this.last_known_revision_id;
267
+ this.bags.forEach(bag => {
268
+ bag.tiddlers.forEach(tid => {
269
+ // if the revision is old, ignore, since deletions create a new revision
270
+ if (tid.revision_id <= this.last_known_revision_id)
271
+ return;
272
+ if (tid.revision_id > last_revision)
273
+ last_revision = tid.revision_id;
274
+ // check if this title still exists in any layer
275
+ if (incomingTitles.has(tid.title))
276
+ modified.add(tid.title);
277
+ else
278
+ deleted.add(tid.title);
279
+ });
280
+ });
281
+ return {
282
+ modifications: [...modified.keys()],
283
+ deletions: [...deleted.keys()],
284
+ last_revision,
285
+ incomingTitles,
286
+ };
287
+ }
288
+ /*
289
+ Queue a load for a tiddler if there has been an update for it since the specified revision
290
+ */
291
+ checkLastRecordedUpdate(title, revision) {
292
+ // var lru = this.lastRecordedUpdate[title];
293
+ // if (lru && lru.revision_id > revision) {
294
+ // console.log(`Checking for updates to ${title} since ${JSON.stringify(lru)} comparing to ${revision}`);
295
+ // this.syncer && this.syncer.enqueueLoadTiddler(title);
296
+ // }
297
+ }
298
+ get syncer() {
299
+ if ($tw.syncadaptor === this)
300
+ return $tw.syncer;
301
+ }
302
+ loadTiddlers(options) {
303
+ return __awaiter(this, void 0, void 0, function* () {
304
+ const { syncer, titles, onNext, onDone, onError } = options;
305
+ const tiddlers = yield this.rpcRequest({
306
+ key: "rpcLoadRecipeTiddlerList",
307
+ body: { titles: titles }
308
+ }).catch(err => {
309
+ onError(err);
310
+ throw new Error("Error loading tiddlers: " + err.message);
311
+ });
312
+ tiddlers.forEach(({ bag_name, tiddler: fields }) => {
313
+ this.setTiddlerInfo({ title: fields.title, revision_id: fields.revision_id, bag_name: bag_name });
314
+ onNext(fields);
315
+ });
316
+ onDone();
317
+ });
318
+ }
319
+ saveTiddlers(options) {
320
+ return __awaiter(this, void 0, void 0, function* () {
321
+ const { syncer, tiddlers, onNext, onDone, onError } = options;
322
+ const fields = tiddlers.map(e => e.getFieldStrings());
323
+ const results = yield this.rpcRequest({
324
+ key: "rpcSaveRecipeTiddlerList",
325
+ body: { tiddlers: fields }
326
+ }).catch(err => {
327
+ onError(err);
328
+ throw new Error("Error saving tiddlers: " + err.message);
329
+ });
330
+ results.forEach((tiddler) => {
331
+ this.setTiddlerInfo({ title: tiddler.title, revision_id: tiddler.revision_id, bag_name: tiddler.bag.bag_name });
332
+ onNext(tiddler.title, {
333
+ bag: tiddler.bag.bag_name,
334
+ title: tiddler.title,
335
+ revision: tiddler.revision_id
336
+ }, tiddler.revision_id);
337
+ });
338
+ onDone();
339
+ });
340
+ }
341
+ deleteTiddlers(options) {
342
+ return __awaiter(this, void 0, void 0, function* () {
343
+ const { syncer, titles, onNext, onDone, onError } = options;
344
+ const results = yield this.rpcRequest({
345
+ key: "rpcDeleteRecipeTiddlerList",
346
+ body: { titles: titles }
347
+ }).catch(err => {
348
+ onError(err);
349
+ throw new Error("Error deleting tiddlers: " + err.message);
350
+ });
351
+ results.forEach(({ title, revision_id, bag_id }) => {
352
+ this.removeTiddlerInfo({ title, revision_id, bag_id });
353
+ onNext(title);
354
+ });
355
+ onDone();
356
+ });
357
+ }
358
+ /*
359
+ Save a tiddler and invoke the callback with (err,adaptorInfo,revision)
360
+ */
361
+ saveTiddler(tiddler, callback, options) {
362
+ return __awaiter(this, void 0, void 0, function* () {
363
+ var self = this, title = tiddler.fields.title;
364
+ if (title === "$:/StoryList") {
365
+ return callback(null);
366
+ }
367
+ if (this.isReadOnly || title.substr(0, MWC_STATE_TIDDLER_PREFIX.length) === MWC_STATE_TIDDLER_PREFIX) {
368
+ return callback(null);
369
+ }
370
+ self.outstandingRequests[title] = { type: "PUT" };
371
+ // application/x-mws-tiddler
372
+ // The .tid file format does not support field names with colons.
373
+ // Rather than trying to catch all the unsupported variations that may appear,
374
+ // we'll just use JSON to send it across the wire, since that is the official fallback format anyway.
375
+ // However, parsing a huge string value inside a JSON object is very slow,
376
+ // so we split off the text field and send it after the other fields.
377
+ const fields = tiddler.getFieldStrings({});
378
+ const text = fields.text;
379
+ delete fields.text;
380
+ let body = JSON.stringify(fields);
381
+ if (tiddler.hasField("text")) {
382
+ if (typeof text !== "string" && text)
383
+ return callback(new Error("Error saving tiddler " + fields.title + ": the text field is truthy but not a string"));
384
+ body += `\n\n${text}`;
385
+ }
386
+ const [ok, err, result] = yield this.recipeRequest({
387
+ key: "handleSaveRecipeTiddler",
388
+ url: "/tiddlers/" + encodeURIComponent(title),
389
+ method: "PUT",
390
+ requestBodyString: body,
391
+ headers: {
392
+ "content-type": "application/x-mws-tiddler"
393
+ }
394
+ });
395
+ delete self.outstandingRequests[title];
396
+ if (!ok)
397
+ return callback(err);
398
+ const data = result.responseJSON;
399
+ if (!data)
400
+ return callback(null); // a 2xx response without a body is unlikely
401
+ //If Browser-Storage plugin is present, remove tiddler from local storage after successful sync to the server
402
+ if ($tw.browserStorage && $tw.browserStorage.isEnabled()) {
403
+ $tw.browserStorage.removeTiddlerFromLocalStorage(title);
404
+ }
405
+ // Save the details of the new revision of the tiddler
406
+ const revision = data.revision_id, bag_name = data.bag_name;
407
+ console.log(`Saved ${title} with revision ${revision} and bag ${bag_name}`);
408
+ // If there has been a more recent update from the server then enqueue a load of this tiddler
409
+ self.checkLastRecordedUpdate(title, revision);
410
+ // Invoke the callback
411
+ self.setTiddlerInfo({ title, revision_id: revision, bag_name: bag_name });
412
+ callback(null, { bag: bag_name, title: title, revision: revision }, revision);
413
+ });
414
+ }
415
+ /*
416
+ Load a tiddler and invoke the callback with (err,tiddlerFields)
417
+
418
+ The syncer does not pass itself into options.
419
+ */
420
+ loadTiddler(title, callback, options) {
421
+ return __awaiter(this, void 0, void 0, function* () {
422
+ var _a, _b;
423
+ const self = this;
424
+ // if (!self.syncer) return callback(new Error("No syncer available"));
425
+ self.outstandingRequests[title] = { type: "GET" };
426
+ const [ok, err, result] = yield this.recipeRequest({
427
+ key: "handleLoadRecipeTiddler",
428
+ url: "/tiddlers/" + encodeURIComponent(title),
429
+ method: "GET",
430
+ });
431
+ delete self.outstandingRequests[title];
432
+ if (!ok)
433
+ return callback(err);
434
+ const { responseJSON: data, headers } = result;
435
+ const revision = (_a = headers.get("x-revision-number")) !== null && _a !== void 0 ? _a : "", bag_name = (_b = headers.get("x-bag-name")) !== null && _b !== void 0 ? _b : "";
436
+ if (!revision || !bag_name || !data)
437
+ return callback(null, null);
438
+ // If there has been a more recent update from the server then enqueue a load of this tiddler
439
+ self.checkLastRecordedUpdate(title, revision);
440
+ // Invoke the callback
441
+ self.setTiddlerInfo({ title, revision_id: revision, bag_name: bag_name });
442
+ callback(null, data);
443
+ });
444
+ }
445
+ /*
446
+ Delete a tiddler and invoke the callback with (err)
447
+ options include:
448
+ tiddlerInfo: the syncer's tiddlerInfo for this tiddler
449
+ */
450
+ deleteTiddler(title, callback, options) {
451
+ return __awaiter(this, void 0, void 0, function* () {
452
+ var self = this;
453
+ if (this.isReadOnly) {
454
+ return callback(null);
455
+ }
456
+ // If we don't have a bag it means that the tiddler hasn't been seen by the server, so we don't need to delete it
457
+ // var bag = this.getTiddlerBag(title);
458
+ // if(!bag) { return callback(null, options.tiddlerInfo.adaptorInfo); }
459
+ self.outstandingRequests[title] = { type: "DELETE" };
460
+ const [ok, err, result] = yield this.recipeRequest({
461
+ key: "handleDeleteRecipeTiddler",
462
+ url: "/tiddlers/" + encodeURIComponent(title),
463
+ method: "DELETE",
464
+ });
465
+ delete self.outstandingRequests[title];
466
+ if (!ok)
467
+ return callback(err);
468
+ const { responseJSON: data } = result;
469
+ if (!data)
470
+ return callback(null);
471
+ const { revision_id, bag_name, bag_id } = data;
472
+ // If there has been a more recent update from the server then enqueue a load of this tiddler
473
+ self.checkLastRecordedUpdate(title, revision_id);
474
+ self.removeTiddlerInfo({ title, revision_id, bag_id });
475
+ // Invoke the callback & return null adaptorInfo
476
+ callback(null, null);
477
+ });
478
+ }
479
+ rpcRequest(options) {
480
+ return __awaiter(this, void 0, void 0, function* () {
481
+ const [ok, err, result] = yield this.recipeRequest({
482
+ key: options.key,
483
+ url: "/rpc/" + options.key,
484
+ method: "PUT",
485
+ requestBodyString: JSON.stringify(options.body),
486
+ headers: {
487
+ "content-type": "application/json"
488
+ }
489
+ });
490
+ if (!ok)
491
+ throw err;
492
+ if (!result.responseJSON)
493
+ throw new Error("No response JSON returned");
494
+ return result.responseJSON;
495
+ });
496
+ }
497
+ /**
498
+ * This throws an error if the response status is not 2xx, but will still return the response alongside the error.
499
+ *
500
+ * So if the first parameter is false, the third parameter may still contain the response.
501
+ */
502
+ recipeRequest(options) {
503
+ return __awaiter(this, void 0, void 0, function* () {
504
+ if (!options.url.startsWith("/"))
505
+ throw new Error("The url does not start with a slash");
506
+ return yield httpRequest(Object.assign(Object.assign({}, options), { responseType: "blob", url: this.host + "recipe/" + encodeURIComponent(this.recipe) + options.url })).then((e) => __awaiter(this, void 0, void 0, function* () {
507
+ var _a;
508
+ if (!e.ok)
509
+ return [
510
+ false, new Error(`The server return a status code ${e.status} with the following reason: `
511
+ + `${(_a = e.headers.get("x-reason")) !== null && _a !== void 0 ? _a : "(no reason given)"}`),
512
+ Object.assign(Object.assign({}, e), { responseString: "", responseJSON: undefined })
513
+ ];
514
+ let responseString = "";
515
+ if (e.headers.get("x-gzip-stream") === "yes") {
516
+ // Browsers only decode the first stream,
517
+ // so we cant use content-encoding or DecompressionStream
518
+ yield new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
519
+ const gunzip = new fflate.AsyncGunzip((err, chunk, final) => {
520
+ if (err)
521
+ return console.log(err);
522
+ responseString += fflate.strFromU8(chunk);
523
+ if (final)
524
+ resolve();
525
+ });
526
+ if (this.isDevMode)
527
+ gunzip.onmember = e => console.log("gunzip member", e);
528
+ gunzip.push(new Uint8Array(yield readBlobAsArrayBuffer(e.response)));
529
+ // this has to be on a separate line
530
+ gunzip.push(new Uint8Array(), true);
531
+ }));
532
+ if (this.isDevMode)
533
+ console.log("gunzip result", responseString.length);
534
+ }
535
+ else {
536
+ responseString = fflate.strFromU8(new Uint8Array(yield readBlobAsArrayBuffer(e.response)));
537
+ }
538
+ return [true, void 0, Object.assign(Object.assign({}, e), { responseString,
539
+ /** this is undefined if status is not 200 */
540
+ responseJSON: e.status === 200
541
+ ? tryParseJSON(responseString)
542
+ : undefined })];
543
+ }), e => [false, e, void 0]);
544
+ function tryParseJSON(data) {
545
+ try {
546
+ return JSON.parse(data);
547
+ }
548
+ catch (e) {
549
+ console.error("Error parsing JSON, returning undefined", e);
550
+ // console.log(data);
551
+ return undefined;
552
+ }
553
+ }
554
+ });
555
+ }
556
+ }
557
+ if ($tw.browser && document.location.protocol.startsWith("http")) {
558
+ exports.adaptorClass = MultiWikiClientAdaptor;
559
+ }
560
+ function httpRequest(options) {
561
+ return new Promise((resolve, reject) => {
562
+ // if this throws sync'ly, the promise will reject.
563
+ options.method = options.method.toUpperCase();
564
+ if ((options.method === "GET" || options.method === "HEAD") && options.requestBodyString)
565
+ throw new Error("requestBodyString must be falsy if method is GET or HEAD");
566
+ const url = new URL(options.url, location.href);
567
+ const query = paramsInput(options.queryParams);
568
+ query.forEach((v, k) => { url.searchParams.append(k, v); });
569
+ const headers = new Headers(options.headers || {});
570
+ const request = new XMLHttpRequest();
571
+ request.responseType = options.responseType || "text";
572
+ try {
573
+ request.open(options.method, url, true);
574
+ }
575
+ catch (e) {
576
+ console.log(e, { e });
577
+ throw e;
578
+ }
579
+ if (!headers.has("content-type"))
580
+ headers.set("content-type", "application/x-www-form-urlencoded; charset=UTF-8");
581
+ if (!headers.has("x-requested-with"))
582
+ headers.set("x-requested-with", "TiddlyWiki");
583
+ headers.set("accept", "application/json");
584
+ headers.forEach((v, k) => {
585
+ request.setRequestHeader(k, v);
586
+ });
587
+ request.onreadystatechange = function () {
588
+ var _a;
589
+ if (this.readyState !== 4)
590
+ return;
591
+ const headers = new Headers();
592
+ (_a = request.getAllResponseHeaders()) === null || _a === void 0 ? void 0 : _a.trim().split(/[\r\n]+/).forEach((line) => {
593
+ var _a;
594
+ const parts = line.split(": ");
595
+ const header = (_a = parts.shift()) === null || _a === void 0 ? void 0 : _a.toLowerCase();
596
+ const value = parts.join(": ");
597
+ if (header)
598
+ headers.append(header, value);
599
+ });
600
+ resolve({
601
+ ok: this.status >= 200 && this.status < 300,
602
+ status: this.status,
603
+ statusText: this.statusText,
604
+ response: this.response,
605
+ headers,
606
+ });
607
+ };
608
+ if (options.progress)
609
+ request.onprogress = options.progress;
610
+ try {
611
+ request.send(options.requestBodyString);
612
+ }
613
+ catch (e) {
614
+ console.log(e, { e });
615
+ throw e;
616
+ }
617
+ });
618
+ function paramsInput(input) {
619
+ if (!input)
620
+ return new URLSearchParams();
621
+ if (input instanceof URLSearchParams)
622
+ return input;
623
+ if (Array.isArray(input) || typeof input === "string")
624
+ return new URLSearchParams(input);
625
+ return new URLSearchParams(Object.entries(input));
626
+ }
627
+ }
628
+ function readBlobAsArrayBuffer(blob) {
629
+ const error = new Error("Error reading blob");
630
+ return new Promise((resolve, reject) => {
631
+ const reader = new FileReader();
632
+ reader.onload = () => {
633
+ resolve(reader.result);
634
+ };
635
+ reader.onerror = () => {
636
+ reject(error);
637
+ };
638
+ reader.readAsArrayBuffer(blob);
639
+ });
640
+ }