querysub 0.312.0 → 0.313.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 (69) hide show
  1. package/.cursorrules +1 -1
  2. package/costsBenefits.txt +4 -1
  3. package/package.json +3 -2
  4. package/spec.txt +23 -18
  5. package/src/-0-hooks/hooks.ts +1 -1
  6. package/src/-a-archives/archives.ts +16 -3
  7. package/src/-a-archives/archivesBackBlaze.ts +51 -3
  8. package/src/-a-archives/archivesLimitedCache.ts +175 -0
  9. package/src/-a-archives/archivesPrivateFileSystem.ts +299 -0
  10. package/src/-a-auth/certs.ts +58 -31
  11. package/src/-b-authorities/cdnAuthority.ts +2 -2
  12. package/src/-b-authorities/dnsAuthority.ts +3 -2
  13. package/src/-c-identity/IdentityController.ts +3 -2
  14. package/src/-d-trust/NetworkTrust2.ts +17 -19
  15. package/src/-e-certs/EdgeCertController.ts +3 -4
  16. package/src/-e-certs/certAuthority.ts +1 -2
  17. package/src/-f-node-discovery/NodeDiscovery.ts +9 -7
  18. package/src/-g-core-values/NodeCapabilities.ts +6 -1
  19. package/src/0-path-value-core/NodePathAuthorities.ts +1 -1
  20. package/src/0-path-value-core/PathValueCommitter.ts +3 -3
  21. package/src/0-path-value-core/PathValueController.ts +3 -3
  22. package/src/0-path-value-core/archiveLocks/ArchiveLocks2.ts +15 -37
  23. package/src/0-path-value-core/pathValueCore.ts +4 -3
  24. package/src/3-path-functions/PathFunctionRunner.ts +2 -2
  25. package/src/4-dom/qreact.tsx +4 -3
  26. package/src/4-querysub/Querysub.ts +2 -2
  27. package/src/4-querysub/QuerysubController.ts +2 -2
  28. package/src/5-diagnostics/GenericFormat.tsx +1 -0
  29. package/src/5-diagnostics/Table.tsx +3 -0
  30. package/src/5-diagnostics/diskValueAudit.ts +2 -1
  31. package/src/5-diagnostics/nodeMetadata.ts +0 -1
  32. package/src/deployManager/components/MachineDetailPage.tsx +9 -1
  33. package/src/deployManager/components/ServiceDetailPage.tsx +10 -1
  34. package/src/diagnostics/NodeViewer.tsx +3 -4
  35. package/src/diagnostics/logs/FastArchiveAppendable.ts +748 -0
  36. package/src/diagnostics/logs/FastArchiveController.ts +524 -0
  37. package/src/diagnostics/logs/FastArchiveViewer.tsx +863 -0
  38. package/src/diagnostics/logs/LogViewer2.tsx +349 -0
  39. package/src/diagnostics/logs/TimeRangeSelector.tsx +94 -0
  40. package/src/diagnostics/logs/diskLogger.ts +135 -305
  41. package/src/diagnostics/logs/diskShimConsoleLogs.ts +6 -29
  42. package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +577 -0
  43. package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +225 -0
  44. package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +207 -0
  45. package/src/diagnostics/logs/importLogsEntry.ts +38 -0
  46. package/src/diagnostics/logs/injectFileLocationToConsole.ts +7 -17
  47. package/src/diagnostics/logs/lifeCycleAnalysis/lifeCycles.tsx +0 -0
  48. package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +151 -0
  49. package/src/diagnostics/managementPages.tsx +7 -16
  50. package/src/diagnostics/misc-pages/ComponentSyncStats.tsx +0 -1
  51. package/src/diagnostics/periodic.ts +5 -0
  52. package/src/diagnostics/watchdog.ts +2 -2
  53. package/src/functional/SocketChannel.ts +67 -0
  54. package/src/library-components/Input.tsx +1 -1
  55. package/src/library-components/InputLabel.tsx +5 -2
  56. package/src/misc.ts +111 -0
  57. package/src/src.d.ts +34 -1
  58. package/src/user-implementation/userData.ts +4 -3
  59. package/test.ts +13 -0
  60. package/testEntry2.ts +29 -0
  61. package/src/diagnostics/errorLogs/ErrorLogController.ts +0 -535
  62. package/src/diagnostics/errorLogs/ErrorLogCore.ts +0 -274
  63. package/src/diagnostics/errorLogs/LogClassifiers.tsx +0 -308
  64. package/src/diagnostics/errorLogs/LogFilterUI.tsx +0 -84
  65. package/src/diagnostics/errorLogs/LogNotify.tsx +0 -101
  66. package/src/diagnostics/errorLogs/LogTimeSelector.tsx +0 -723
  67. package/src/diagnostics/errorLogs/LogViewer.tsx +0 -757
  68. package/src/diagnostics/errorLogs/logFiltering.tsx +0 -149
  69. package/src/diagnostics/logs/DiskLoggerPage.tsx +0 -613
@@ -28,7 +28,7 @@ import { addComponentButton } from "../5-diagnostics/qreactDebug";
28
28
  import { closeAllModals } from "../5-diagnostics/Modal";
29
29
  import { delay } from "socket-function/src/batching";
30
30
 
31
- const LogNotify = createLazyComponent(() => import("./errorLogs/LogNotify"))("LogNotify");
31
+ const ErrorWarning = createLazyComponent(() => import("./logs/errorNotifications/ErrorWarning"))("ErrorWarning");
32
32
 
33
33
  export const managementPageURL = new URLParam("managementpage", "");
34
34
  export const showingManagementURL = new URLParam("showingmanagement", false);
@@ -63,6 +63,9 @@ export async function registerManagementPages2(config: {
63
63
  title?: string;
64
64
  }[];
65
65
  }) {
66
+ // Wait, so this happens asynchronously
67
+ await delay(0);
68
+
66
69
  if (registeredModule) {
67
70
  if (registeredModule === config.module) return;
68
71
  throw new Error(`registerManagementPages2 called with a different module than previously registered. This will break isManagementUser, and so is not allowed.`);
@@ -84,20 +87,8 @@ export async function registerManagementPages2(config: {
84
87
  });
85
88
  inputPages.push({
86
89
  title: "Logs",
87
- componentName: "DiskLoggerPage",
88
- controllerName: "DiskLoggerController",
89
- getModule: () => import("./logs/DiskLoggerPage"),
90
- });
91
- inputPages.push({
92
- title: "Error Notifications",
93
- componentName: "LogViewer",
94
- controllerName: "LogViewerController",
95
- getModule: () => import("./errorLogs/LogViewer"),
96
- });
97
- inputPages.push({
98
- title: "Error Classes",
99
- componentName: "LogClassifiers",
100
- getModule: () => import("./errorLogs/LogClassifiers"),
90
+ componentName: "LogViewer2",
91
+ getModule: () => import("./logs/LogViewer2"),
101
92
  });
102
93
  inputPages.push({
103
94
  title: "Audit Paths",
@@ -319,7 +310,7 @@ class ManagementRoot extends qreact.Component {
319
310
  `}
320
311
  </style>
321
312
  <div class={css.fillWidth.hbox(30, 10).wrap.hsl(245, 25, 60).pad2(10).pointerEvents("all")}>
322
- <LogNotify />
313
+ <ErrorWarning />
323
314
  {pages.map(page =>
324
315
  <ATag values={[{ param: managementPageURL, value: page.componentName }]}>{page.title}</ATag>
325
316
  )}
@@ -10,7 +10,6 @@ import { formatNumber, formatPercent, formatTime } from "socket-function/src/for
10
10
  import { nextId, sort } from "socket-function/src/misc";
11
11
  import { ATag } from "../../library-components/ATag";
12
12
  import { managementPageURL, showingManagementURL } from "../../diagnostics/managementPages";
13
- import { filterURL } from "../../diagnostics/logs/DiskLoggerPage";
14
13
  import { watchShowLocalPaths, watchValueType } from "./LocalWatchViewer";
15
14
  import { filtersURL, viewMode } from "./archiveViewerShared";
16
15
  import { proxyWatcher } from "../../2-proxy/PathValueProxyWatcher";
@@ -6,6 +6,11 @@ import { nodeDiscoveryShutdown } from "../-f-node-discovery/NodeDiscovery";
6
6
  import { authorityStorage } from "../0-path-value-core/pathValueCore";
7
7
  import { red } from "socket-function/src/formatting/logColors";
8
8
 
9
+ // Import querysub, so all the hooks we use will exist at some point
10
+ setImmediate(async () => {
11
+ await import("../4-querysub/QuerysubController");
12
+ });
13
+
9
14
  let periodicFncs: (() => void)[] = [];
10
15
  export function registerPeriodic(fnc: () => void) {
11
16
  periodicFncs.push(fnc);
@@ -6,10 +6,10 @@ import debugbreak from "debugbreak";
6
6
  import { registerPeriodic } from "./periodic";
7
7
  import { getOwnMachineId } from "../-a-auth/certs";
8
8
  import { SocketFunction } from "socket-function/SocketFunction";
9
- import { logDisk } from "./logs/diskLogger";
10
9
  import { formatPercent, formatTime } from "socket-function/src/formatting/format";
11
10
  import { pathWatcher } from "../0-path-value-core/pathValueCore";
12
11
  import { addStatPeriodic, addStatSumPeriodic, addTimeProfileDistribution, registerNodeMetadata } from "../-0-hooks/hooks";
12
+ import { logDisk } from "./logs/diskLogger";
13
13
 
14
14
  let lastProfile: MeasureProfile | undefined;
15
15
 
@@ -72,7 +72,7 @@ function logProfileMeasuresTimingsNow() {
72
72
  measureObj = startMeasure();
73
73
  function diskLogMeasureObj(table: FormattedMeasureTable | undefined) {
74
74
  if (!table) return;
75
- logDisk(table.title, table.entries);
75
+ logDisk("log", table.title, { entries: table.entries });
76
76
  }
77
77
  diskLogMeasureObj(logMeasureTable(profile, {
78
78
  name: `watchdog at ${new Date().toLocaleString()}`,
@@ -0,0 +1,67 @@
1
+ import { SocketFunction } from "socket-function/SocketFunction";
2
+ import { lazy } from "socket-function/src/caching";
3
+ import { getAllNodeIds, watchNodeIds } from "../-f-node-discovery/NodeDiscovery";
4
+ import { requiresNetworkTrustHook } from "../-d-trust/NetworkTrust2";
5
+ import { errorToUndefinedSilent } from "../errors";
6
+ import { assertIsManagementUser } from "../diagnostics/managementPages";
7
+
8
+ export class SocketChannel<T> {
9
+ constructor(private globalUniqueChannelName: string) { }
10
+ private controller = SocketFunction.register(
11
+ this.globalUniqueChannelName,
12
+ // Why do we have to cast here? Odd...
13
+ () => this as {
14
+ _internal_onMessage: (message: T) => Promise<void>;
15
+ _internal_watchMessages: () => Promise<void>;
16
+ },
17
+ () => ({
18
+ _internal_onMessage: { hooks: [assertIsManagementUser] },
19
+ _internal_watchMessages: { hooks: [assertIsManagementUser] },
20
+ }),
21
+ );
22
+
23
+ private localWatchers = new Set<(message: T) => void>();
24
+ // Has to be public for SocketFunction, don't call this externally
25
+ public async _internal_onMessage(message: T) {
26
+ for (let watcher of this.localWatchers) {
27
+ // Ignore errors (let the unhandled promise rejection handler log them)
28
+ void (async () => {
29
+ await watcher(message);
30
+ })();
31
+ }
32
+ }
33
+
34
+ private remoteWatchers = new Set<string>();
35
+ public async _internal_watchMessages() {
36
+ let caller = SocketFunction.getCaller();
37
+ this.remoteWatchers.add(caller.nodeId);
38
+ SocketFunction.onNextDisconnect(caller.nodeId, () => {
39
+ this.remoteWatchers.delete(caller.nodeId);
40
+ });
41
+ }
42
+
43
+ public broadcast(message: T) {
44
+ void Array.from(this.remoteWatchers).map(async (nodeId) => {
45
+ try {
46
+ await this.controller.nodes[nodeId]._internal_onMessage(message);
47
+ } catch {
48
+ this.remoteWatchers.delete(nodeId);
49
+ }
50
+ });
51
+ }
52
+
53
+ private watchAllNodes = lazy(async () => {
54
+ watchNodeIds((nodeIds) => {
55
+ for (let nodeId of nodeIds) {
56
+ void errorToUndefinedSilent(this.controller.nodes[nodeId]._internal_watchMessages());
57
+ }
58
+ });
59
+ });
60
+ public watch(callback: (message: T) => void) {
61
+ void this.watchAllNodes();
62
+ this.localWatchers.add(callback);
63
+ return () => {
64
+ this.localWatchers.delete(callback);
65
+ };
66
+ }
67
+ }
@@ -27,7 +27,7 @@ export type InputProps = (
27
27
  noEnterKeyCommit?: boolean;
28
28
  noFocusSelect?: boolean;
29
29
  inputKey?: string;
30
- fillWidth?: boolean;
30
+ fillWidth?: boolean | number;
31
31
 
32
32
  /** By default, we don't accept value updates when focused. This prevents the user's
33
33
  * typing from being interrupted by a value update. However, this flag will
@@ -27,7 +27,7 @@ export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
27
27
 
28
28
  tooltip?: string;
29
29
 
30
- fillWidth?: boolean;
30
+ fillWidth?: boolean | number;
31
31
  };
32
32
 
33
33
  function roundToDecimals(value: number, decimals: number) {
@@ -152,7 +152,10 @@ export class InputLabel extends qreact.Component<InputLabelProps> {
152
152
  + " trigger-hover "
153
153
  + props.outerClass
154
154
  + (props.flavor === "large" && css.fontSize(18, "soft"))
155
- + (props.fillWidth && css.fillWidth)
155
+ + (props.fillWidth &&
156
+ css.flexGrow(typeof props.fillWidth === "number" ? props.fillWidth : 1)
157
+ .alignSelf("stretch")
158
+ )
156
159
  }>
157
160
  <div
158
161
  class={
package/src/misc.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { canHaveChildren } from "socket-function/src/types";
2
+
1
3
  // TIMING: About 20MB/s
2
4
  export function createRandomText(count: number): string {
3
5
  let text = "";
@@ -61,4 +63,113 @@ export function mostCommon<T>(array: T[]): T | undefined {
61
63
  counts.set(item, (counts.get(item) || 0) + 1);
62
64
  }
63
65
  return Array.from(counts.entries()).sort((a, b) => b[1] - a[1])[0]?.[0];
66
+ }
67
+
68
+
69
+ export function parsePath(path: string): { dir: string; name: string } {
70
+ path = path.replaceAll("\\", "/");
71
+ let lastSlash = path.lastIndexOf("/");
72
+ if (lastSlash === -1) return { dir: "", name: path };
73
+ return { dir: path.slice(0, lastSlash + 1), name: path.slice(lastSlash + 1) };
74
+ }
75
+
76
+ // Loses spaces in keys and values
77
+ export function toFileNameKVP(kvp: { [key: string]: string }): string {
78
+ function s(v: string) {
79
+ return v.replaceAll(" ", "_");
80
+ }
81
+ return " " + Object.entries(kvp).map(([key, value]) => `${s(key)}=${s(value)}`).join(" ") + " ";
82
+ }
83
+ export function parseFileNameKVP(fileName: string): { [key: string]: string } {
84
+ let parts = fileName.trim().split(" ");
85
+ let obj: { [key: string]: string } = {};
86
+ for (let part of parts) {
87
+ let [key, value] = part.split("=");
88
+ obj[key] = value || key;
89
+ }
90
+ return obj;
91
+ }
92
+
93
+ /** Copied object until maxFields is reached. */
94
+ export function partialCopyObject(data: unknown, maxFields: number = 500): unknown {
95
+ try {
96
+ let fieldCount = 0;
97
+ const seen = new WeakSet();
98
+
99
+ function copy(value: unknown): unknown {
100
+ // Handle primitives
101
+ if (!canHaveChildren(value)) {
102
+ return value;
103
+ }
104
+
105
+ // Check for circular references
106
+ if (seen.has(value as object)) {
107
+ return null;
108
+ }
109
+ seen.add(value as object);
110
+
111
+ // Check if we've hit the field limit
112
+ if (fieldCount >= maxFields) {
113
+ return null;
114
+ }
115
+
116
+ if (Array.isArray(value)) {
117
+ const result: unknown[] = [];
118
+ for (const item of value) {
119
+ fieldCount++;
120
+ if (fieldCount >= maxFields) break;
121
+ result.push(copy(item));
122
+ }
123
+ seen.delete(value);
124
+ return result;
125
+ }
126
+
127
+ // Handle plain objects
128
+ const result: Record<string, unknown> = {};
129
+ for (const key of Object.keys(value as object)) {
130
+ fieldCount++;
131
+ if (fieldCount >= maxFields) break;
132
+ result[key] = copy((value as Record<string, unknown>)[key]);
133
+ }
134
+ seen.delete(value);
135
+ return result;
136
+ }
137
+
138
+ return copy(data) as any;
139
+ } catch (e: any) {
140
+ return { errorCopying: e.messsage } as any;
141
+ }
142
+ }
143
+
144
+ export function sum(array: number[]): number {
145
+ return array.reduce((a, b) => a + b, 0);
146
+ }
147
+
148
+ export function matchFilter(filter: { value: string }, value: string) {
149
+ let filterValue = filter.value.toLowerCase().trim();
150
+ if (!filterValue) return true;
151
+ value = value.toLowerCase().trim();
152
+ return filterValue.split("|").some(part =>
153
+ part.split("&").every(part =>
154
+ value.includes(part.trim())
155
+ )
156
+ );
157
+ }
158
+ export const applyFilter = matchFilter;
159
+
160
+
161
+ export function streamToIteratable<T>(reader: {
162
+ read(): Promise<{ value: T; done: false } | { done: true; value?: T }>;
163
+ }): AsyncIterable<T> {
164
+ return {
165
+ [Symbol.asyncIterator]: async function* () {
166
+ while (true) {
167
+ const { done, value } = await reader.read();
168
+ if (done) {
169
+ break;
170
+ }
171
+ yield value;
172
+ }
173
+ }
174
+ };
64
175
  }
package/src/src.d.ts CHANGED
@@ -1 +1,34 @@
1
- /// <reference path="./-a-auth/node-forge-ed25519.d.ts" />
1
+ /// <reference path="./-a-auth/node-forge-ed25519.d.ts" />
2
+
3
+ interface FileSystemDirectoryHandle {
4
+ [Symbol.asyncIterator](): AsyncIterator<[string, FileSystemFileHandle | FileSystemDirectoryHandle]>;
5
+ requestPermission(config: { mode: "read" | "readwrite" }): Promise<void>;
6
+ }
7
+
8
+ interface FileSystemFileHandle {
9
+ getFile(): File;
10
+ createWritable(): FileSystemWritableFileStream;
11
+ createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
12
+ }
13
+
14
+ interface FileSystemSyncAccessHandle {
15
+ read(buffer: ArrayBuffer | ArrayBufferView, options?: { at: number }): number;
16
+ write(buffer: ArrayBuffer | ArrayBufferView, options?: { at: number }): number;
17
+ truncate(newSize: number): void;
18
+ flush(): void;
19
+ close(): void;
20
+ }
21
+
22
+ interface Window {
23
+ showSaveFilePicker(config?: {
24
+ types: {
25
+ description: string; accept: { [mimeType: string]: string[] }
26
+ }[];
27
+ }): Promise<FileSystemFileHandle>;
28
+ showDirectoryPicker(): Promise<FileSystemDirectoryHandle>;
29
+ showOpenFilePicker(config?: {
30
+ types: {
31
+ description: string; accept: { [mimeType: string]: string[] }
32
+ }[];
33
+ }): Promise<FileSystemFileHandle[]>;
34
+ }
@@ -16,6 +16,7 @@ import { delay } from "socket-function/src/batching";
16
16
  import { enableErrorNotifications } from "../library-components/errorNotifications";
17
17
  import { clamp } from "../misc";
18
18
  import { sha256 } from "js-sha256";
19
+ import { logDisk } from "../diagnostics/logs/diskLogger";
19
20
 
20
21
  /*
21
22
  IMPORTANT!
@@ -606,7 +607,7 @@ function sendLoginEmail(config: {
606
607
  );
607
608
  if (alreadyAllowed) {
608
609
  data().machineSecure[machineId].userId = userId;
609
- console.info(`User ${userId} already allowed for current ip and machine id, no need to send email.`);
610
+ logDisk("log", `User ${userId} already allowed for current ip and machine id, no need to send email.`);
610
611
  return;
611
612
  }
612
613
  }
@@ -780,14 +781,14 @@ function inviteUser(config: { email: string }) {
780
781
  Querysub.ignorePermissionsChecks(() => {
781
782
  let curUserObj = getUserObjAssert();
782
783
  if (config.email in curUserObj.invitedUsers2) {
783
- console.info(`User ${config.email} already invited`);
784
+ logDisk("log", `User ${config.email} already invited`);
784
785
  return;
785
786
  }
786
787
  // If the user already exists, don't invite
787
788
  const { email } = config;
788
789
  let userId = atomic(data().secure.emailToUserId[email]) || createNewUserId();
789
790
  if (userId in data().users) {
790
- console.info(`User ${userId} already exists, no need to invite`);
791
+ logDisk("log", `User ${userId} already exists, no need to invite`);
791
792
  return;
792
793
  }
793
794
 
package/test.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { Querysub } from "./src/4-querysub/QuerysubController";
2
+ // Import querysub, so inject is run
3
+ Querysub;
4
+ import { formatTime } from "socket-function/src/formatting/format";
5
+ import { getMachineId, getOwnMachineId } from "./src/-a-auth/certs";
6
+ import { testLogs } from "./src/diagnostics/logs/LogViewer2";
7
+ import { shutdown } from "./src/diagnostics/periodic";
8
+ import crypto from "crypto";
9
+ import { testMain } from "./testEntry2";
10
+
11
+ testMain().catch(console.error).finally(() => {
12
+ process.exit(0);
13
+ });
package/testEntry2.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { delay } from "socket-function/src/batching";
2
+ import { getOwnMachineId } from "./src/-a-auth/certs";
3
+ import { getOwnThreadId } from "./src/-f-node-discovery/NodeDiscovery";
4
+ import { shutdown } from "./src/diagnostics/periodic";
5
+
6
+ export async function testMain() {
7
+ await delay(0);
8
+ console.log(getOwnThreadId());
9
+ console.error(`Test warning for ErrorWarning testing`);
10
+ console.log(getOwnThreadId());
11
+ await shutdown();
12
+ //await Querysub.hostService("test");
13
+ // for (let i = 0; i < 1000 * 10; i++) {
14
+ // for (let j = 0; j < 1000; j++) {
15
+ // testLogs.append({
16
+ // time: Date.now(),
17
+ // message: `Test message ${i}`,
18
+ // randomString: crypto.randomBytes(16).toString(),
19
+ // randomValue: Math.random(),
20
+ // });
21
+ // }
22
+ // await testLogs.flushNow();
23
+ // }
24
+ // await shutdown();
25
+ }
26
+ async function main() {
27
+
28
+ }
29
+