socket-function 0.5.0 → 0.7.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.
@@ -0,0 +1,44 @@
1
+ module.allowclient = true;
2
+
3
+ /**
4
+ * Adds a global function setFlag(require, "typescript", flag) which sets a flag on the client
5
+ * - Ex, setFlag(require, "typescript", "allowclient") so allowclient = true on the typescript module.
6
+ * - Passing true as the fourth argument sets it recursively
7
+ */
8
+
9
+ // We need at least 1 export, to force this to be treated like a module
10
+ export const forceModule = true;
11
+
12
+ declare global {
13
+ function setFlag(require: NodeRequire, request: string, flag: string, recursive?: boolean): void;
14
+ }
15
+
16
+ function setRecursive(bangPart: string, module: NodeJS.Module) {
17
+ let m = module as any;
18
+ m.recursiveBangs = m.recursiveBangs || {};
19
+
20
+ if (m.recursiveBangs[bangPart]) return;
21
+
22
+ m.recursiveBangs[bangPart] = true;
23
+
24
+ Object.assign(module, { [bangPart]: true });
25
+ for (let child of module.children) {
26
+ setRecursive(bangPart, child);
27
+ }
28
+ }
29
+
30
+ const g = new Function("return this")();
31
+ g.setFlag = setFlag;
32
+ export function setFlag(require: NodeRequire, request: string, flag: string, recursive?: boolean) {
33
+ let resolvedPath = require.resolve(request);
34
+ let module = require.cache[resolvedPath] as any;
35
+ if (!module) {
36
+ console.warn(`setFlag cannot resolve module ${request}`);
37
+ return;
38
+ }
39
+ if (recursive) {
40
+ setRecursive(flag, module);
41
+ } else {
42
+ module[flag] = true;
43
+ }
44
+ }
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8">
5
+ </head>
6
+ <body>
7
+ <div id="main"></div>
8
+
9
+ <script src="./buffer.js"></script>
10
+ <script src="./require.js"></script>
11
+
12
+ <!-- ENTRY_TEMPLATE -->
13
+ </body>
14
+ </html>
@@ -0,0 +1,464 @@
1
+ (function () {
2
+ // Globals
3
+ Object.assign(window, {
4
+ process: {
5
+ argv: [],
6
+ env: {
7
+ // Mirror the tnode.js setting
8
+ NODE_ENV: "production"
9
+ },
10
+ },
11
+ setImmediate(callback) {
12
+ setTimeout(callback, 0);
13
+ },
14
+ // Ignore flags for now, even though they should work fine if we just hardcoded compileFlags.ts here.
15
+ setFlag() { },
16
+ global: window,
17
+ });
18
+
19
+
20
+ // Not real modules, as we just define their exports
21
+ const builtInModuleExports = {
22
+ worker_threads: {
23
+ isMainThread: true
24
+ },
25
+ util: {
26
+ // https://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor
27
+ inherits(constructor, superConstructor) {
28
+ Object.setPrototypeOf(constructor.prototype, superConstructor.prototype);
29
+ }
30
+ },
31
+ buffer: { Buffer },
32
+ stream: {
33
+ // HACK: Needed to get SAX JS to work correctly.
34
+ Stream: function () { },
35
+ },
36
+ timers: {
37
+ // TODO: Add all members of timers
38
+ setImmediate: window.setImmediate,
39
+ },
40
+ child_process: {},
41
+ events: {},
42
+ };
43
+
44
+
45
+ /** @type {{
46
+ [resolvePath: string]: {
47
+ // May be different then the module filename
48
+ filename: string;
49
+ // If a module is not allowed clientside it is likely requests will be empty,
50
+ // to save effort parsing requests for modules that only exist to give better
51
+ // error messages.
52
+ requests: {
53
+ // request => resolvedPath
54
+ [request: string]: string;
55
+ };
56
+ // NOTE: IF !allowclient && !serveronly, it might just mean we didn't add allowclient
57
+ // to the module yet. BUT, if serveronly, then we know for sure we don't want it client.
58
+ // So the messages and behavior will be different.
59
+ allowclient?: boolean;
60
+ serveronly?: boolean;
61
+
62
+ source?: string;
63
+ }
64
+ }} */
65
+ let serializedModules = Object.create(null);
66
+
67
+ let moduleCache = Object.create(null);
68
+ let alreadyHave = undefined;
69
+
70
+ let rootResolveCache = Object.create(null);
71
+
72
+ rootRequire.cache = moduleCache;
73
+ // Expose require for debugging, not so it can be called
74
+ window.require = rootRequire;
75
+ window.import = rootRequire;
76
+
77
+ window.r = function r(text) {
78
+ text = text.toLowerCase();
79
+ return Object
80
+ .values(moduleCache)
81
+ .filter(x => x.filename.toLowerCase().includes(text))
82
+ [0]
83
+ .exports;
84
+ };
85
+
86
+ let requireBatch;
87
+ function rootRequire(request, batch) {
88
+ if (!batch) {
89
+ if (request in rootResolveCache) {
90
+ let resolvedRequest = rootResolveCache[request];
91
+ if (resolvedRequest in rootRequire.cache) {
92
+ return rootRequire.cache[resolvedRequest].exports;
93
+ }
94
+ }
95
+
96
+
97
+ if (request in rootRequire.cache) {
98
+ return rootRequire.cache[request].exports;
99
+ }
100
+ }
101
+
102
+ if (request in builtInModuleExports) {
103
+ return builtInModuleExports[request];
104
+ }
105
+ if (batch) {
106
+ if (!requireBatch) {
107
+ requireBatch = requireBatch || {};
108
+ setTimeout(() => {
109
+ let requests = Object.keys(requireBatch);
110
+ let callbacks = Object.values(requireBatch).reduce((a, b) => a.concat(b), []);
111
+ requireBatch = undefined;
112
+ void rootRequireMultiple(requests, true).then(() => {
113
+ for (let callback of callbacks) {
114
+ callback();
115
+ }
116
+ }, err => { throw err; });
117
+ }, 0);
118
+ }
119
+ return new Promise(resolve => {
120
+ requireBatch[request] = requireBatch[request] || [];
121
+ requireBatch[request].push(resolve);
122
+ });
123
+ } else {
124
+ return rootRequireMultiple([request]).then(x => x[0].exports);
125
+ }
126
+ }
127
+ async function rootRequireMultiple(requests) {
128
+ console.log(`%cimport(${requests.join(", ")})`, "color: orange");
129
+
130
+ let time = Date.now();
131
+
132
+ let alreadyHaveRanges;
133
+ if (alreadyHave) {
134
+ let seqNums = Object.keys(alreadyHave.seqNums).map(x => +x);
135
+ seqNums.sort((a, b) => a - b);
136
+ let seqNumRanges = [];
137
+ alreadyHaveRanges = { requireSeqNumProcessId: alreadyHave.requireSeqNumProcessId, seqNumRanges };
138
+ for (let seqNum of seqNums) {
139
+ let prev = seqNumRanges[seqNumRanges.length - 1];
140
+ if (prev?.e === seqNum) {
141
+ prev.e++;
142
+ } else {
143
+ seqNumRanges.push({ s: seqNum, e: seqNum + 1 });
144
+ }
145
+ }
146
+ for (let range of seqNumRanges) {
147
+ if (range.s + 1 === range.e) {
148
+ delete range.e;
149
+ }
150
+ }
151
+ }
152
+
153
+ let args = [requests, alreadyHaveRanges];
154
+ // We have to add hardcoded support for droppermissions, because... this call
155
+ // doesn't have the conventional persisted code sending code, because... it's
156
+ // all on its own.
157
+ if (new URL(location).searchParams.get("droppermissions") !== null) {
158
+ args.push(true);
159
+ }
160
+ let requestUrl = location.origin + location.pathname + `?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=getModules`;
161
+ let rawText = await requestText(requestUrl, { args });
162
+ let resultObj;
163
+ try {
164
+ resultObj = JSON.parse(rawText);
165
+ } catch (e) {
166
+ console.log(rawText);
167
+ throw e;
168
+ }
169
+ let { modules, requestsResolvedPaths, requireSeqNumProcessId, error } = resultObj;
170
+
171
+ if (error) {
172
+ let errorObj = new Error();
173
+ errorObj.stack = error;
174
+ throw errorObj;
175
+ }
176
+
177
+ for (let i = 0; i < requests.length; i++) {
178
+ rootResolveCache[requests[i]] = requestsResolvedPaths[i];
179
+ }
180
+
181
+ // Store the function, so we only call it if it exists BEFORE we import
182
+ // (which means we already have something loading, so this is likely hot reloading...)
183
+ let observerOnHotReload = global.observerOnHotReload;
184
+ setTimeout(() => {
185
+ if (observerOnHotReload) {
186
+ observerOnHotReload();
187
+ }
188
+ }, 0);
189
+
190
+ time = Date.now() - time;
191
+ let moduleCount = Object.values(modules).filter(x => x.source).length;
192
+ let requireModuleCount = Object.values(modules).filter(x => !x.source).length;
193
+ let dependenciesOnlyText = requireModuleCount ? ` (+${requireModuleCount} dependencies only)` : "";
194
+ console.log(`%cimport(${requests.join(", ")}) download ${time}ms, ${Math.ceil(rawText.length / 1024)}KB, ${moduleCount} modules${dependenciesOnlyText}`, "color: green");
195
+
196
+ time = Date.now();
197
+
198
+ if (alreadyHave?.requireSeqNumProcessId !== requireSeqNumProcessId) {
199
+ alreadyHave = { requireSeqNumProcessId, seqNums: {} };
200
+ }
201
+
202
+ for (let id in modules) {
203
+ let module = modules[id];
204
+ alreadyHave.seqNums[module.seqNum] = 1;
205
+ serializedModules[id] = module;
206
+ }
207
+
208
+ try {
209
+ return requestsResolvedPaths.map(x => getModule(x));
210
+ } finally {
211
+ time = Date.now() - time;
212
+ console.log(`%cimport(${requests.join(", ")}) evaluate ${time}ms (${moduleCount} modules)`, "color: blue");
213
+ }
214
+ }
215
+
216
+ function createRequire(module, serializedModule, asyncIsFine) {
217
+ require.cache = moduleCache;
218
+ require.resolve = function (request) {
219
+ // TODO: Maybe do a request, making this async, if it isn't found?
220
+ return serializedModule.requests[request];
221
+ };
222
+ return require;
223
+ function require(request) {
224
+ if (request in builtInModuleExports) {
225
+ return builtInModuleExports[request];
226
+ }
227
+
228
+ if (!(request in serializedModule.requests)) {
229
+ if (!asyncIsFine) {
230
+ console.warn(`Accessed unexpected module %c${request}%c in %c${module.id}%c\n\tTreating it as an async require.\n\tAll modules require synchronously clientside must be required serverside at a module level.`,
231
+ "color: red", "color: unset",
232
+ "color: red", "color: unset",
233
+ );
234
+ }
235
+ debugger;
236
+ return rootRequire(request);
237
+ }
238
+
239
+ // Built in modules that we haven't been implemented
240
+ if (serializedModule.requests[request] === "") {
241
+ return {};
242
+ }
243
+
244
+ let resolvedPath = serializedModule.requests[request];
245
+
246
+ let exportsOverride = undefined;
247
+ if (resolvedPath === "NOTALLOWEDCLIENTSIDE" || !serializedModules[resolvedPath].allowclient) {
248
+ let childId = resolvedPath === "NOTALLOWEDCLIENTSIDE" ? request : resolvedPath;
249
+ if (serializedModules[resolvedPath]?.serveronly) {
250
+ exportsOverride = new Proxy({}, {
251
+ get(target, property) {
252
+ if (property === "__esModule") return undefined;
253
+ // NOTE: Return a toString that evaluates to "" so we can EXPLICITLY detect non-loaded modules
254
+ if (property === unloadedModule) return true;
255
+
256
+ throw new Error(`Module ${childId} is serverside only. Tried to access ${property} from ${module.id}`);
257
+ }
258
+ });
259
+ } else {
260
+ exportsOverride = new Proxy({}, {
261
+ get(target, property) {
262
+ if (property === "__esModule") return undefined;
263
+ // NOTE: Return a toString that evaluates to "" so we can EXPLICITLY detect non-loaded modules
264
+ if (property === unloadedModule) return true;
265
+
266
+ serializedModule;
267
+
268
+ console.warn(`Accessed non-whitelisted module %c${childId}%c, specifically property %c${String(property)}%c.\n\tAdd %cmodule.allowclient = true%c to the file to allow access.\n\t(IF it is a 3rd party library, use the global "setFlag" helper (in the file you imported the module) to set properties on other modules (it can even recursively set properties)).\n\n\tFrom ${module.id}`,
269
+ "color: red", "color: unset",
270
+ "color: red", "color: unset",
271
+ "color: red", "color: unset",
272
+ );
273
+ return undefined;
274
+ }
275
+ });
276
+ }
277
+ }
278
+
279
+ if (resolvedPath === "NOTALLOWEDCLIENTSIDE") {
280
+ return exportsOverride;
281
+ }
282
+
283
+ let childModule = getModule(resolvedPath);
284
+ module.children.push(childModule);
285
+ if (exportsOverride !== undefined) {
286
+ childModule.exports = exportsOverride;
287
+ }
288
+ return childModule.exports;
289
+ };
290
+ }
291
+
292
+ /** Generates the module root function, which can be called to evaluate the module,
293
+ * and has code equal to contents.
294
+ * - filename is just for debugging / stack traces
295
+ */
296
+ function wrapSafe(filename, contents) {
297
+ // TODO: Have the serverside inform us of the correct loader, or... have it actually emit a .json loader.
298
+ if (filename.endsWith(".json")) {
299
+ return (exports, require, module) => module.exports = contents && JSON.parse(contents);
300
+ }
301
+
302
+ // NOTE: debugName only matters during module evaluation. After that the sourcemap should work.
303
+ let debugName = (
304
+ filename
305
+ .replace(/\\/g, "/")
306
+ .split("/")
307
+ .slice(-1)[0]
308
+ .replace(/\./g, "_")
309
+ .replace(/[^a-zA-Z_]/g, "")
310
+ );
311
+ // NOTE: eval is used instead of new Function, as new Function inject lines, which messes
312
+ // up our sourcemaps.
313
+ // NOTE: All on one line, so we don't break sourcemaps by TOO much. We could also parse
314
+ // the sourcemap and adjust it, but... it is much easier to just not change the line counts.
315
+ return eval(`(function ${debugName}(exports, require, module, __filename, __dirname, importDynamic) {${contents}\n })`);
316
+ }
317
+
318
+
319
+ const unloadedModule = Symbol("unloadedModule");
320
+
321
+ let currentModuleEvaluationStack = [];
322
+ // See https://nodejs.org/api/modules.html
323
+ function getModule(resolvedId) {
324
+ if (resolvedId === "") {
325
+ return {};
326
+ }
327
+ if (resolvedId in moduleCache) {
328
+ return moduleCache[resolvedId];
329
+ }
330
+
331
+ let serializedModule = serializedModules[resolvedId];
332
+
333
+ let module = Object.create(null);
334
+ moduleCache[resolvedId] = module;
335
+ module.id = resolvedId;
336
+ module.filename = serializedModule?.filename;
337
+ module.exports = {};
338
+ module.children = [];
339
+
340
+ module.load = load;
341
+
342
+ module.loaded = true;
343
+ module.load();
344
+
345
+ function load(filename) {
346
+ let serializedModule = serializedModules[resolvedId];
347
+ if (!module.loaded) {
348
+ if (alreadyHave) {
349
+ delete alreadyHave.seqNums[serializedModule.seqNum];
350
+ }
351
+ // NOTE: There is almost never recovery from module downloading errors, so just don't catch them
352
+ void Promise.resolve().then(() => rootRequire(resolvedId, true)).then(() => {
353
+ module.loaded = true;
354
+ load();
355
+ });
356
+ return;
357
+ }
358
+
359
+ module.requires = serializedModule.requests;
360
+ module.require = createRequire(module, serializedModule);
361
+ // TODO: Once typescript supports dynamic import, map import() to importDynamic, so it
362
+ // uses our import function, instead of the built in one.
363
+ // (As apparently we can't just override import on a per module basis, because
364
+ // we can't have an identify called "import"... which is annoying).
365
+ let importDynamic = createRequire(module, serializedModule, true);
366
+ module.import = importDynamic;
367
+
368
+ let source = serializedModule.source;
369
+
370
+ module.allowclient = !!serializedModule.source;
371
+
372
+ // Import children, as the children may be allowed clientside, and may have side-effects!
373
+ if (!source) {
374
+ let requests = Object.keys(serializedModule.requests).filter(x => x !== "NOTALLOWEDCLIENTSIDE");
375
+ source = requests.map(id => `require(${JSON.stringify(id)});\n`).join("");
376
+ }
377
+
378
+ module.size = source.length;
379
+
380
+ let moduleFnc = wrapSafe(module.id, source);
381
+
382
+ let dirname = module.filename.replace(/\\/g, "/").split("/").slice(0, -1).join("/");
383
+
384
+ var __createBinding = (Object.create ? (function (o, m, k, k2) {
385
+ if (k2 === undefined) k2 = k;
386
+ Object.defineProperty(o, k2, { enumerable: true, get: function () { return m[k]; } });
387
+ }) : (function (o, m, k, k2) {
388
+ if (k2 === undefined) k2 = k;
389
+ o[k2] = m[k];
390
+ }));
391
+ var __setModuleDefault = (Object.create ? (function (o, v) {
392
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
393
+ }) : function (o, v) {
394
+ o["default"] = v;
395
+ });
396
+
397
+ let time = Date.now();
398
+ currentModuleEvaluationStack.push(module.filename);
399
+ try {
400
+ moduleFnc.call(
401
+ {
402
+ // NOTE: Adding __importStar to the module causes typescript to use our implementation,
403
+ // which checks for unloadedModule and returns undefined in that case.
404
+ __importStar(mod) {
405
+ if (mod[unloadedModule]) return undefined;
406
+ if (mod && mod.__esModule) return mod;
407
+ var result = {};
408
+ if (mod !== null && mod !== undefined) {
409
+ for (var k in mod) {
410
+ if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) {
411
+ __createBinding(result, mod, k);
412
+ }
413
+ }
414
+ }
415
+ __setModuleDefault(result, mod);
416
+ return result;
417
+ }
418
+ },
419
+ module.exports,
420
+ module.require,
421
+ module,
422
+ module.filename,
423
+ dirname,
424
+ importDynamic
425
+ );
426
+ time = Date.now() - time;
427
+ // NOTE: This log statment is disabled as I believe it causes lag (when devtools is open).
428
+ // As in, adding about 500ms to our load time, which is annoying when debugging.
429
+ //console.debug(`Evaluated module ${module.filename} ${Math.ceil(source.length / 1024)}KB`);
430
+ } finally {
431
+ currentModuleEvaluationStack.pop();
432
+ }
433
+
434
+ }
435
+
436
+ return module;
437
+ }
438
+
439
+ async function requestText(endpoint, values) {
440
+ let url = new URL(endpoint);
441
+
442
+ let json = JSON.stringify(values);
443
+ if (json.length < 6000 && false) {
444
+ // NOTE: Try to use a GET, as GETs can be cached! However, if the data is too large,
445
+ // we have to use a post, or else the request url will be too large
446
+ let parameters = [];
447
+ for (let key in values) {
448
+ url.searchParams.set(key, JSON.stringify(values[key]));
449
+ }
450
+ let response = await fetch(url.toString(), {
451
+ method: "GET",
452
+ credentials: "include",
453
+ });
454
+ return await response.text();
455
+ } else {
456
+ let response = await fetch(url.toString(), {
457
+ method: "POST",
458
+ body: json,
459
+ credentials: "include",
460
+ });
461
+ return await response.text();
462
+ }
463
+ }
464
+ })();
package/spec.txt CHANGED
@@ -1,22 +1,30 @@
1
1
  spec.txt
2
2
 
3
- - Implement SocketFunction for NodeJS => NodeJS
4
- - Publish it, and make sure we can include it correctly in non-ts projects
5
- (so we need to publish the dist folders that typenode creates).
6
- - We might need to emit source maps as well?
7
- - Maybe we should just compile with typescript?
8
- - Support HTTP responses in SocketFunction
9
- - Expose a http://127.0.0.1/RequireController-6016c77f-6863-47b5-a421-2abdea637436?html=./index.html&js=./index.ts endpoint
10
- - RequireController will have to look through all imports, and send the files clientside
11
- - Use allowclient to allow whitelisting of files, and setFlag to allow nested values. ALso compileDirFlags.ts
12
- - Add handling for .css files by calling compileTransform in typenode to add a handler for .css (after adding it to require.extensions).
13
- - Support default HTTP function in SocketFunction, via functions.httpDefault(() => {}), so we can expose http://127.0.0.1
14
-
15
- - Other libraries
3
+ - Add a flag for rejectUnauthorized, that is off by default
4
+ - Add the ability to specify the certs yourself (so you can specify your identity with a real cert)
5
+ - Then use real certificates on the server
6
+
7
+ - Other stuff
16
8
  - JSON buffer serialize, which generates an object, that allows for rehydration of buffers
17
9
  - Also... static classes (maybe even static resources), so structures can be sent
18
-
19
-
10
+ - Consider forcing everything to use real certs everywhere, and make generating and updating
11
+ certs very easy.
12
+ - ALTHOUGH, maybe this should just be a downstream user of socket-function thing, and not
13
+ a requirement of socket-function?
14
+ - This will be something where machines ask a server for an identity, and then the owner
15
+ allows it (giving them a subdomain), verifying their ip, etc.
16
+ - MAYBE we just automatically allow it? This works if we never use the root domain for anything,
17
+ and only use subdomains?
18
+ - Although, of course, sometimes we WILL want to group by domain, so it is more than just
19
+ identity, so having a way for the owner to authenticate it might be nice as well...
20
+ - Once they are given an identity, they are allowed to request their certs are updated,
21
+ and to get the new certs (it probably won't happen automatically, so that we can create
22
+ many nodes and let them die, without having to try to track which still need certs to be updated)
23
+ - ALSO, remember, for domains, use the domain as the nodeId, not the public key, that way it
24
+ is more consistent.
25
+
26
+
27
+ - https://letx.ca:2542/?classGuid=RequireController-e2f811f3-14b8-4759-b0d6-73f14516cf1d&functionName=requireHTML&args=[%22./test/test%22]
20
28
 
21
29
  ================== SocketFunction ==================
22
30