querysub 0.2.0

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 (169) hide show
  1. package/.dependency-cruiser.js +304 -0
  2. package/.eslintrc.js +51 -0
  3. package/.github/copilot-instructions.md +1 -0
  4. package/.vscode/settings.json +25 -0
  5. package/bin/deploy.js +4 -0
  6. package/bin/function.js +4 -0
  7. package/bin/server.js +4 -0
  8. package/costsBenefits.txt +112 -0
  9. package/deploy.ts +3 -0
  10. package/inject.ts +1 -0
  11. package/package.json +60 -0
  12. package/prompts.txt +54 -0
  13. package/spec.txt +820 -0
  14. package/src/-a-archives/archiveCache.ts +913 -0
  15. package/src/-a-archives/archives.ts +148 -0
  16. package/src/-a-archives/archivesBackBlaze.ts +792 -0
  17. package/src/-a-archives/archivesDisk.ts +418 -0
  18. package/src/-a-archives/copyLocalToBackblaze.ts +24 -0
  19. package/src/-a-auth/certs.ts +517 -0
  20. package/src/-a-auth/der.ts +122 -0
  21. package/src/-a-auth/ed25519.ts +1015 -0
  22. package/src/-a-auth/node-forge-ed25519.d.ts +17 -0
  23. package/src/-b-authorities/dnsAuthority.ts +203 -0
  24. package/src/-b-authorities/emailAuthority.ts +57 -0
  25. package/src/-c-identity/IdentityController.ts +200 -0
  26. package/src/-d-trust/NetworkTrust2.ts +150 -0
  27. package/src/-e-certs/EdgeCertController.ts +288 -0
  28. package/src/-e-certs/certAuthority.ts +192 -0
  29. package/src/-f-node-discovery/NodeDiscovery.ts +543 -0
  30. package/src/-g-core-values/NodeCapabilities.ts +134 -0
  31. package/src/-g-core-values/oneTimeForward.ts +91 -0
  32. package/src/-h-path-value-serialize/PathValueSerializer.ts +769 -0
  33. package/src/-h-path-value-serialize/stringSerializer.ts +176 -0
  34. package/src/0-path-value-core/LoggingClient.tsx +24 -0
  35. package/src/0-path-value-core/NodePathAuthorities.ts +978 -0
  36. package/src/0-path-value-core/PathController.ts +1 -0
  37. package/src/0-path-value-core/PathValueCommitter.ts +565 -0
  38. package/src/0-path-value-core/PathValueController.ts +231 -0
  39. package/src/0-path-value-core/archiveLocks/ArchiveLocks.ts +154 -0
  40. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +820 -0
  41. package/src/0-path-value-core/archiveLocks/archiveSnapshots.ts +180 -0
  42. package/src/0-path-value-core/debugLogs.ts +90 -0
  43. package/src/0-path-value-core/pathValueArchives.ts +483 -0
  44. package/src/0-path-value-core/pathValueCore.ts +2217 -0
  45. package/src/1-path-client/RemoteWatcher.ts +558 -0
  46. package/src/1-path-client/pathValueClientWatcher.ts +702 -0
  47. package/src/2-proxy/PathValueProxyWatcher.ts +1857 -0
  48. package/src/2-proxy/archiveMoveHarness.ts +376 -0
  49. package/src/2-proxy/garbageCollection.ts +753 -0
  50. package/src/2-proxy/pathDatabaseProxyBase.ts +37 -0
  51. package/src/2-proxy/pathValueProxy.ts +139 -0
  52. package/src/2-proxy/schema2.ts +518 -0
  53. package/src/3-path-functions/PathFunctionHelpers.ts +129 -0
  54. package/src/3-path-functions/PathFunctionRunner.ts +619 -0
  55. package/src/3-path-functions/PathFunctionRunnerMain.ts +67 -0
  56. package/src/3-path-functions/deployBlock.ts +10 -0
  57. package/src/3-path-functions/deployCheck.ts +7 -0
  58. package/src/3-path-functions/deployMain.ts +160 -0
  59. package/src/3-path-functions/pathFunctionLoader.ts +282 -0
  60. package/src/3-path-functions/syncSchema.ts +475 -0
  61. package/src/3-path-functions/tests/functionsTest.ts +135 -0
  62. package/src/3-path-functions/tests/rejectTest.ts +77 -0
  63. package/src/4-dom/css.tsx +29 -0
  64. package/src/4-dom/cssTypes.d.ts +212 -0
  65. package/src/4-dom/qreact.tsx +2322 -0
  66. package/src/4-dom/qreactTest.tsx +417 -0
  67. package/src/4-querysub/Querysub.ts +877 -0
  68. package/src/4-querysub/QuerysubController.ts +620 -0
  69. package/src/4-querysub/copyEvent.ts +0 -0
  70. package/src/4-querysub/permissions.ts +289 -0
  71. package/src/4-querysub/permissionsShared.ts +1 -0
  72. package/src/4-querysub/querysubPrediction.ts +525 -0
  73. package/src/5-diagnostics/FullscreenModal.tsx +67 -0
  74. package/src/5-diagnostics/GenericFormat.tsx +165 -0
  75. package/src/5-diagnostics/Modal.tsx +79 -0
  76. package/src/5-diagnostics/Table.tsx +183 -0
  77. package/src/5-diagnostics/TimeGrouper.tsx +114 -0
  78. package/src/5-diagnostics/diskValueAudit.ts +216 -0
  79. package/src/5-diagnostics/memoryValueAudit.ts +442 -0
  80. package/src/5-diagnostics/nodeMetadata.ts +135 -0
  81. package/src/5-diagnostics/qreactDebug.tsx +309 -0
  82. package/src/5-diagnostics/shared.ts +26 -0
  83. package/src/5-diagnostics/synchronousLagTracking.ts +47 -0
  84. package/src/TestController.ts +35 -0
  85. package/src/allowclient.flag +0 -0
  86. package/src/bits.ts +86 -0
  87. package/src/buffers.ts +69 -0
  88. package/src/config.ts +53 -0
  89. package/src/config2.ts +48 -0
  90. package/src/diagnostics/ActionsHistory.ts +56 -0
  91. package/src/diagnostics/NodeViewer.tsx +503 -0
  92. package/src/diagnostics/SizeLimiter.ts +62 -0
  93. package/src/diagnostics/TimeDebug.tsx +18 -0
  94. package/src/diagnostics/benchmark.ts +139 -0
  95. package/src/diagnostics/errorLogs/ErrorLogController.ts +515 -0
  96. package/src/diagnostics/errorLogs/ErrorLogCore.ts +274 -0
  97. package/src/diagnostics/errorLogs/LogClassifiers.tsx +302 -0
  98. package/src/diagnostics/errorLogs/LogFilterUI.tsx +84 -0
  99. package/src/diagnostics/errorLogs/LogNotify.tsx +101 -0
  100. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +724 -0
  101. package/src/diagnostics/errorLogs/LogViewer.tsx +757 -0
  102. package/src/diagnostics/errorLogs/hookErrors.ts +60 -0
  103. package/src/diagnostics/errorLogs/logFiltering.tsx +149 -0
  104. package/src/diagnostics/heapTag.ts +13 -0
  105. package/src/diagnostics/listenOnDebugger.ts +77 -0
  106. package/src/diagnostics/logs/DiskLoggerPage.tsx +572 -0
  107. package/src/diagnostics/logs/ObjectDisplay.tsx +165 -0
  108. package/src/diagnostics/logs/ansiFormat.ts +108 -0
  109. package/src/diagnostics/logs/diskLogGlobalContext.ts +38 -0
  110. package/src/diagnostics/logs/diskLogger.ts +305 -0
  111. package/src/diagnostics/logs/diskShimConsoleLogs.ts +32 -0
  112. package/src/diagnostics/logs/injectFileLocationToConsole.ts +50 -0
  113. package/src/diagnostics/logs/logGitHashes.ts +30 -0
  114. package/src/diagnostics/managementPages.tsx +289 -0
  115. package/src/diagnostics/periodic.ts +89 -0
  116. package/src/diagnostics/runSaturationTest.ts +416 -0
  117. package/src/diagnostics/satSchema.ts +64 -0
  118. package/src/diagnostics/trackResources.ts +82 -0
  119. package/src/diagnostics/watchdog.ts +55 -0
  120. package/src/errors.ts +132 -0
  121. package/src/forceProduction.ts +3 -0
  122. package/src/fs.ts +72 -0
  123. package/src/heapDumps.ts +666 -0
  124. package/src/https.ts +2 -0
  125. package/src/inject.ts +1 -0
  126. package/src/library-components/ATag.tsx +84 -0
  127. package/src/library-components/Button.tsx +344 -0
  128. package/src/library-components/ButtonSelector.tsx +64 -0
  129. package/src/library-components/DropdownCustom.tsx +151 -0
  130. package/src/library-components/DropdownSelector.tsx +32 -0
  131. package/src/library-components/Input.tsx +334 -0
  132. package/src/library-components/InputLabel.tsx +198 -0
  133. package/src/library-components/InputPicker.tsx +125 -0
  134. package/src/library-components/LazyComponent.tsx +62 -0
  135. package/src/library-components/MeasureHeightCSS.tsx +48 -0
  136. package/src/library-components/MeasuredDiv.tsx +47 -0
  137. package/src/library-components/ShowMore.tsx +51 -0
  138. package/src/library-components/SyncedController.ts +171 -0
  139. package/src/library-components/TimeRangeSelector.tsx +407 -0
  140. package/src/library-components/URLParam.ts +263 -0
  141. package/src/library-components/colors.tsx +14 -0
  142. package/src/library-components/drag.ts +114 -0
  143. package/src/library-components/icons.tsx +692 -0
  144. package/src/library-components/niceStringify.ts +50 -0
  145. package/src/library-components/renderToString.ts +52 -0
  146. package/src/misc/PromiseRace.ts +101 -0
  147. package/src/misc/color.ts +30 -0
  148. package/src/misc/getParentProcessId.cs +53 -0
  149. package/src/misc/getParentProcessId.ts +53 -0
  150. package/src/misc/hash.ts +83 -0
  151. package/src/misc/ipPong.js +13 -0
  152. package/src/misc/networking.ts +2 -0
  153. package/src/misc/random.ts +45 -0
  154. package/src/misc.ts +19 -0
  155. package/src/noserverhotreload.flag +0 -0
  156. package/src/path.ts +226 -0
  157. package/src/persistentLocalStore.ts +37 -0
  158. package/src/promise.ts +15 -0
  159. package/src/server.ts +73 -0
  160. package/src/src.d.ts +1 -0
  161. package/src/test/heapProcess.ts +36 -0
  162. package/src/test/mongoSatTest.tsx +55 -0
  163. package/src/test/satTest.ts +193 -0
  164. package/src/test/test.tsx +552 -0
  165. package/src/zip.ts +92 -0
  166. package/src/zipThreaded.ts +106 -0
  167. package/src/zipThreadedWorker.js +19 -0
  168. package/tsconfig.json +27 -0
  169. package/yarnSpec.txt +56 -0
@@ -0,0 +1,877 @@
1
+ // NOTE: This should be imported at the top of all entrypoints. BUT, if it is missed,
2
+ // this should ensure it is at least included once (even though including it after
3
+ // a file prevents it from transforming it, so we'll miss a lot of files doing it this late).
4
+ import "../inject";
5
+
6
+ import { isNode, timeInMinute } from "socket-function/src/misc";
7
+
8
+ import { SocketFunction } from "socket-function/SocketFunction";
9
+ import { isHotReloading, onHotReload, watchFilesAndTriggerHotReloading } from "socket-function/hot/HotReloadController";
10
+ import { RequireController, SerializedModule, setRequireBootRequire } from "socket-function/require/RequireController";
11
+ import { cache, cacheLimited, lazy } from "socket-function/src/caching";
12
+ import { getOwnMachineId, getThreadKeyCert, verifyMachineIdForPublicKey } from "../-a-auth/certs";
13
+ import { getSNICerts, publishMachineARecords } from "../-e-certs/EdgeCertController";
14
+ import { LOCAL_DOMAIN, nodePathAuthority } from "../0-path-value-core/NodePathAuthorities";
15
+ import { debugCoreMode, registerGetCompressNetwork, encodeParentFilter, registerGetCompressDisk } from "../0-path-value-core/pathValueCore";
16
+ import { clientWatcher, ClientWatcher } from "../1-path-client/pathValueClientWatcher";
17
+ import { SyncWatcher, proxyWatcher, specialObjectWriteValue, isSynced, PathValueProxyWatcher, atomic, doAtomicWrites, noAtomicSchema, undeleteFromLookup, registerSchemaPrefix } from "../2-proxy/PathValueProxyWatcher";
18
+ import { isInProxyDatabase, rawSchema } from "../2-proxy/pathDatabaseProxyBase";
19
+ import { isValueProxy2, getProxyPath } from "../2-proxy/pathValueProxy";
20
+ import { getCurrentCallAllowUndefined, getCurrentCall, CallSpec } from "../3-path-functions/PathFunctionRunner";
21
+ import { listenOnDebugger } from "../diagnostics/listenOnDebugger";
22
+ import { logErrors } from "../errors";
23
+ import { getLastPathPart, getPathIndexAssert, getPathStr2, hack_setPackedPathSuffix } from "../path";
24
+ import { QuerysubController, flushDelayedFunctions, onCallPredict, waitUntilAllPredictionsFinish } from "./QuerysubController";
25
+ import { PermissionsCheck } from "./permissions";
26
+ import { inlineNestedCalls, syncSchema } from "../3-path-functions/syncSchema";
27
+ import type { identityStorageKey, IdentityStorageType } from "../-a-auth/certs";
28
+
29
+ import "../diagnostics/watchdog";
30
+ import "../diagnostics/trackResources";
31
+ import "../diagnostics/benchmark";
32
+ import { qreact } from "../4-dom/qreact";
33
+ import { configRootDiscoveryLocation, getOwnNodeId } from "../-f-node-discovery/NodeDiscovery";
34
+ import { pathValueCommitter } from "../0-path-value-core/PathValueCommitter";
35
+ import debugbreak from "debugbreak";
36
+ import { extractPublicKey, verifyED25519 } from "../-a-auth/ed25519";
37
+ import { registerDynamicResource, registerResource } from "../diagnostics/trackResources";
38
+ import { registerPeriodic } from "../diagnostics/periodic";
39
+ import { sha256 } from "js-sha256";
40
+ import { green, red } from "socket-function/src/formatting/logColors";
41
+ import { minify_sync } from "terser";
42
+ import { isClient } from "../config2";
43
+ import { remoteWatcher } from "../1-path-client/RemoteWatcher";
44
+ import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
45
+ import { measureFnc } from "socket-function/src/profiling/measure";
46
+ import { delay } from "socket-function/src/batching";
47
+ import { MaybePromise } from "socket-function/src/types";
48
+ import { devDebugbreak, getDomain, isDevDebugbreak, isNoNetwork, isPublic } from "../config";
49
+ import { hookErrors } from "../diagnostics/errorLogs/hookErrors";
50
+ import { Schema2, Schema2T, t } from "../2-proxy/schema2";
51
+ import { CALL_PERMISSIONS_KEY } from "./permissionsShared";
52
+
53
+ export { t };
54
+
55
+
56
+ export type MachineSourceCheck<T = unknown> = {
57
+ /** NOTE: The source for this is added inline, and so it cannot use any external variables. */
58
+ getExtraDataClientsideInline?: () => T | "nosource";
59
+ shouldMachineIdSeeSource: (config: { machineId: string, extra: T }) => Promise<boolean>;
60
+ }
61
+
62
+ export function id(obj: unknown) {
63
+ let path = isValueProxy2(obj);
64
+ if (!path) {
65
+ return "";
66
+ }
67
+ return getLastPathPart(path);
68
+ }
69
+
70
+ export class Querysub {
71
+ public static syncSchema = syncSchema;
72
+ public static qreact = qreact;
73
+ public static Component = qreact.Component;
74
+ public static render = qreact.render;
75
+ public static context = qreact.context;
76
+ public static createElement = qreact.createElement;
77
+ public static Fragment = qreact.Fragment;
78
+ public static t = t;
79
+ /**
80
+ IMPORTANT! New schemas must be deployed by calling `yarn deploy`
81
+
82
+ const { data } = syncSchema<{
83
+ width: number;
84
+ height: number;
85
+ }>()({
86
+ // If domainName is LOCAL_DOMAIN, the values are kept in memory in the current process.
87
+ domainName: getDomain(),
88
+ functions: {
89
+ setWidth,
90
+ },
91
+ module,
92
+ moduleId: "sharedParams",
93
+ });
94
+ function setWidth(width: number) {
95
+ data().width = width;
96
+ }
97
+ */
98
+ public static createSchema = syncSchema;
99
+ public static noAtomicSchema = <T>(code: () => T) => noAtomicSchema(code);
100
+ /** Undelete is a bit dangerous, and races with garbage collection. Using gcDelay
101
+ * helps reduce this race, but it is still possible to undelete, and then
102
+ * have the value disappear after a few minutes.
103
+ * - The race occurs if an undelete happens less than ~5 * MAX_CHANGE_AGE
104
+ * before we start to GC.
105
+ * - This race is more of a propagation delay. The value is already past the point
106
+ * of no return, and so the undeletion cannot work. IF you set a large enough gcDelay
107
+ * (a month), then not only is this unlikely to happen, but it means it will only happen
108
+ * if the user waits a month to undelete it. Because undeletions aren't random, and are
109
+ * more likely to occur shorter after the deletion, this means the chance is really less
110
+ * than 5 minutes / 1 month (< 1/100K).
111
+ */
112
+ public static undeleteFromLookup = undeleteFromLookup;
113
+ public static WILDCARD = "*";
114
+ public static WILDCARD_NUM = "*" as any as number;
115
+ public static createLocalSchema = createLocalSchema;
116
+ public static flushDelayedFunctions = () => flushDelayedFunctions();
117
+ public static LOCAL_DOMAIN = LOCAL_DOMAIN;
118
+ public static DEBUG_CALLS = false;
119
+ public static DEBUG_PREDICTIONS = false;
120
+ public static PREDICT_CALLS = true;
121
+ public static AUDIT_PREDICTIONS = true;
122
+ public static SIMULATE_LAG = 0;
123
+
124
+ /** Delay used when functions are specified as delayCommit */
125
+ public static DELAY_COMMIT_DELAY = 1000 * 3;
126
+
127
+ /** Time predictions last for, after which point we remove them irregardless of if we have received a response.
128
+ * - NOTE: If this is too short, predictions stop before the server can finish. If it's too long,
129
+ * server issues (such as the FunctionRunner being missing) won't be noticed. So...
130
+ * we SHOULD increase this when our setup is more stable (60 seconds is probably good).
131
+ */
132
+ public static PREDICTION_MAX_LIFESPAN = 1000 * 15;
133
+
134
+ /** Fairly lax, to handle lag during initial connection setup.
135
+ * AND THEN, VERY lax, to handle server lag.
136
+ */
137
+ public static MAX_FUTURE_CALL_TIME = 10_000;
138
+
139
+ /** Maximum amount of synchronous time spend handling triggered operations until we
140
+ * abort them (with an error).
141
+ */
142
+ public static SET_MAX_TRIGGER_TIME = (value: number) => ClientWatcher.MAX_TRIGGER_TIME = value;
143
+
144
+ /** Compression makes serialization about 2X slower, but reduces the size by about 2X (more or less if your
145
+ * data size is dominated by value size instead of key size). */
146
+ public static COMPRESS_DISK = false;
147
+ public static COMPRESS_NETWORK = true;
148
+
149
+ public static now = getSyncedTime;
150
+ public static time = getSyncedTime;
151
+ public static getCallTime = getSyncedTime;
152
+ public static getFunctionCallTime = getSyncedTime;
153
+ public static getCallId = () => Querysub.getCallerMachineId() + "_" + Querysub.getCallTime();
154
+ // Returns a random value between 0 and 1. The value depends on the callId, and index (being identical for
155
+ // the same callid and index).
156
+ public static callRandom = (index: number) => hashRandom(Querysub.getCallId(), index);
157
+
158
+ public static configRootDiscoveryLocation = configRootDiscoveryLocation;
159
+
160
+
161
+ public static nowDelayed = timeDelayed;
162
+ public static timeDelayed = timeDelayed;
163
+
164
+ public static getCallerMachineId() {
165
+ let call = getCurrentCallAllowUndefined();
166
+ if (!call) return getOwnMachineId();
167
+ return getCurrentCall().callerMachineId;
168
+ }
169
+ public static getCallerIP = () => getCurrentCall().callerIP;
170
+ public static getCallerIPAllowUndefined = () => getCurrentCallAllowUndefined()?.callerIP;
171
+ public static CALL_PERMISSIONS_KEY = CALL_PERMISSIONS_KEY;
172
+
173
+ public static watchCurrentFnc = () => PathValueProxyWatcher.BREAK_ON_CALL.add(
174
+ getPathStr2(getCurrentCall().ModuleId, getCurrentCall().FunctionId)
175
+ );
176
+ public static unwatchCurrentFnc = () => PathValueProxyWatcher.BREAK_ON_CALL.delete(
177
+ getPathStr2(getCurrentCall().ModuleId, getCurrentCall().FunctionId)
178
+ );
179
+
180
+ public static watchUnsyncedComponents = () => qreact.watchUnsyncedComponents();
181
+ public static watchAnyUnsyncedComponents = () => qreact.watchUnsyncedComponents().size > 0;
182
+
183
+ public static doAtomicWrites = <T>(callback: () => T): T => doAtomicWrites(callback);
184
+
185
+ public static trustedDomains = new Set<string>();
186
+
187
+ /** Wait for some startup initialization. Not necessary, but can ensure any initial calls
188
+ * are in a better state.
189
+ * - Useful for client service scripts.
190
+ * NOTE: waitForFirstTimeSync is called in Socket.mount, so this function
191
+ * isn't presently necessary in most serverside scripts.
192
+ */
193
+ @measureFnc
194
+ public static async optionalStartupWait() {
195
+ await waitForFirstTimeSync();
196
+ await nodePathAuthority.waitUntilRoutingIsReady();
197
+ }
198
+
199
+ public static createWatcher(watcher: (obj: SyncWatcher) => void): {
200
+ dispose: () => void;
201
+ explicitlyTrigger: () => void;
202
+ } {
203
+ return proxyWatcher.createWatcher({
204
+ debugName: watcher.name,
205
+ canWrite: true,
206
+ watchFunction: () => {
207
+ return watcher(proxyWatcher.getTriggeredWatcher());
208
+ },
209
+ });
210
+ }
211
+ public static createWriteWatcher(watcher: (obj: SyncWatcher) => void) {
212
+ return this.createWatcher(watcher);
213
+ }
214
+ public static undoDelete(holder: { [key: string]: unknown }, key: string) {
215
+ holder[key] = specialObjectWriteValue;
216
+ }
217
+
218
+ public static localWriteWrapper<Args>(fnc: (...args: Args[]) => void): (...args: Args[]) => void {
219
+ return (...args: Args[]) => Querysub.serviceWriteDetached(() => fnc(...args));
220
+ }
221
+ public static runSynced = Querysub.serviceWriteDetached;
222
+ public static commit = Querysub.serviceWriteDetached;
223
+ public static write = Querysub.serviceWriteDetached;
224
+ public static serviceWriteDetached(fnc: () => unknown) {
225
+ logErrors(proxyWatcher.commitFunction({
226
+ canWrite: true,
227
+ watchFunction: fnc
228
+ }));
229
+ }
230
+
231
+ public static exit = Querysub.gracefulTerminateProcess;
232
+ public static async gracefulTerminateProcess() {
233
+ await pathValueCommitter.waitForValuesToCommit();
234
+ if (isNode()) {
235
+ process.exit();
236
+ }
237
+ };
238
+
239
+ public static syncedCommit = Querysub.serviceWrite;
240
+ public static commitSynced = Querysub.serviceWrite;
241
+ public static async serviceWrite<T>(fnc: () => T) {
242
+ return await proxyWatcher.commitFunction({
243
+ canWrite: true,
244
+ watchFunction: fnc
245
+ });
246
+ }
247
+ public static localCommit = Querysub.localRead;
248
+ public static commitLocal = Querysub.localRead;
249
+ public static localRead<T>(fnc: () => T) {
250
+ return proxyWatcher.runOnce({
251
+ watchFunction: fnc
252
+ });
253
+ }
254
+
255
+ public static async unsafeInlineFunctionsServiceWrite<T>(code: () => T) {
256
+ return await Querysub.serviceWrite(() => {
257
+ return inlineNestedCalls(code);
258
+ });
259
+ }
260
+
261
+ public static undefinedIfAnyUnsynced<T>(get: () => T) {
262
+ let count = { value: 0 };
263
+ let result = proxyWatcher.countUnsynced(count, get);
264
+ if (count.value > 0) return undefined;
265
+ return result;
266
+ }
267
+
268
+ public static ignorePermissionsChecks = <T>(code: () => T) => PermissionsCheck.skipPermissionsChecks(code);
269
+ public static skipPermissionsChecks = <T>(code: () => T) => PermissionsCheck.skipPermissionsChecks(code);
270
+
271
+ public static anyUnsynced() {
272
+ return !Querysub.allSynced();
273
+ }
274
+ public static allSynced() {
275
+ return proxyWatcher.isAllSynced();
276
+ }
277
+ public static fullySynced = Querysub.allSynced;
278
+ public static isFullySynced = Querysub.allSynced;
279
+ public static isAllSynced = Querysub.allSynced;
280
+ public static isAnyUnsynced = Querysub.anyUnsynced;
281
+
282
+ public static pathHasAnyWatchers(get: () => unknown) {
283
+ return clientWatcher.pathHasAnyWatchers(getProxyPath(get));
284
+ }
285
+
286
+ public static onCommitFinished(callback: () => void) {
287
+ proxyWatcher.getTriggeredWatcher().onInnerDisposed.push(callback);
288
+ }
289
+
290
+ public static onCallPredict = (x: CallSpec | undefined) => onCallPredict(x);
291
+ /** NOTE: USUALLY you don't have to wait for predictions, as functions will run in order anyways.
292
+ * BUT, if you run a synced function in the UI, then somewhere unrelated try to read values
293
+ * for offload writing (which isn't ideal, but sometimes is necessary), then waiting
294
+ * for predictions to finish can help in the rare cases where predictions are slow
295
+ * (such as if code is still loading).
296
+ */
297
+ public static waitUntilAllPredictionsFinished = () => waitUntilAllPredictionsFinish();
298
+ public static onCommitPredictFinished = this.onCallPredict;
299
+
300
+ public static getOwnMachineId = getOwnMachineId;
301
+ public static getSelfMachineId = getOwnMachineId;
302
+
303
+ public static getOwnNodeId = getOwnNodeId;
304
+ public static getSelfNodeId = getOwnMachineId;
305
+
306
+ /** Set ClientWatcher.DEBUG_SOURCES to true for to be populated */
307
+ public static getTriggerReason() {
308
+ return proxyWatcher.getTriggeredWatcher().triggeredByChanges;
309
+ }
310
+
311
+ public static isInSyncedCall() {
312
+ return proxyWatcher.inWatcher() && isInProxyDatabase();
313
+ }
314
+
315
+ public static isSynced(value: unknown) {
316
+ return isSynced(value);
317
+ }
318
+
319
+ public static watchWrites(get: () => unknown) {
320
+ let path = getProxyPath(get);
321
+ if (!path) throw new Error(`Failed to get path`);
322
+ PathValueProxyWatcher.LOG_WRITES_INCLUDES.add(path);
323
+ }
324
+
325
+ public static assertDomainAllowed(path: string) {
326
+ let domain = getPathIndexAssert(path, 0);
327
+ if (!Querysub.trustedDomains.has(domain)) {
328
+ throw new Error(`Domain ${domain} is not trusted on this server`);
329
+ }
330
+ }
331
+
332
+ private static pendingLazyCommit: undefined | {
333
+ fncs: (() => unknown)[];
334
+ };
335
+ private static getLazyCommit(delayTime: number = 100) {
336
+ const TARGET_FRACTION_TIME = 0.1;
337
+ if (this.pendingLazyCommit) {
338
+ return this.pendingLazyCommit;
339
+ }
340
+ this.pendingLazyCommit = {
341
+ fncs: [],
342
+ };
343
+ let obj = this.pendingLazyCommit;
344
+ setTimeout(async () => {
345
+ let fncs = obj.fncs;
346
+ obj.fncs = [];
347
+ // Go back to non-active mode if there are no triggers
348
+ if (fncs.length === 0) {
349
+ this.pendingLazyCommit = undefined;
350
+ return;
351
+ }
352
+
353
+ let startTime = Date.now();
354
+ //console.log(`Batched ${fncs.length} functions in lazy commit`);
355
+ await Querysub.commitSynced(() => {
356
+ for (let fnc of fncs) {
357
+ try {
358
+ fnc();
359
+ } catch (e) {
360
+ console.error(`Error in lazy commit`, e);
361
+ }
362
+ }
363
+ });
364
+ await clientWatcher.waitForTriggerFinished();
365
+ //await delay("afterPaint");
366
+ let totalTimeCaused = Date.now() - startTime;
367
+ totalTimeCaused = Math.max(16, totalTimeCaused);
368
+
369
+ let delayTime = totalTimeCaused / TARGET_FRACTION_TIME;
370
+ delayTime = Math.min(5000, delayTime);
371
+ //console.log(`Lazy commit took ${totalTimeCaused}ms, setting delay time to ${delayTime}ms`);
372
+ let queuedWhileCommitting = obj.fncs;
373
+ this.pendingLazyCommit = undefined;
374
+ // Create a new commit with our new delay time
375
+ let newCommit = this.getLazyCommit(delayTime);
376
+ newCommit.fncs.push(...queuedWhileCommitting);
377
+ }, delayTime);
378
+ return obj;
379
+ }
380
+ /** Waits to commit
381
+ * - Might reorder other types of commit calls, but WILL NOT reorder commitLazy calls
382
+ * - Batches fncs into single functions (which might result in extra dependencies /
383
+ * extra rejections of unrelated functions).
384
+ * - Tries to track triggered time from these commits, and keep it below 10%,
385
+ * batching more and more to make sure this is the case.
386
+ */
387
+ public static commitLazy(
388
+ fnc: () => unknown,
389
+ ) {
390
+ this.getLazyCommit().fncs.push(fnc);
391
+ }
392
+
393
+ private static synchronousStartTime = 0;
394
+ /** To be called in a loop, frequently, and when enough time has passed without a paint,
395
+ * it will return a promise which delays long enough until we can paint again.
396
+ * - Works in conjunctions with other yieldForPaint calls, so if many loops are
397
+ * running we won't unnecessarily wait in all of them.
398
+ * @param yieldAfterTime, if there are multiple concurrent callers the first one will
399
+ * determine the used time, and both will use that time.
400
+ */
401
+ public static yieldForPaint(
402
+ yieldAfterTime = 1000
403
+ ): MaybePromise<void> {
404
+ // NOTE: We even wait serverside, as the server doesn't want to get locked up
405
+ // on synchronous processing either!
406
+
407
+ // NOTE: Painting can easily take 32ms, and even more if we are rendering,
408
+ // so... we can't wait too frequently, otherwise slow processing scripts (the
409
+ // only reason to use this function) will be significantly slower.
410
+ if (!this.synchronousStartTime) {
411
+ this.synchronousStartTime = Date.now();
412
+ void delay("paintLoop").finally(() => {
413
+ this.synchronousStartTime = 0;
414
+ });
415
+ }
416
+ let timeSinceStart = Date.now() - this.synchronousStartTime;
417
+ if (timeSinceStart > yieldAfterTime) {
418
+ console.log(`Yielded for paint after ${timeSinceStart}ms`);
419
+ return delay("paintLoop");
420
+ }
421
+ }
422
+
423
+ public static debugMax() {
424
+ this.debug();
425
+ ClientWatcher.DEBUG_READS = true;
426
+ SocketFunction.silent = false;
427
+ PathValueProxyWatcher.TRACE = true;
428
+ debugCoreMode();
429
+ }
430
+ public static debug() {
431
+ console.log(`We are ${Querysub.getSelfMachineId()}`);
432
+ ClientWatcher.DEBUG_WRITES = true;
433
+ Querysub.DEBUG_CALLS = true;
434
+ }
435
+
436
+ private static hostCalled = false;
437
+ private static afterBootImports: string[] = [];
438
+ /** NOTE: Imports occur one at a time, after the "clientsideEntryPoint", with each import waiting for the previous to finish.
439
+ */
440
+ public static registerAdditionalRoot(path: string) {
441
+ if (Querysub.hostCalled) {
442
+ throw new Error(`All "Querysub.registerAfterBootImport" calls MUST be called before "Querysub.hostServer" (to prevent servers from existing in partially loaded states).`);
443
+ }
444
+ Querysub.afterBootImports.push(path);
445
+ }
446
+ public static async hostServer(config: {
447
+ // Domains we will run functions from. If this domains have any malicious code deploy (to the FunctionRunner data schema),
448
+ // we are vulnerable to running this code when we check permissions for syncing paths.
449
+ trustedDomains: string[];
450
+ // The root of your project, likely where your package.json and .git are.
451
+ rootPath: string;
452
+ // Ex, `./src/client.ts`
453
+ // - Relative to rootPath
454
+ clientsideEntryPoint: string;
455
+ hotReload: boolean;
456
+ /** You can also set `port: 0` if any port is fine. */
457
+ port: number;
458
+ justHTTP?: boolean;
459
+ debugName?: string;
460
+
461
+ sourceCheck?: MachineSourceCheck<any>;
462
+ }) {
463
+ if (isClient()) {
464
+ throw new Error(`--client processes cannot host a service. Either stop passing --client and keep the process on the network and trusted, or stop calling hostServer and call Querysub.configRootDiscoveryLocation instead.`);
465
+ }
466
+ Querysub.hostCalled = true;
467
+ for (let domain of config.trustedDomains) {
468
+ Querysub.trustedDomains.add(domain);
469
+ }
470
+
471
+ if (config.hotReload) {
472
+ watchFilesAndTriggerHotReloading();
473
+ }
474
+
475
+ listenOnDebugger(config.debugName ?? "Querysub");
476
+ //SocketFunction.silent = false;
477
+ //PermissionsCheck.DEBUG = true;
478
+ //ClientWatcher.DEBUG_WRITES = true;
479
+
480
+ await this.addSourceMapCheck(config);
481
+
482
+ // NOTE: This is just static hosting, and unrelated to querysub (sort of). Any site with some kind of transpiler
483
+ // and way to serve transpiled files can server our code.
484
+ SocketFunction.expose(RequireController);
485
+ setRequireBootRequire(config.rootPath);
486
+ SocketFunction.setDefaultHTTPCall(RequireController, "requireHTML", {
487
+ requireCalls: [
488
+ config.clientsideEntryPoint,
489
+ ...Querysub.afterBootImports,
490
+ ]
491
+ });
492
+
493
+ if (!config.justHTTP) {
494
+ SocketFunction.expose(QuerysubController);
495
+ }
496
+
497
+ let allowHostnames: string[] = [];
498
+ for (let domain of Querysub.trustedDomains) {
499
+ allowHostnames.push(domain);
500
+ }
501
+ allowHostnames.push("127-0-0-1." + getDomain());
502
+ await SocketFunction.mount({
503
+ public: isPublic(),
504
+ port: config.port,
505
+ autoForwardPort: true,
506
+ ...await getThreadKeyCert(),
507
+ SNICerts: {
508
+ ...getSNICerts({
509
+ publicPort: config.port,
510
+ }),
511
+ },
512
+ allowHostnames,
513
+ });
514
+
515
+ await publishMachineARecords();
516
+ }
517
+ private static async addSourceMapCheck(config: {
518
+ sourceCheck?: MachineSourceCheck;
519
+ }) {
520
+ const sourceCheck = config.sourceCheck;
521
+ if (!sourceCheck) return;
522
+ let { getExtraDataClientsideInline, shouldMachineIdSeeSource, } = sourceCheck;
523
+
524
+ let ed25519 = await import("../-a-auth/ed25519");
525
+ const ed25519Module = require.cache[require.resolve("../-a-auth/ed25519")];
526
+ if (!ed25519Module) {
527
+ throw new Error(`Failed to find imported ed25519 module in require.cache`);
528
+ }
529
+ let moduleContents = ed25519Module.moduleContents;
530
+ if (!moduleContents) {
531
+ throw new Error(`ed25519 module was missing moduleContents`);
532
+ }
533
+ moduleContents = moduleContents.replace(/\/\/# sourceMappingURL=.*\n/g, "");
534
+
535
+ type SignedIdentity = {
536
+ payload: {
537
+ machineId: string;
538
+ publicKeyB64: string;
539
+ time: number;
540
+ extra: ReturnType<Exclude<MachineSourceCheck["getExtraDataClientsideInline"], undefined>>;
541
+ };
542
+ signatureB64: string;
543
+ };
544
+
545
+ const getExtraDataClientsideSource = (getExtraDataClientsideInline || (() => { })).toString();
546
+ function signWrapper(moduleContents: string, getExtraDataClientsideSource: string) {
547
+ let exports = {} as typeof ed25519;
548
+ let module = { exports };
549
+ function require() { throw new Error("require not supported"); }
550
+ let fnc = `(function ed25519(exports, require, module, __filename, __dirname, importDynamic) {${moduleContents}\n })`;
551
+ eval(fnc)(exports, require, module, "inlined", "inlined", require);
552
+
553
+ if (!getExtraDataClientsideSource.startsWith("function") && !getExtraDataClientsideSource.split("\n").includes("=>")) {
554
+ getExtraDataClientsideSource = "function " + getExtraDataClientsideSource;
555
+ }
556
+ const getExtraDataClientside = eval(`(${getExtraDataClientsideSource})`) as Exclude<MachineSourceCheck["getExtraDataClientsideInline"], undefined>;
557
+
558
+ globalThis.remapImportRequestsClientside = globalThis.remapImportRequestsClientside || [];
559
+ globalThis.remapImportRequestsClientside.push(async (args) => {
560
+ try {
561
+ let key: typeof identityStorageKey = "machineCA_6";
562
+ let storageValueJSON = localStorage.getItem(key);
563
+ if (!storageValueJSON) return args;
564
+ let storageValue = JSON.parse(storageValueJSON) as IdentityStorageType;
565
+ let machineId = storageValue.domain;
566
+
567
+ let pem = Buffer.from(storageValue.keyB64, "base64");
568
+ let privateKey = exports.extractRawED25519PrivateKey(pem);
569
+ let publicKey = await exports.extractPublicKey(privateKey);
570
+
571
+ let extra = getExtraDataClientside();
572
+ if (extra === "nosource") return args;
573
+ let payload: SignedIdentity["payload"] = {
574
+ machineId,
575
+ time: Date.now(),
576
+ extra,
577
+ publicKeyB64: publicKey.toString("base64"),
578
+ };
579
+ let signature = await exports.signWithPEM({
580
+ pem,
581
+ data: payload,
582
+ });
583
+ let signedIdentity: SignedIdentity = {
584
+ payload,
585
+ signatureB64: signature.toString("base64"),
586
+ };
587
+
588
+ args[2] = args[2] || {};
589
+ let config = args[2] as any;
590
+ config.signedIdentity = signedIdentity;
591
+ return args;
592
+ } catch (e) {
593
+ console.error(`Error signing request, code won't include sourcesmaps`, e);
594
+ return args;
595
+ }
596
+ });
597
+ }
598
+ moduleContents = `(${signWrapper.toString()})(${JSON.stringify(moduleContents)}, ${JSON.stringify(getExtraDataClientsideSource)})`;
599
+
600
+ RequireController.injectHTMLBeforeStartup(`
601
+ <script>
602
+ ${moduleContents}
603
+ </script>
604
+ `);
605
+
606
+ async function isAllowedToSeeSource(signedIdentity: SignedIdentity | undefined): Promise<boolean> {
607
+ if (!signedIdentity) return false;
608
+ let { signatureB64, payload } = signedIdentity;
609
+ let { publicKeyB64, machineId, time, extra } = payload;
610
+ let publicKey = Buffer.from(publicKeyB64, "base64");
611
+ if (!verifyMachineIdForPublicKey({ machineId, publicKey })) return false;
612
+
613
+ // Requests shouldn't take TOO long, and if they do, it is likely due to cache, and permissions should be denied.
614
+ let expireTime = Date.now() - timeInMinute * 5;
615
+ if (time < expireTime) return false;
616
+
617
+ return await shouldMachineIdSeeSource({ machineId, extra });
618
+ }
619
+ const stripSourceBase = cacheLimited(100_000, function stripSourceBase(source: string | undefined) {
620
+ if (!source) return source;
621
+
622
+ const USE_TERSER = true;
623
+
624
+ if (USE_TERSER) {
625
+ // NOTE: Using terser results in a bundle of 638KB, instead of 815KB (a 20% reduction),
626
+ // which is just barely good enough to justify using a library. It is a lot slower too,
627
+ // but as it is cached, it should be fine...
628
+ // - It takes about 5s to minify our entire code base, which again, is barely good enough.
629
+ try {
630
+ return minify_sync(
631
+ { file: source },
632
+ {
633
+ mangle: {
634
+ module: true
635
+ }
636
+ }
637
+ ).code;
638
+ } catch {
639
+ return source;
640
+ }
641
+ }
642
+
643
+ // Strip inline sourcemaps
644
+ source = source.replace(/\/\/# sourceMappingURL=.*\n/g, "");
645
+ // Strip comments
646
+ let inSingleLineComment = false;
647
+ let inMultiLineComment = false;
648
+ let output = "";
649
+ for (let i = 0; i < source.length; i++) {
650
+ let char = source[i];
651
+ if (inSingleLineComment) {
652
+ if (char === "\n") {
653
+ inSingleLineComment = false;
654
+ output += "\n";
655
+ }
656
+ continue;
657
+ }
658
+ if (inMultiLineComment) {
659
+ if (char === "*" && source[i + 1] === "/") {
660
+ inMultiLineComment = false;
661
+ i++;
662
+ }
663
+ continue;
664
+ }
665
+ if (char === "/") {
666
+ // HACK: To prevent needing to handle strings, we only strip comment at the beginning of the line
667
+ if (source[i + 1] === "/") {
668
+ // Find line start
669
+ let lineStart = source.lastIndexOf("\n", i);
670
+ if (!source.slice(lineStart, i).trim()) {
671
+ inSingleLineComment = true;
672
+ i++;
673
+ continue;
674
+ }
675
+ }
676
+ if (source[i + 1] === "*") {
677
+ inMultiLineComment = true;
678
+ i++;
679
+ continue;
680
+ }
681
+ }
682
+ output += char;
683
+ }
684
+ return output;
685
+ });
686
+ function stripSource(module: SerializedModule) {
687
+ module = { ...module };
688
+ module.source = stripSourceBase(module.source);
689
+ return module;
690
+ }
691
+
692
+ RequireController.addMapGetModules(async (result, args) => {
693
+ let configObj = args[2] as { signedIdentity: SignedIdentity | undefined } | undefined;
694
+ if (!await isAllowedToSeeSource(configObj?.signedIdentity)) {
695
+ await isAllowedToSeeSource(configObj?.signedIdentity);
696
+ //console.log(red(`Not allowed to see source`));
697
+ for (let [key, value] of Object.entries(result.modules)) {
698
+ result.modules[key] = stripSource(value);
699
+ }
700
+ } else {
701
+ //console.log(green(`Allowed to see source`));
702
+ }
703
+ return result;
704
+ });
705
+ }
706
+
707
+ public static async hostService(name: string, port = 0) {
708
+ if (isClient()) {
709
+ throw new Error(`--client processes cannot host a service. Either stop passing --client and keep the process on the network and trusted, or stop calling hostServer and call Querysub.configRootDiscoveryLocation instead.`);
710
+ }
711
+
712
+ listenOnDebugger(name);
713
+ await SocketFunction.mount({
714
+ public: isPublic(),
715
+ port,
716
+ ...await getThreadKeyCert(),
717
+ // WAIT! Why were services getting SNI CERTS!!!??? This is used to set the
718
+ // HTTPS cloudflare pool ips, so... services should DEFINITELY not use this!
719
+ // SNICerts: {
720
+ // ...getSNICerts({
721
+ // publicPort: port,
722
+ // }),
723
+ // }
724
+ });
725
+
726
+ await publishMachineARecords();
727
+
728
+ await Querysub.optionalStartupWait();
729
+ }
730
+
731
+ public static keys<T extends { [key: string | number]: unknown }>(obj: T, config?: {
732
+ /** Value between 0 and 1, used with endFraction to try return a subset of keys.
733
+ * - Tries to only talk to the necessary servers, being highly efficient if the servers are sharded
734
+ * (otherwise likely does filtering serverside, which is still somewhat efficient).
735
+ */
736
+ startFraction?: number;
737
+ /** Value between 0 and 1 */
738
+ endFraction?: number;
739
+ }): (keyof T)[] {
740
+ let { startFraction, endFraction } = config || {};
741
+ if (startFraction === undefined || endFraction === undefined) {
742
+ return Object.keys(obj);
743
+ }
744
+ let path = isValueProxy2(obj);
745
+ if (!path) {
746
+ return Object.keys(obj);
747
+ }
748
+ let packedPath = encodeParentFilter({ path, startFraction, endFraction });
749
+ return proxyWatcher.getKeys(packedPath);
750
+ }
751
+
752
+ // TODO: Maybe expose checkPermissions(getValue: () => unknown)?
753
+ // - It would be easy, if we every need to explicitly check if we have permissions. Although, it seems
754
+ // like just relying on the automatic checking is better?
755
+ }
756
+
757
+ let localSchemaNames = new Set<string>();
758
+ function createLocalSchema<T>(name: string): () => T;
759
+ function createLocalSchema<SchemaDef extends Schema2>(name: string, schema: SchemaDef): () => Schema2T<SchemaDef>;
760
+ function createLocalSchema<T>(name: string, schema2?: Schema2): () => T {
761
+ if (localSchemaNames.has(name) && !isHotReloading()) {
762
+ throw new Error(`Already have local schema with name ${JSON.stringify(name)}`);
763
+ }
764
+ if (schema2) {
765
+ registerSchemaPrefix({
766
+ schema: schema2,
767
+ prefixPathStr: getPathStr2(LOCAL_DOMAIN, name),
768
+ });
769
+ }
770
+ localSchemaNames.add(name);
771
+ let schema = rawSchema<{
772
+ [LOCAL_DOMAIN]: {
773
+ [name: string]: T;
774
+ }
775
+ }>();
776
+ return () => {
777
+ return schema()[LOCAL_DOMAIN][name];
778
+ };
779
+ }
780
+
781
+ const timeData = createLocalSchema<{
782
+ time: number;
783
+ intervals: {
784
+ [interval: number]: number;
785
+ };
786
+ }>("time");
787
+ const initTimeLoop = lazy(() => {
788
+ function onNextTime() {
789
+ requestAnimationFrame(onNextTime);
790
+ // TODO: We should really stop the loop, and start it again when someone is watching
791
+ // our values. Which we can either do via getSyncedTime, OR, we could technically
792
+ // get pathValueClientWatcher to tell us when someone is watching it (but using getSyncedTime
793
+ // is probably better).
794
+ if (clientWatcher.pathHasAnyWatchers(getProxyPath(() => timeData().time))) {
795
+ Querysub.serviceWriteDetached(() => {
796
+ timeData().time = Date.now();
797
+ });
798
+ }
799
+ }
800
+ onNextTime();
801
+ });
802
+ let initInterval = cache((interval: number) => {
803
+ setInterval(() => {
804
+ if (clientWatcher.pathHasAnyWatchers(getProxyPath(() => timeData().intervals[interval]))) {
805
+ Querysub.serviceWriteDetached(() => {
806
+ timeData().intervals[interval] = Date.now();
807
+ });
808
+ }
809
+ }, interval);
810
+ });
811
+ function getSyncedTime() {
812
+ let call = getCurrentCallAllowUndefined();
813
+ if (call) {
814
+ return call.runAtTime.time;
815
+ }
816
+ if (isNode()) {
817
+ return Date.now();
818
+ }
819
+ initTimeLoop();
820
+ return atomic(timeData().time) || Date.now();
821
+ }
822
+
823
+
824
+ function timeDelayed(interval: number) {
825
+ let call = getCurrentCallAllowUndefined();
826
+ if (call) {
827
+ return call.runAtTime.time;
828
+ }
829
+ initInterval(interval);
830
+ atomic(timeData().intervals[interval]);
831
+ return Date.now();
832
+ }
833
+
834
+ // Returns a value between 0 and 1
835
+ function hashRandom(base: string, offset: number): number {
836
+ let hashBuf = Buffer.from(sha256(base + "_" + offset), "hex");
837
+ let value0 = hashBuf.readUInt32BE(0);
838
+ let value1 = hashBuf.readUInt16BE(4);
839
+ return value0 / (2 ** 32) + value1 / (2 ** 48);
840
+ }
841
+
842
+ // NOTE: Use `SocketFunction.logMessages = true` to diagnose what specifically is using the bandwidth.
843
+ setImmediate(() => {
844
+ let uploadLatest = 0;
845
+ let downloadLatest = 0;
846
+ let uploadTotal = 0;
847
+ let downloadTotal = 0;
848
+ registerDynamicResource("network|TOTAL_UPLOAD", () => uploadTotal);
849
+ registerDynamicResource("network|TOTAL_DOWNLOAD", () => downloadTotal);
850
+ registerDynamicResource("network|LATEST_UPLOAD", () => {
851
+ let value = uploadLatest;
852
+ uploadLatest = 0;
853
+ return value;
854
+ });
855
+ registerDynamicResource("network|LATEST_DOWNLOAD", () => {
856
+ let value = downloadLatest;
857
+ downloadLatest = 0;
858
+ return value;
859
+ });
860
+ SocketFunction.trackMessageSizes.upload.push(size => {
861
+ uploadLatest += size;
862
+ uploadTotal += size;
863
+ });
864
+ SocketFunction.trackMessageSizes.download.push(size => {
865
+ downloadLatest += size;
866
+ downloadTotal += size;
867
+ });
868
+ });
869
+
870
+ setImmediate(() => {
871
+ hookErrors();
872
+ });
873
+
874
+ registerGetCompressNetwork(() => Querysub.COMPRESS_NETWORK);
875
+ registerGetCompressDisk(() => Querysub.COMPRESS_DISK);
876
+
877
+ (global as any).Querysub = Querysub;