@tiddlywiki/mws 0.0.12 → 0.0.13

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 (65) hide show
  1. package/README.md +3 -1
  2. package/dist/server.d.ts +923 -0
  3. package/dist/server.js +1763 -1415
  4. package/dist/server.js.map +1 -1
  5. package/editions/mws-docs/tiddlers/$__StoryList.tid +3 -3
  6. package/editions/mws-docs/tiddlers/MWS and SQLite.md +11 -0
  7. package/editions/mws-docs/tiddlers/Request Server.md +20 -0
  8. package/editions/mws-docs/tiddlers/Request Server.md.meta +5 -0
  9. package/package.json +16 -10
  10. package/prisma/schema.prisma +1 -0
  11. package/react-user-mgmt/package-lock.json +76 -321
  12. package/react-user-mgmt/package.json +2 -5
  13. package/react-user-mgmt/public/chunk-YALWN5OT.js +2 -0
  14. package/react-user-mgmt/public/chunk-YALWN5OT.js.map +7 -0
  15. package/react-user-mgmt/public/esm-QKDFEP3B.js +3 -0
  16. package/react-user-mgmt/public/esm-QKDFEP3B.js.map +7 -0
  17. package/react-user-mgmt/public/index.html +1 -26
  18. package/react-user-mgmt/public/main.js +68 -109
  19. package/react-user-mgmt/public/main.js.map +4 -4
  20. package/react-user-mgmt/public/stats.css +597 -0
  21. package/react-user-mgmt/public/stats.html +59 -0
  22. package/react-user-mgmt/public/stats.js +2013 -0
  23. package/react-user-mgmt/public/stats.json +1 -0
  24. package/react-user-mgmt/public/stats.svg +4 -0
  25. package/react-user-mgmt/tsconfig.json +3 -0
  26. package/dist/server.cjs +0 -16677
  27. package/dist/server.cjs.map +0 -1
  28. package/src/StateObject.ts +0 -316
  29. package/src/commander.ts +0 -417
  30. package/src/commands/divider.ts +0 -20
  31. package/src/commands/index.ts +0 -24
  32. package/src/commands/mws-init-store.ts +0 -77
  33. package/src/commands/mws-listen.ts +0 -136
  34. package/src/commands/mws-load-archive.ts +0 -281
  35. package/src/commands/mws-load-plugin-bags.ts +0 -151
  36. package/src/commands/mws-load-tiddlers.ts +0 -37
  37. package/src/commands/mws-load-wiki-folder.ts +0 -124
  38. package/src/commands/mws-render-tiddlywiki5.ts +0 -55
  39. package/src/commands/mws-save-archive.ts +0 -111
  40. package/src/commands/mws-save-tiddler-text.ts +0 -33
  41. package/src/commands/tests-complete.ts +0 -21
  42. package/src/global.ts +0 -247
  43. package/src/routes/TiddlerStore.ts +0 -722
  44. package/src/routes/bag-file-server.ts +0 -340
  45. package/src/routes/cache.ts +0 -25
  46. package/src/routes/index.ts +0 -138
  47. package/src/routes/managers/index.ts +0 -17
  48. package/src/routes/managers/manager-recipes.ts +0 -438
  49. package/src/routes/managers/manager-users.ts +0 -201
  50. package/src/routes/managers/router-tiddlers.ts +0 -313
  51. package/src/routes/router.ts +0 -429
  52. package/src/routes/services/PasswordService.ts +0 -178
  53. package/src/routes/services/attachments.ts +0 -259
  54. package/src/routes/services/sessions.ts +0 -166
  55. package/src/routes/tw-test.ts +0 -65
  56. package/src/server.ts +0 -151
  57. package/src/setupDevServer.ts +0 -71
  58. package/src/streamer.ts +0 -430
  59. package/src/tiddlywiki.ts +0 -94
  60. package/src/utils/data-checks.ts +0 -172
  61. package/src/utils/index.ts +0 -4
  62. package/src/utils/rootRoute.ts +0 -237
  63. package/src/utils/utils.ts +0 -403
  64. package/src/utils/zodAssert.ts +0 -269
  65. /package/prisma/{migrations/notes.md → notes.md} +0 -0
@@ -1,259 +0,0 @@
1
- import { tryParseJSON } from "../../utils";
2
- import * as path from "path";
3
- import * as fs from "fs";
4
- import { SiteConfig } from "../router";
5
- import sjcl from "sjcl";
6
- export interface TiddlerFields extends Record<string, any> {
7
- title: PrismaField<"Tiddlers", "title">;
8
- }
9
- const $tw: any = {};
10
-
11
-
12
- /*
13
-
14
- Class to handle the attachments in the filing system
15
-
16
- The store folder looks like this:
17
-
18
- store/
19
- inbox/ - files that are in the process of being uploaded via a multipart form upload
20
- 202402282125432742/
21
- 0
22
- 1
23
- ...
24
- ...
25
- files/ - files that are the text content of large tiddlers
26
- b7def178-79c4-4d88-b7a4-39763014a58b/
27
- data.jpg - the extension is provided for convenience when directly inspecting the file system
28
- meta.json - contains:
29
- {
30
- "filename": "data.jpg",
31
- "type": "video/mp4",
32
- "uploaded": "2024021821224823"
33
- }
34
- database.sql - The database file (managed by sql-tiddler-database.js)
35
-
36
- */
37
-
38
- export class AttachmentService {
39
-
40
- constructor(
41
- protected config: SiteConfig,
42
- protected prisma: PrismaTxnClient,
43
- ) {
44
-
45
- }
46
-
47
- makeCanonicalUri(bag_name: string, title: string) {
48
- return `/bags/${encodeURIComponentExtended(bag_name)}/tiddlers/${encodeURIComponentExtended(title)}/blob`;
49
- }
50
- /*
51
- Given tiddler fields, tiddler_id and a bag_name, return the tiddler fields after the following process:
52
- - Apply the tiddler_id as the revision field
53
- - Apply the bag_name as the bag field
54
- */
55
- processOutgoingTiddler({ tiddler, tiddler_id, bag_name, attachment_hash }: {
56
- tiddler: TiddlerFields;
57
- tiddler_id: any;
58
- bag_name: PrismaField<"Bags", "bag_name">;
59
- attachment_hash: PrismaField<"Tiddlers", "attachment_hash">;
60
- }) {
61
- if (attachment_hash !== null) {
62
- return Object.assign(
63
- {},
64
- tiddler,
65
- {
66
- text: undefined,
67
- _canonical_uri: this.makeCanonicalUri(bag_name, tiddler.title),
68
- revision: tiddler_id,
69
- bag: bag_name
70
- }
71
- );
72
- } else {
73
- return tiddler;
74
- }
75
- }
76
- /*
77
- */
78
- async processIncomingTiddler({
79
- tiddlerFields, existing_attachment_hash, existing_canonical_uri
80
- }: {
81
- tiddlerFields: TiddlerFields;
82
- existing_attachment_hash: PrismaField<"Tiddlers", "attachment_hash">;
83
- existing_canonical_uri: any;
84
- }) {
85
- const { attachmentSizeLimit, attachmentsEnabled, } = this.config;
86
- const contentTypeInfo = this.config.contentTypeInfo[tiddlerFields.type || "text/vnd.tiddlywiki"];
87
- const isBinary = !!contentTypeInfo && contentTypeInfo.encoding === "base64";
88
-
89
- let shouldProcessAttachment = tiddlerFields.text && tiddlerFields.text.length > attachmentSizeLimit;
90
-
91
- if (existing_attachment_hash) {
92
- const fileSize = await this.getAttachmentFileSize(existing_attachment_hash);
93
- if (fileSize && fileSize <= attachmentSizeLimit) {
94
- const existingAttachmentMeta = await this.getAttachmentMetadata(existing_attachment_hash);
95
- const hasCanonicalField = !!tiddlerFields._canonical_uri;
96
- const skipAttachment = hasCanonicalField && (tiddlerFields._canonical_uri === (existingAttachmentMeta ? existingAttachmentMeta._canonical_uri : existing_canonical_uri));
97
- shouldProcessAttachment = !skipAttachment;
98
- } else {
99
- shouldProcessAttachment = false;
100
- }
101
- }
102
-
103
- if (attachmentsEnabled && isBinary && shouldProcessAttachment) {
104
- const attachment_hash = existing_attachment_hash || await this.saveAttachment({
105
- text: tiddlerFields.text,
106
- type: tiddlerFields.type,
107
- reference: tiddlerFields.title,
108
- _canonical_uri: tiddlerFields._canonical_uri
109
- });
110
-
111
- if (tiddlerFields && tiddlerFields._canonical_uri) {
112
- delete tiddlerFields._canonical_uri;
113
- }
114
-
115
- return {
116
- tiddlerFields: Object.assign({}, tiddlerFields, { text: undefined }),
117
- attachment_hash: attachment_hash
118
- };
119
- } else {
120
- return {
121
- tiddlerFields: tiddlerFields,
122
- attachment_hash: existing_attachment_hash
123
- };
124
- }
125
- }
126
-
127
- /*
128
- Check if an attachment name is valid (is a 64 character hex string)
129
- */
130
- isValidAttachmentName(content_hash: string) {
131
- const re = new RegExp('^[a-f0-9]{64}$');
132
- return re.test(content_hash);
133
- }
134
- /*
135
- Saves an attachment to a file. Options include:
136
-
137
- text: text content (may be binary)
138
- type: MIME type of content
139
- reference: reference to use for debugging
140
- _canonical_uri: canonical uri of the content
141
- */
142
- async saveAttachment(options: {
143
- text: string;
144
- type: string;
145
- reference: string;
146
- _canonical_uri: string;
147
- }) {
148
-
149
- // Compute the content hash for naming the attachment
150
- const contentHash = sjcl.codec.hex.fromBits($tw.sjcl.hash.sha256.hash(options.text)).slice(0, 64).toString();
151
- // Choose the best file extension for the attachment given its type
152
- const contentTypeInfo = this.config.contentTypeInfo[options.type] || this.config.contentTypeInfo["application/octet-stream"];
153
- // Creat the attachment directory
154
- const attachmentPath = path.resolve(this.config.storePath, "files", contentHash);
155
- createDirectory(attachmentPath);
156
- // Save the data file
157
- const dataFilename = "data" + contentTypeInfo.extension;
158
- fs.writeFileSync(path.resolve(attachmentPath, dataFilename), options.text, contentTypeInfo.encoding);
159
- // Save the meta.json file
160
- this.writeMetaFile(fs, path, attachmentPath, options, contentHash, dataFilename);
161
- return contentHash as PrismaField<"Tiddlers", "attachment_hash">;
162
- }
163
- private writeMetaFile(fs: any, path: any, attachmentPath: any, options: { text: string; type: string; reference: string; _canonical_uri: string; }, contentHash: any, dataFilename: string) {
164
- fs.writeFileSync(path.resolve(attachmentPath, "meta.json"), JSON.stringify({
165
- _canonical_uri: options._canonical_uri,
166
- created: new Date().toISOString(),
167
- modified: new Date().toISOString(),
168
- contentHash: contentHash,
169
- filename: dataFilename,
170
- type: options.type
171
- }, null, 4));
172
- }
173
-
174
- /*
175
- Adopts an attachment file into the store (e.g. move from a temp upload directory)
176
- */
177
- async adoptAttachment({ incomingFilepath, type, hash, _canonical_uri }: {
178
- incomingFilepath: any;
179
- type: string | number;
180
- hash: any;
181
- _canonical_uri: any;
182
- }) {
183
- // Choose the best file extension for the attachment given its type
184
- const contentTypeInfo = this.config.contentTypeInfo[type] || this.config.contentTypeInfo["application/octet-stream"];
185
- // Creat the attachment directory
186
- const attachmentPath = path.resolve(this.config.storePath, "files", hash);
187
- createDirectory(attachmentPath);
188
- // Rename the data file
189
- const dataFilename = "data" + contentTypeInfo.extension, dataFilepath = path.resolve(attachmentPath, dataFilename);
190
- fs.renameSync(incomingFilepath, dataFilepath);
191
- // Save the meta.json file
192
- fs.writeFileSync(path.resolve(attachmentPath, "meta.json"), JSON.stringify({
193
- _canonical_uri: _canonical_uri,
194
- created: new Date().toISOString(),
195
- modified: new Date().toISOString(),
196
- contentHash: hash,
197
- filename: dataFilename,
198
- type: type
199
- }, null, 4));
200
- return hash;
201
- }
202
- /*
203
- Get an attachment ready to stream. Returns null if there is an error or:
204
- stream: filestream of file
205
- type: type of file
206
- */
207
- async getAttachmentStream(content_hash: string) {
208
- // Check the attachment name
209
- if (!this.isValidAttachmentName(content_hash)) return null;
210
-
211
- // Construct the path to the attachment directory
212
- const attachmentPath = path.resolve(this.config.storePath, "files", content_hash);
213
-
214
- // Read the meta.json file
215
- const metaJsonPath = path.resolve(attachmentPath, "meta.json");
216
- if (!fs.existsSync(metaJsonPath) || !fs.statSync(metaJsonPath).isFile()) return null;
217
-
218
- const meta = tryParseJSON<any>(fs.readFileSync(metaJsonPath, "utf8"));
219
- if (!meta) return null;
220
-
221
- const dataFilepath = path.resolve(attachmentPath, meta.filename);
222
- // Check if the data file exists
223
- if (!fs.existsSync(dataFilepath) || !fs.statSync(dataFilepath).isFile()) return null
224
-
225
- // Stream the file
226
- return {
227
- stream: fs.createReadStream(dataFilepath),
228
- type: meta.type
229
- };
230
-
231
-
232
- }
233
- /*
234
- Get the size of an attachment file given the contentHash.
235
- Returns the size in bytes, or null if the file doesn't exist.
236
- */
237
- async getAttachmentFileSize(content_hash: PrismaField<"Tiddlers", "attachment_hash"> & {}) {
238
- // Construct the path to the attachment directory
239
- const attachmentPath = path.resolve(this.config.storePath, "files", content_hash);
240
- // Read the meta.json file
241
- const metaJsonPath = path.resolve(attachmentPath, "meta.json");
242
- if (!(fs.existsSync(metaJsonPath) && fs.statSync(metaJsonPath).isFile())) return null
243
- const meta = tryParseJSON<any>(fs.readFileSync(metaJsonPath, "utf8"));
244
- if (!meta) return null;
245
- const dataFilepath = path.resolve(attachmentPath, meta.filename);
246
- // Check if the data file exists and return its size
247
- if (!(fs.existsSync(dataFilepath) && fs.statSync(dataFilepath).isFile())) return null;
248
- return fs.statSync(dataFilepath).size;
249
- }
250
-
251
- async getAttachmentMetadata(content_hash: PrismaField<"Tiddlers", "attachment_hash"> & {}) {
252
- const attachmentPath = path.resolve(this.config.storePath, "files", content_hash);
253
- const metaJsonPath = path.resolve(attachmentPath, "meta.json");
254
- if (!fs.existsSync(metaJsonPath)) return null;
255
- return JSON.parse(fs.readFileSync(metaJsonPath, "utf8"));
256
- }
257
-
258
- }
259
-
@@ -1,166 +0,0 @@
1
-
2
- import { Streamer } from "../../streamer";
3
- import { randomBytes } from "node:crypto";
4
- import { registerZodRoutes, Router, RouterKeyMap, RouterRouteMap, zodRoute, ZodState } from "../router";
5
- import { StateObject } from "../../StateObject";
6
- import { z } from "zod";
7
- import { JsonValue, Z2 } from "../../utils";
8
-
9
- export interface AuthUser {
10
- /** User ID. 0 if the user is not logged in. */
11
- user_id: PrismaField<"Users", "user_id">;
12
- /** User role_ids. This may have length even if the user isn't logged in, to allow ACL for anon. */
13
- role_ids: PrismaField<"Roles", "role_id">[];
14
- /** Username passed to the client */
15
- username: PrismaField<"Users", "username">;
16
- /** A session_id isn't guarenteed. There may be a session even if the user isn't logged in, and may not be even if they are, depending on the the situation. */
17
- sessionId: PrismaField<"Sessions", "session_id"> | undefined;
18
- /** Is this user considered a site-admin. This is determined by the auth service, not MWS. */
19
- isAdmin: boolean;
20
- /** Is the user logged in? This also means that user_id should be 0. role_ids may still be specified. */
21
- isLoggedIn: boolean;
22
- }
23
-
24
- export const SessionKeyMap: RouterKeyMap<SessionManager, true> = {
25
- login1: true,
26
- login2: true,
27
- logout: true,
28
- }
29
-
30
-
31
- /**
32
- *
33
- * @param path path starting with a forward slash
34
- * @param zodRequest the zod for state.data
35
- * @param inner the handler to call
36
- * @returns the ZodRoute
37
- */
38
- export function zodSession<T extends z.ZodTypeAny, R extends JsonValue>(
39
- path: string,
40
- zodRequest: (z: Z2<"JSON">) => T,
41
- inner: (state: ZodState<"POST", "json", Record<string, z.ZodTypeAny>, T>, prisma: PrismaTxnClient) => Promise<R>
42
- ) {
43
- return zodRoute(["POST"], path, z => ({}), "json", zodRequest, async state => {
44
- return state.$transaction(async (prisma) => await inner(state, prisma));
45
- });
46
- }
47
-
48
-
49
- export type SessionManagerMap = RouterRouteMap<SessionManager>;
50
-
51
- export class SessionManager {
52
-
53
- static async defineRoutes(root: rootRoute) {
54
- registerZodRoutes(root, new SessionManager(), Object.keys(SessionKeyMap))
55
- }
56
-
57
- static async parseIncomingRequest(streamer: Streamer, router: Router): Promise<AuthUser> {
58
-
59
- const sessionId = streamer.cookies.session as PrismaField<"Sessions", "session_id"> | undefined;
60
- const session = sessionId && await router.engine.sessions.findUnique({
61
- where: { session_id: sessionId },
62
- select: { user: { select: { user_id: true, username: true, roles: { select: { role_id: true } } } } }
63
- });
64
-
65
- if (sessionId && session) return {
66
- user_id: session.user.user_id,
67
- username: session.user.username,
68
- isAdmin: session.user.roles.some(e => e.role_id === 1),
69
- role_ids: session.user.roles.map(e => e.role_id),
70
- sessionId,
71
- isLoggedIn: true,
72
- };
73
- else return {
74
- user_id: 0 as PrismaField<"Users", "user_id">,
75
- username: "(anon)" as PrismaField<"Users", "username">,
76
- isAdmin: false,
77
- role_ids: [] as PrismaField<"Roles", "role_id">[],
78
- sessionId: undefined as PrismaField<"Sessions", "session_id"> | undefined,
79
- isLoggedIn: false,
80
- };
81
- }
82
-
83
- login1 = zodSession("/login/1", z => z.object({
84
- username: z.prismaField("Users", "username", "string"),
85
- startLoginRequest: z.string(),
86
- }), async (state, prisma) => {
87
- const { username, startLoginRequest } = state.data;
88
-
89
- const user = await prisma.users.findUnique({
90
- where: { username },
91
- select: { user_id: true, password: true, }
92
- });
93
-
94
- if (!user) throw "User not found.";
95
-
96
- const { user_id, password } = user;
97
-
98
- const stater = state.PasswordService.LoginGenerator({
99
- user_id,
100
- startLoginRequest,
101
- registrationRecord: password,
102
- });
103
-
104
- const loginResponse = await stater.next(0);
105
-
106
- if (loginResponse.done) throw "Login failed.";
107
-
108
- const loginSession = await state.PasswordService.startLoginSession(stater);
109
-
110
- state.setCookie("loginsession", loginSession, { httpOnly: true, path: "/", secure: state.isSecure, sameSite: "Strict" });
111
-
112
- return { loginResponse: loginResponse.value };
113
-
114
- })
115
-
116
- login2 = zodSession("/login/2", z => z.object({
117
- finishLoginRequest: z.string()
118
- }), async (state, prisma) => {
119
- const { finishLoginRequest } = state.data;
120
-
121
- if (!state.cookies.loginsession) throw "Login session not found.";
122
-
123
- const stater = state.PasswordService.serverState.get(state.cookies.loginsession);
124
-
125
- if (!stater) throw "Login session not found.";
126
-
127
- const { done, value } = await stater.next(1, finishLoginRequest);
128
-
129
- if (!value?.session?.sessionKey) throw "Login failed.";
130
-
131
- const session_id = await this.createSession(prisma, value.user_id, value.session.sessionKey);
132
-
133
- state.setCookie("session", session_id, { httpOnly: true, path: "/", secure: state.isSecure, sameSite: "Strict" });
134
-
135
- return null;
136
-
137
- })
138
-
139
- logout = zodSession("/logout", z => z.undefined(), async (state, prisma) => {
140
-
141
- if (state.user.isLoggedIn) {
142
- await prisma.sessions.delete({ where: { session_id: state.user.sessionId } });
143
- }
144
- var cookies = state.headers.cookie ? state.headers.cookie.split(";") : [];
145
- for (var i = 0; i < cookies.length; i++) {
146
- var cookie = cookies[i]?.trim().split("=")[0];
147
- if (!cookie) continue;
148
- // state.setHeader("Set-Cookie", cookie + "=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict");
149
- state.setCookie(cookie, "", { httpOnly: true, path: "/", expires: new Date(0), secure: state.isSecure, sameSite: "Strict" });
150
- }
151
-
152
- return null;
153
- });
154
-
155
- async createSession(prisma: PrismaTxnClient, user_id: PrismaField<"Users", "user_id">, session_key: string) {
156
- const session_id = randomBytes(16).toString("base64url");
157
- return await prisma.sessions.create({
158
- data: {
159
- user_id,
160
- session_key,
161
- session_id,
162
- last_accessed: new Date(),
163
- }
164
- }).then(({ session_id }) => session_id);
165
- }
166
- }
@@ -1,65 +0,0 @@
1
- import { Server } from "http";
2
- import { rootRoute } from "./router";
3
- import { ZodAssert as zodAssert } from "../utils";
4
- import { Tiddler, TiddlyWiki, TW } from "tiddlywiki";
5
-
6
- export function TWRoutes(parent: rootRoute) {
7
- const wikiRouter = new TWRouter();
8
- parent.defineRoute({
9
- method: ["OPTIONS", "GET", "HEAD", "POST", "PUT"],
10
- bodyFormat: "stream",
11
- path: /^\/mws(\/|$)/,
12
- pathParams: ["final_slash"]
13
- }, async (state) => {
14
- zodAssert.pathParams(state, z => ({ final_slash: z.string().optional() }));
15
- if (!state.pathParams.final_slash) throw state.redirect(state.urlInfo.pathname + "/");
16
- // literally don't do this
17
- const { req, res } = (state as any).streamer;
18
- // options not required
19
- wikiRouter.twserver.requestHandler(req, res, {} as any);
20
- });
21
- }
22
-
23
- class TWRouter {
24
- $tw: TW;
25
- twserver!: any;
26
-
27
- constructor() {
28
- this.$tw = TiddlyWiki();
29
- // tiddlywiki [+<pluginname> | ++<pluginpath>] [<wikipath>] ...[--command ...args]
30
- this.$tw.boot.argv = [
31
- // "++plugins/client",
32
- // "++plugins/server",
33
- // "+tiddlywiki/filesystem",
34
- // "+tiddlywiki/tiddlyweb",
35
- // "./editions/mws-server",
36
- // "--mws-load-plugin-bags",
37
- // "--build", "load-mws-demo-data",
38
- // "--mws-listen", "port=5001", "host=::"
39
- ];
40
- this.$tw.boot.boot(() => {
41
-
42
-
43
- this.$tw.preloadTiddler({
44
- text: "$protocol$//$host$" + "/mws/",
45
- title: "$:/config/tiddlyweb/host",
46
- });
47
- // Boot the TW5 app
48
- this.$tw.boot.boot(() => {
49
- const Server = this.$tw.modules.execute("$:/core/modules/server/server.js", "tw-test.ts").Server;
50
- this.twserver = new Server({
51
- wiki: this.$tw.wiki,
52
- variables: {
53
- // do not use a trailing slash
54
- "path-prefix": "/mws",
55
- "root-tiddler": "$:/core/save/all",
56
- },
57
- routes: [],
58
- });
59
- this.$tw.hooks.invokeHook("th-server-command-post-start", this.twserver, null, "mws");
60
- });
61
-
62
- });
63
- }
64
- }
65
-
package/src/server.ts DELETED
@@ -1,151 +0,0 @@
1
- import "./routes/router";
2
- import "./StateObject";
3
- import "./streamer";
4
- import "./global";
5
- import * as opaque from "@serenity-kit/opaque";
6
- import { existsSync, mkdirSync, PathOrFileDescriptor, readFileSync, writeFileSync } from 'node:fs';
7
- import * as sessions from "./routes/services/sessions";
8
- import * as attacher from "./routes/services/attachments";
9
- import { bootTiddlyWiki } from "./tiddlywiki";
10
- import { Commander } from "./commander";
11
- import { ListenerBase } from "./commands/mws-listen";
12
- import { createPasswordService } from "./routes/services/PasswordService";
13
- import { resolve } from "node:path";
14
-
15
- export * from "./routes/services/sessions";
16
-
17
- export interface MWSConfig {
18
- /**
19
- * Listener config for the mws-listen command.
20
- * This is extracted from the config early and passed directly to mws-listen.
21
- */
22
- listeners: {
23
- /** The key file for the HTTPS server. If either key or cert are set, both are required, and HTTPS is enforced. */
24
- key?: string
25
- /** The cert file for the HTTPS server. If either key or cert are set, both are required, and HTTPS is enforced. */
26
- cert?: string
27
- /** The port to listen on. If not specified, listen will not be called. */
28
- port?: number
29
- /** The hostname to listen on. If this is specified, then port is required. */
30
- host?: string
31
- }[]
32
- /**
33
- * Called and awaited by the mws-listen command.
34
- * This is extracted from the config early and passed directly to mws-listen.
35
- */
36
- onListenersCreated?: (listeners: ListenerBase[]) => Promise<void>;
37
-
38
- /**
39
- * Enables the dev configuration of the server.
40
- * The string is the absolute path to the dev repo.
41
- */
42
- enableDevServer?: string
43
- /**
44
- * Path or file descriptor to the password master key.
45
- * If this key changes, all passwords will be invalid and need to be changed.
46
- */
47
- passwordMasterKeyFile: string | number
48
- onPasswordKeyFileRead?: () => void;
49
- /** Path to the datafolder. */
50
- wikiPath: string
51
- /** MWS site configuration */
52
- config?: MWSConfigConfig
53
- /**
54
- * The session manager class registers the login handler routes and sets the auth user
55
- */
56
- SessionManager?: typeof sessions.SessionManager;
57
- /**
58
- * The attachment service class is used by routes to handle attachments
59
- */
60
- AttachmentService?: typeof attacher.AttachmentService;
61
-
62
- }
63
-
64
-
65
- export interface MWSConfigConfig {
66
- /** If true, allow users that aren't logged in to read. */
67
- readonly allowAnonReads?: boolean
68
- /** If true, allow users that aren't logged in to write. */
69
- readonly allowAnonWrites?: boolean
70
- /** If true, recipes will allow access to a user who does not have read access to all its bags. */
71
- readonly allowUnreadableBags?: boolean
72
- /** If true, files larger than `this * 1024` will be saved to disk alongside the database instead of in the database. */
73
- readonly saveLargeTextToFileSystem?: number;
74
- readonly enableGzip?: boolean
75
- readonly enableBrowserCache?: boolean
76
- }
77
-
78
-
79
-
80
- /**
81
- *
82
- * The promise returned will resolve once the commander has completed.
83
- * It will reject if the commander or anything else throws an error.
84
- *
85
- ```
86
- import startServer from "./src/server.ts";
87
-
88
- startServer({
89
- // enableDevServer: true,
90
- passwordMasterKey: "./localpass.key",
91
- listeners: [{
92
- key: "./localhost.key",
93
- cert: "./localhost.crt",
94
- port: 5000,
95
- }],
96
- config: {
97
- wikiPath: "./editions/mws",
98
- allowAnonReads: false,
99
- allowAnonWrites: false,
100
- allowUnreadableBags: false,
101
- },
102
- });
103
-
104
- ```
105
- */
106
- export default async function startServer(config: MWSConfig) {
107
-
108
- await opaque.ready;
109
-
110
- if (!["string", "number"].includes(typeof config.passwordMasterKeyFile)) {
111
- throw new Error("passwordMasterKeyFile must be a string or number");
112
- }
113
-
114
- if (typeof config.passwordMasterKeyFile === "string"
115
- && config.passwordMasterKeyFile
116
- && !existsSync(config.passwordMasterKeyFile)
117
- ) {
118
- writeFileSync(config.passwordMasterKeyFile, opaque.server.createSetup());
119
- console.log("Password master key created at", config.passwordMasterKeyFile);
120
- }
121
-
122
- const passwordMasterKey = readFileSync(config.passwordMasterKeyFile, "utf8").trim();
123
-
124
- config.onPasswordKeyFileRead?.();
125
-
126
- config.wikiPath = resolve(config.wikiPath);
127
-
128
- const commander = new Commander(
129
- config,
130
- await bootTiddlyWiki(config.wikiPath),
131
- await createPasswordService(passwordMasterKey)
132
- );
133
-
134
- await commander.init();
135
- const cli = process.argv.slice(2);
136
- // mws-command-separator prevents params after it from being applied to the command before it.
137
- // it throws an error if it ends up with any params.
138
- await commander.execute([
139
- ...cli.length ? cli : [
140
- "--mws-init-store",
141
- "--mws-listen"
142
- ],
143
- ]);
144
-
145
- if (commander.setupRequired) {
146
- console.log("MWS setup required. Please run either --mws-init-store or --mws-load-archive");
147
- process.exit(1);
148
- }
149
-
150
- }
151
-