querysub 0.326.0 → 0.328.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.
- package/package.json +3 -4
- package/src/-a-archives/archivesBackBlaze.ts +20 -0
- package/src/-a-archives/archivesDisk.ts +5 -5
- package/src/-a-archives/archivesLimitedCache.ts +118 -7
- package/src/-a-archives/archivesPrivateFileSystem.ts +3 -0
- package/src/-g-core-values/NodeCapabilities.ts +26 -11
- package/src/0-path-value-core/auditLogs.ts +4 -2
- package/src/2-proxy/PathValueProxyWatcher.ts +3 -0
- package/src/3-path-functions/PathFunctionRunner.ts +2 -2
- package/src/4-querysub/Querysub.ts +1 -1
- package/src/5-diagnostics/GenericFormat.tsx +2 -2
- package/src/deployManager/machineApplyMainCode.ts +10 -8
- package/src/deployManager/machineSchema.ts +4 -3
- package/src/deployManager/setupMachineMain.ts +3 -2
- package/src/diagnostics/logs/FastArchiveAppendable.ts +85 -59
- package/src/diagnostics/logs/FastArchiveController.ts +5 -2
- package/src/diagnostics/logs/FastArchiveViewer.tsx +222 -51
- package/src/diagnostics/logs/LogViewer2.tsx +83 -35
- package/src/diagnostics/logs/TimeRangeSelector.tsx +8 -0
- package/src/diagnostics/logs/diskLogGlobalContext.ts +3 -3
- package/src/diagnostics/logs/diskLogger.ts +70 -23
- package/src/diagnostics/logs/errorNotifications/ErrorNotificationController.ts +111 -82
- package/src/diagnostics/logs/errorNotifications/ErrorSuppressionUI.tsx +37 -3
- package/src/diagnostics/logs/errorNotifications/ErrorWarning.tsx +52 -22
- package/src/diagnostics/logs/errorNotifications/errorDigests.tsx +8 -0
- package/src/diagnostics/logs/errorNotifications/errorWatchEntry.tsx +198 -52
- package/src/diagnostics/logs/lifeCycleAnalysis/spec.md +3 -2
- package/src/diagnostics/managementPages.tsx +5 -0
- package/src/email_ims_notifications/discord.tsx +203 -0
- package/src/fs.ts +9 -0
- package/src/functional/SocketChannel.ts +9 -0
- package/src/functional/throttleRender.ts +134 -0
- package/src/library-components/ATag.tsx +2 -2
- package/src/library-components/SyncedController.ts +5 -3
- package/src/misc.ts +13 -0
- package/src/misc2.ts +54 -0
- package/src/user-implementation/SecurityPage.tsx +11 -5
- package/src/user-implementation/userData.ts +31 -16
- package/testEntry2.ts +14 -5
- package/src/user-implementation/setEmailKey.ts +0 -25
- /package/src/{email → email_ims_notifications}/postmark.tsx +0 -0
- /package/src/{email → email_ims_notifications}/sendgrid.tsx +0 -0
|
@@ -3,6 +3,7 @@ import { css, isNode } from "typesafecss";
|
|
|
3
3
|
import { URLParam, parseSearchString, encodeSearchString } from "./URLParam";
|
|
4
4
|
import { qreact } from "../4-dom/qreact";
|
|
5
5
|
import { niceStringify } from "../niceStringify";
|
|
6
|
+
import { getDomain } from "../config";
|
|
6
7
|
|
|
7
8
|
export type URLOverride<T = unknown> = {
|
|
8
9
|
param: URLParam<T>;
|
|
@@ -118,8 +119,7 @@ export const Anchor = ATag;
|
|
|
118
119
|
export const Link = ATag;
|
|
119
120
|
|
|
120
121
|
export function createLink(values: URLOverride[]) {
|
|
121
|
-
|
|
122
|
-
let urlObj = new URL(document.location.href);
|
|
122
|
+
let urlObj = new URL(isNode() ? "https://" + getDomain() : document.location.href);
|
|
123
123
|
let params = parseSearchString(urlObj.search);
|
|
124
124
|
for (let value of values) {
|
|
125
125
|
params[value.param.urlKey] = value.value;
|
|
@@ -318,9 +318,11 @@ export function getSyncedController<T extends SocketRegistered>(
|
|
|
318
318
|
}
|
|
319
319
|
// Don't cache promise calls
|
|
320
320
|
void promise.finally(() => {
|
|
321
|
-
|
|
322
|
-
obj.promise
|
|
323
|
-
|
|
321
|
+
Querysub.fastRead(() => {
|
|
322
|
+
if (obj.promise === promise) {
|
|
323
|
+
obj.promise = undefined;
|
|
324
|
+
}
|
|
325
|
+
});
|
|
324
326
|
});
|
|
325
327
|
return promise;
|
|
326
328
|
});
|
package/src/misc.ts
CHANGED
|
@@ -126,6 +126,19 @@ export function partialCopyObject(data: unknown, maxFields: number = 500): unkno
|
|
|
126
126
|
|
|
127
127
|
// Handle plain objects
|
|
128
128
|
const result: Record<string, unknown> = {};
|
|
129
|
+
|
|
130
|
+
// Special case for Error objects - include stack and message
|
|
131
|
+
if (value instanceof Error) {
|
|
132
|
+
fieldCount++;
|
|
133
|
+
if (fieldCount < maxFields) {
|
|
134
|
+
result.message = value.message;
|
|
135
|
+
}
|
|
136
|
+
fieldCount++;
|
|
137
|
+
if (fieldCount < maxFields) {
|
|
138
|
+
result.stack = value.stack;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
129
142
|
for (const key of Object.keys(value as object)) {
|
|
130
143
|
fieldCount++;
|
|
131
144
|
if (fieldCount >= maxFields) break;
|
package/src/misc2.ts
CHANGED
|
@@ -2,4 +2,58 @@ import { atomic } from "./2-proxy/PathValueProxyWatcher";
|
|
|
2
2
|
|
|
3
3
|
export function isStrSimilar(a: string | undefined, b: string | undefined) {
|
|
4
4
|
return atomic(a)?.toLowerCase().trim().replaceAll(" ", "") === atomic(b)?.toLowerCase().trim().replaceAll(" ", "");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface StreamLike {
|
|
8
|
+
on(event: "data", listener: (chunk: Buffer) => void): void;
|
|
9
|
+
on(event: "end", listener: () => void): void;
|
|
10
|
+
on(event: "error", listener: (error: Error) => void): void;
|
|
11
|
+
}
|
|
12
|
+
export async function* streamToAsyncIterable(stream: StreamLike): AsyncIterable<Buffer> {
|
|
13
|
+
let pendingChunks: Buffer[] = [];
|
|
14
|
+
let streamEnded = false;
|
|
15
|
+
let streamError: Error | undefined = undefined;
|
|
16
|
+
let resolveNext: (() => void) | undefined = undefined;
|
|
17
|
+
|
|
18
|
+
stream.on("data", (chunk: Buffer) => {
|
|
19
|
+
pendingChunks.push(chunk);
|
|
20
|
+
if (resolveNext) {
|
|
21
|
+
resolveNext();
|
|
22
|
+
resolveNext = undefined;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
stream.on("end", () => {
|
|
27
|
+
streamEnded = true;
|
|
28
|
+
if (resolveNext) {
|
|
29
|
+
resolveNext();
|
|
30
|
+
resolveNext = undefined;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
stream.on("error", (error) => {
|
|
35
|
+
streamError = error;
|
|
36
|
+
if (resolveNext) {
|
|
37
|
+
resolveNext();
|
|
38
|
+
resolveNext = undefined;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
while (!streamEnded && !streamError) {
|
|
43
|
+
if (pendingChunks.length > 0) {
|
|
44
|
+
yield pendingChunks.shift()!;
|
|
45
|
+
} else {
|
|
46
|
+
await new Promise<void>((resolve) => {
|
|
47
|
+
resolveNext = resolve;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (streamError) {
|
|
53
|
+
throw streamError;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
while (pendingChunks.length > 0) {
|
|
57
|
+
yield pendingChunks.shift()!;
|
|
58
|
+
}
|
|
5
59
|
}
|
|
@@ -13,7 +13,7 @@ import { redButton } from "../library-components/colors";
|
|
|
13
13
|
|
|
14
14
|
export class SecurityPage extends qreact.Component {
|
|
15
15
|
render() {
|
|
16
|
-
return <div class={css.pad(10)}>
|
|
16
|
+
return <div class={css.pad(10).fillWidth}>
|
|
17
17
|
<TabbedUI
|
|
18
18
|
tabs={[
|
|
19
19
|
{ value: "config", title: "Config", contents: <ConfigTab /> },
|
|
@@ -49,13 +49,19 @@ class ConfigTab extends qreact.Component {
|
|
|
49
49
|
<InputLabel
|
|
50
50
|
label={"Postmark API Key"}
|
|
51
51
|
edit
|
|
52
|
-
|
|
52
|
+
fillWidth
|
|
53
53
|
value={user_data().secure.postmarkAPIKey}
|
|
54
54
|
onChangeValue={value => user_functions.setPostmarkAPIKey({ apiKey: value })}
|
|
55
55
|
/>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
<InputLabel
|
|
57
|
+
label={"Discord Webhook URL"}
|
|
58
|
+
edit
|
|
59
|
+
value={user_data().secure.notifyDiscordWebhookURL}
|
|
60
|
+
onChangeValue={value => user_functions.setNotifyDiscordWebhookURL({ webhookURL: value })}
|
|
61
|
+
/>
|
|
62
|
+
<Button onClick={() => user_functions.testSendDiscordMessage()}>
|
|
63
|
+
Test Discord Message
|
|
64
|
+
</Button>
|
|
59
65
|
</>
|
|
60
66
|
);
|
|
61
67
|
}
|
|
@@ -6,7 +6,7 @@ import { registerAliveChecker } from "../2-proxy/garbageCollection";
|
|
|
6
6
|
import { generateLoginEmail } from "./loginEmail";
|
|
7
7
|
import { logErrors } from "../errors";
|
|
8
8
|
import { PermissionsParameters } from "../3-path-functions/syncSchema";
|
|
9
|
-
import { sendEmail_postmark } from "../
|
|
9
|
+
import { sendEmail_postmark } from "../email_ims_notifications/postmark";
|
|
10
10
|
import { isClient } from "../config2";
|
|
11
11
|
import { getExternalIP } from "../misc/networking";
|
|
12
12
|
import { MAX_ACCEPTED_CHANGE_AGE } from "../0-path-value-core/pathValueCore";
|
|
@@ -17,6 +17,10 @@ import { enableErrorNotifications } from "../library-components/errorNotificatio
|
|
|
17
17
|
import { clamp } from "../misc";
|
|
18
18
|
import { sha256 } from "js-sha256";
|
|
19
19
|
import { logDisk } from "../diagnostics/logs/diskLogger";
|
|
20
|
+
import { createLink } from "../library-components/ATag";
|
|
21
|
+
import { showingManagementURL, managementPageURL } from "../diagnostics/managementPages";
|
|
22
|
+
import { sendDiscordMessage } from "../email_ims_notifications/discord";
|
|
23
|
+
import { formatDateTime } from "socket-function/src/formatting/format";
|
|
20
24
|
|
|
21
25
|
/*
|
|
22
26
|
IMPORTANT!
|
|
@@ -161,6 +165,7 @@ const { data, functions } = Querysub.syncSchema<{
|
|
|
161
165
|
secure: {
|
|
162
166
|
signupsOpen?: boolean;
|
|
163
167
|
postmarkAPIKey?: string;
|
|
168
|
+
notifyDiscordWebhookURL?: string;
|
|
164
169
|
|
|
165
170
|
// NOTE: For now these will only be blocked at a user level. If we run into issues, we can
|
|
166
171
|
// make Querysub have configurable IP blocking that accepts a synced read function, which
|
|
@@ -217,18 +222,25 @@ const { data, functions } = Querysub.syncSchema<{
|
|
|
217
222
|
specialBlockIP, specialUnblockIP,
|
|
218
223
|
specialSetInviteCount,
|
|
219
224
|
setPostmarkAPIKey,
|
|
225
|
+
setNotifyDiscordWebhookURL,
|
|
220
226
|
specialSetUserType,
|
|
221
227
|
registerPageLoadTime,
|
|
222
228
|
banUser, unbanUser,
|
|
223
|
-
|
|
229
|
+
testSendDiscordMessage: () => {
|
|
224
230
|
assertUserType("superuser");
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
data().users[
|
|
231
|
-
|
|
231
|
+
const webhookURL = atomic(data().secure.notifyDiscordWebhookURL);
|
|
232
|
+
if (!webhookURL) {
|
|
233
|
+
throw new Error("No Discord webhook URL set");
|
|
234
|
+
}
|
|
235
|
+
let callerUserId = getCurrentUserAssert();
|
|
236
|
+
let email = data().users[callerUserId].email;
|
|
237
|
+
Querysub.onCommitFinished(async () => {
|
|
238
|
+
await sendDiscordMessage({
|
|
239
|
+
webhookURL,
|
|
240
|
+
message: `Discord webhook test at ${formatDateTime(Date.now())} by ${email} (${JSON.stringify(callerUserId)})`,
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
},
|
|
232
244
|
},
|
|
233
245
|
module,
|
|
234
246
|
moduleId: "userData2",
|
|
@@ -269,11 +281,13 @@ const { data, functions } = Querysub.syncSchema<{
|
|
|
269
281
|
},
|
|
270
282
|
},
|
|
271
283
|
functionMetadata: {
|
|
284
|
+
// There's no point to predict sending emails, or inviting users, or registering the page load time or sending discord messages, As the main function of these can only be done server-side.
|
|
285
|
+
// verifyMachineId is used when logging in with the login token. We could predict it, but there's no real reason to because if we do, all of our reads are gonna fail until it runs, Which will actually be really confusing to the user and look like a bug.
|
|
272
286
|
sendLoginEmail: { nopredict: true },
|
|
273
287
|
verifyMachineId: { nopredict: true },
|
|
274
288
|
inviteUser: { nopredict: true },
|
|
275
|
-
setPostmarkAPIKey: { nopredict: true },
|
|
276
289
|
registerPageLoadTime: { nopredict: true },
|
|
290
|
+
testSendDiscordMessage: { nopredict: true },
|
|
277
291
|
},
|
|
278
292
|
});
|
|
279
293
|
|
|
@@ -595,7 +609,11 @@ function sendLoginEmail(config: {
|
|
|
595
609
|
|
|
596
610
|
const apiKey = atomic(data().secure.postmarkAPIKey);
|
|
597
611
|
if (!apiKey) {
|
|
598
|
-
|
|
612
|
+
let link = createLink([
|
|
613
|
+
showingManagementURL.getOverride(true),
|
|
614
|
+
managementPageURL.getOverride("SecurityPage"),
|
|
615
|
+
]);
|
|
616
|
+
throw new Error(`No postmark API key, so we can't send login emails. Set the key at ${link}.`);
|
|
599
617
|
}
|
|
600
618
|
|
|
601
619
|
{
|
|
@@ -938,11 +956,8 @@ export function scriptCreateUser(config: {
|
|
|
938
956
|
function setPostmarkAPIKey(config: { apiKey: string; }) {
|
|
939
957
|
data().secure.postmarkAPIKey = config.apiKey;
|
|
940
958
|
}
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
apiKey: string;
|
|
944
|
-
}) {
|
|
945
|
-
setPostmarkAPIKey(config);
|
|
959
|
+
function setNotifyDiscordWebhookURL(config: { webhookURL: string; }) {
|
|
960
|
+
data().secure.notifyDiscordWebhookURL = config.webhookURL;
|
|
946
961
|
}
|
|
947
962
|
|
|
948
963
|
|
package/testEntry2.ts
CHANGED
|
@@ -5,21 +5,34 @@ import { shutdown } from "./src/diagnostics/periodic";
|
|
|
5
5
|
import { testTCPIsListening } from "socket-function/src/networking";
|
|
6
6
|
import { Querysub } from "./src/4-querysub/QuerysubController";
|
|
7
7
|
import { timeInSecond } from "socket-function/src/misc";
|
|
8
|
+
import { LOG_LINE_LIMIT_ID } from "./src/diagnostics/logs/diskLogger";
|
|
9
|
+
import { waitForFirstTimeSync } from "socket-function/time/trueTimeShim";
|
|
8
10
|
|
|
9
11
|
export async function testMain() {
|
|
10
12
|
Querysub;
|
|
13
|
+
await waitForFirstTimeSync();
|
|
14
|
+
|
|
11
15
|
//let test = await testTCPIsListening("1.1.1.1", 443);
|
|
12
16
|
//console.log(test);
|
|
13
17
|
// Writing heartbeat 2025/09/14 08:37:46 PM for self (5ac8a2fa78fce4ea.971ed8b01743d123.querysubtest.com:13900)
|
|
14
18
|
await delay(timeInSecond);
|
|
15
19
|
await Querysub.hostService("test");
|
|
16
20
|
await delay(timeInSecond * 5);
|
|
21
|
+
|
|
22
|
+
console.error(`This should show up locally, but not remotely.`);
|
|
23
|
+
await delay(timeInSecond * 15);
|
|
24
|
+
await shutdown();
|
|
25
|
+
return;
|
|
26
|
+
|
|
27
|
+
|
|
17
28
|
// console.log(getOwnThreadId());
|
|
18
29
|
// Log an error every 30 seconds forever.
|
|
19
30
|
while (true) {
|
|
20
31
|
console.error(`Test warning for im testing ${Date.now()}`);
|
|
21
32
|
await delay(timeInSecond * 30);
|
|
22
33
|
}
|
|
34
|
+
|
|
35
|
+
|
|
23
36
|
// console.log(getOwnThreadId());
|
|
24
37
|
// await shutdown();
|
|
25
38
|
//await Querysub.hostService("test");
|
|
@@ -36,8 +49,4 @@ export async function testMain() {
|
|
|
36
49
|
// }
|
|
37
50
|
await delay(timeInSecond * 15);
|
|
38
51
|
await shutdown();
|
|
39
|
-
}
|
|
40
|
-
async function main() {
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
52
|
+
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Querysub } from "../4-querysub/Querysub";
|
|
2
|
-
import { pathValueCommitter } from "../0-path-value-core/PathValueCommitter";
|
|
3
|
-
import { scriptSetPostmarkAPIKey } from "./userData";
|
|
4
|
-
import { isNodeTrue } from "socket-function/src/misc";
|
|
5
|
-
import yargs from "yargs";
|
|
6
|
-
|
|
7
|
-
let yargObj = isNodeTrue() && yargs(process.argv)
|
|
8
|
-
.option("key", { type: "string", desc: `The key to set for the email.` })
|
|
9
|
-
.argv || {}
|
|
10
|
-
;
|
|
11
|
-
|
|
12
|
-
async function main() {
|
|
13
|
-
const howToCall = ` Call with yarn setemailkey --key <key>`;
|
|
14
|
-
const key = yargObj.key;
|
|
15
|
-
if (!key) throw new Error("No key provided." + howToCall);
|
|
16
|
-
|
|
17
|
-
await Querysub.hostService("setEmailKey");
|
|
18
|
-
|
|
19
|
-
await Querysub.commitSynced(() => {
|
|
20
|
-
scriptSetPostmarkAPIKey({ apiKey: key });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
await pathValueCommitter.waitForValuesToCommit();
|
|
24
|
-
}
|
|
25
|
-
main().catch(e => console.log(e)).finally(() => process.exit());
|
|
File without changes
|
|
File without changes
|