@brass-build/client 0.4.0 → 0.5.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/AGENTS.md +142 -41
- package/CHANGELOG.md +93 -4
- package/README.md +666 -179
- package/dist/acting-organization.d.ts.map +1 -1
- package/dist/acting-organization.js +6 -11
- package/dist/acting-organization.js.map +1 -1
- package/dist/api-fetch.d.ts +8 -0
- package/dist/api-fetch.d.ts.map +1 -1
- package/dist/api-fetch.js +35 -8
- package/dist/api-fetch.js.map +1 -1
- package/dist/batched-get.d.ts +11 -0
- package/dist/batched-get.d.ts.map +1 -0
- package/dist/batched-get.js +92 -0
- package/dist/batched-get.js.map +1 -0
- package/dist/call-service.d.ts +4 -0
- package/dist/call-service.d.ts.map +1 -1
- package/dist/call-service.js +66 -3
- package/dist/call-service.js.map +1 -1
- package/dist/client.d.ts +51 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +982 -124
- package/dist/client.js.map +1 -1
- package/dist/document-checkpoints.d.ts +25 -2
- package/dist/document-checkpoints.d.ts.map +1 -1
- package/dist/document-checkpoints.js +258 -61
- package/dist/document-checkpoints.js.map +1 -1
- package/dist/document-commands.d.ts.map +1 -1
- package/dist/document-commands.js +5 -3
- package/dist/document-commands.js.map +1 -1
- package/dist/document-contents.d.ts +10 -0
- package/dist/document-contents.d.ts.map +1 -1
- package/dist/document-contents.js +23 -1
- package/dist/document-contents.js.map +1 -1
- package/dist/document-set-subscription.d.ts +56 -0
- package/dist/document-set-subscription.d.ts.map +1 -0
- package/dist/document-set-subscription.js +240 -0
- package/dist/document-set-subscription.js.map +1 -0
- package/dist/document-signals.d.ts +6 -0
- package/dist/document-signals.d.ts.map +1 -1
- package/dist/document-signals.js +31 -1
- package/dist/document-signals.js.map +1 -1
- package/dist/document-streams.d.ts +36 -2
- package/dist/document-streams.d.ts.map +1 -1
- package/dist/document-streams.js +69 -18
- package/dist/document-streams.js.map +1 -1
- package/dist/document-thumbnail-capture.d.ts +12 -2
- package/dist/document-thumbnail-capture.d.ts.map +1 -1
- package/dist/document-thumbnail-capture.js +103 -27
- package/dist/document-thumbnail-capture.js.map +1 -1
- package/dist/documents-namespace.d.ts +92 -10
- package/dist/documents-namespace.d.ts.map +1 -1
- package/dist/documents-namespace.js +285 -39
- package/dist/documents-namespace.js.map +1 -1
- package/dist/documents.d.ts +173 -8
- package/dist/documents.d.ts.map +1 -1
- package/dist/documents.js +1778 -310
- package/dist/documents.js.map +1 -1
- package/dist/errors.d.ts +3 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/fetch-retry.d.ts +1 -0
- package/dist/fetch-retry.d.ts.map +1 -1
- package/dist/fetch-retry.js +2 -0
- package/dist/fetch-retry.js.map +1 -1
- package/dist/file-choice.d.ts +2 -0
- package/dist/file-choice.d.ts.map +1 -0
- package/dist/file-choice.js +27 -0
- package/dist/file-choice.js.map +1 -0
- package/dist/guest-link.d.ts +18 -1
- package/dist/guest-link.d.ts.map +1 -1
- package/dist/guest-link.js +329 -1
- package/dist/guest-link.js.map +1 -1
- package/dist/handle-types.d.ts +67 -5
- package/dist/handle-types.d.ts.map +1 -1
- package/dist/handles.d.ts +15 -19
- package/dist/handles.d.ts.map +1 -1
- package/dist/handles.js +231 -76
- package/dist/handles.js.map +1 -1
- package/dist/hosted-ui.d.ts +61 -0
- package/dist/hosted-ui.d.ts.map +1 -1
- package/dist/hosted-ui.js +94 -42
- package/dist/hosted-ui.js.map +1 -1
- package/dist/import-contract.d.ts +74 -0
- package/dist/import-contract.d.ts.map +1 -0
- package/dist/import-contract.js +163 -0
- package/dist/import-contract.js.map +1 -0
- package/dist/import-guest.d.ts +9 -0
- package/dist/import-guest.d.ts.map +1 -0
- package/dist/import-guest.js +155 -0
- package/dist/import-guest.js.map +1 -0
- package/dist/import-report.d.ts +17 -0
- package/dist/import-report.d.ts.map +1 -0
- package/dist/import-report.js +7 -0
- package/dist/import-report.js.map +1 -0
- package/dist/importer-wait.d.ts +2 -0
- package/dist/importer-wait.d.ts.map +1 -0
- package/dist/importer-wait.js +5 -0
- package/dist/importer-wait.js.map +1 -0
- package/dist/importer.d.ts +18 -4
- package/dist/importer.d.ts.map +1 -1
- package/dist/importer.js +322 -105
- package/dist/importer.js.map +1 -1
- package/dist/in-development.d.ts +2 -0
- package/dist/in-development.d.ts.map +1 -0
- package/dist/in-development.js +10 -0
- package/dist/in-development.js.map +1 -0
- package/dist/index.d.ts +16 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -8
- package/dist/index.js.map +1 -1
- package/dist/init-keys.d.ts +1 -1
- package/dist/init-keys.d.ts.map +1 -1
- package/dist/init-keys.js +6 -1
- package/dist/init-keys.js.map +1 -1
- package/dist/input-documents.d.ts +67 -0
- package/dist/input-documents.d.ts.map +1 -0
- package/dist/input-documents.js +693 -0
- package/dist/input-documents.js.map +1 -0
- package/dist/inputs-status.d.ts +80 -0
- package/dist/inputs-status.d.ts.map +1 -0
- package/dist/inputs-status.js +460 -0
- package/dist/inputs-status.js.map +1 -0
- package/dist/inputs.d.ts +2 -0
- package/dist/inputs.d.ts.map +1 -0
- package/dist/inputs.js +793 -0
- package/dist/inputs.js.map +1 -0
- package/dist/internal.d.ts +6 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +4 -1
- package/dist/internal.js.map +1 -1
- package/dist/join-content.d.ts +33 -0
- package/dist/join-content.d.ts.map +1 -0
- package/dist/join-content.js +114 -0
- package/dist/join-content.js.map +1 -0
- package/dist/join-stamp.d.ts +3 -0
- package/dist/join-stamp.d.ts.map +1 -0
- package/dist/join-stamp.js +12 -0
- package/dist/join-stamp.js.map +1 -0
- package/dist/manifest-drift.d.ts +9 -0
- package/dist/manifest-drift.d.ts.map +1 -0
- package/dist/manifest-drift.js +46 -0
- package/dist/manifest-drift.js.map +1 -0
- package/dist/open-document.d.ts +109 -2
- package/dist/open-document.d.ts.map +1 -1
- package/dist/open-document.js +1822 -811
- package/dist/open-document.js.map +1 -1
- package/dist/page-restore.d.ts +12 -0
- package/dist/page-restore.d.ts.map +1 -0
- package/dist/page-restore.js +23 -0
- package/dist/page-restore.js.map +1 -0
- package/dist/placement.d.ts +13 -0
- package/dist/placement.d.ts.map +1 -0
- package/dist/placement.js +34 -0
- package/dist/placement.js.map +1 -0
- package/dist/presence.d.ts +1 -0
- package/dist/presence.d.ts.map +1 -1
- package/dist/presence.js +16 -4
- package/dist/presence.js.map +1 -1
- package/dist/read-only-document.d.ts +87 -0
- package/dist/read-only-document.d.ts.map +1 -0
- package/dist/read-only-document.js +4 -0
- package/dist/read-only-document.js.map +1 -0
- package/dist/record-paging.d.ts +63 -6
- package/dist/record-paging.d.ts.map +1 -1
- package/dist/record-paging.js +122 -29
- package/dist/record-paging.js.map +1 -1
- package/dist/references.d.ts +40 -0
- package/dist/references.d.ts.map +1 -0
- package/dist/references.js +135 -0
- package/dist/references.js.map +1 -0
- package/dist/request-lane.d.ts +5 -0
- package/dist/request-lane.d.ts.map +1 -0
- package/dist/request-lane.js +48 -0
- package/dist/request-lane.js.map +1 -0
- package/dist/sealed-arrow.d.ts +170 -0
- package/dist/sealed-arrow.d.ts.map +1 -0
- package/dist/sealed-arrow.js +769 -0
- package/dist/sealed-arrow.js.map +1 -0
- package/dist/sealed-cache.d.ts +21 -0
- package/dist/sealed-cache.d.ts.map +1 -0
- package/dist/sealed-cache.js +247 -0
- package/dist/sealed-cache.js.map +1 -0
- package/dist/sealed-encode.d.ts +87 -0
- package/dist/sealed-encode.d.ts.map +1 -0
- package/dist/sealed-encode.js +876 -0
- package/dist/sealed-encode.js.map +1 -0
- package/dist/sealed-handles.d.ts +45 -0
- package/dist/sealed-handles.d.ts.map +1 -0
- package/dist/sealed-handles.js +408 -0
- package/dist/sealed-handles.js.map +1 -0
- package/dist/sealed-store.d.ts +242 -0
- package/dist/sealed-store.d.ts.map +1 -0
- package/dist/sealed-store.js +1155 -0
- package/dist/sealed-store.js.map +1 -0
- package/dist/serializer.d.ts +6 -15
- package/dist/serializer.d.ts.map +1 -1
- package/dist/session-toolbar.d.ts +20 -5
- package/dist/session-toolbar.d.ts.map +1 -1
- package/dist/session-toolbar.js +419 -67
- package/dist/session-toolbar.js.map +1 -1
- package/dist/session.d.ts +38 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +562 -79
- package/dist/session.js.map +1 -1
- package/dist/sha256.d.ts +3 -0
- package/dist/sha256.d.ts.map +1 -0
- package/dist/sha256.js +74 -0
- package/dist/sha256.js.map +1 -0
- package/dist/stable-handles.d.ts +19 -0
- package/dist/stable-handles.d.ts.map +1 -0
- package/dist/stable-handles.js +193 -0
- package/dist/stable-handles.js.map +1 -0
- package/dist/staged-seal.d.ts +2 -0
- package/dist/staged-seal.d.ts.map +1 -0
- package/dist/staged-seal.js +76 -0
- package/dist/staged-seal.js.map +1 -0
- package/dist/subscriber-delivery.d.ts +8 -0
- package/dist/subscriber-delivery.d.ts.map +1 -0
- package/dist/subscriber-delivery.js +31 -0
- package/dist/subscriber-delivery.js.map +1 -0
- package/dist/tail-op-import.d.ts +10 -0
- package/dist/tail-op-import.d.ts.map +1 -0
- package/dist/tail-op-import.js +33 -0
- package/dist/tail-op-import.js.map +1 -0
- package/dist/topic-lifecycle.d.ts +6 -0
- package/dist/topic-lifecycle.d.ts.map +1 -1
- package/dist/topic-lifecycle.js +32 -2
- package/dist/topic-lifecycle.js.map +1 -1
- package/dist/trial-replace-dialog.d.ts +10 -0
- package/dist/trial-replace-dialog.d.ts.map +1 -0
- package/dist/trial-replace-dialog.js +153 -0
- package/dist/trial-replace-dialog.js.map +1 -0
- package/dist/types.d.ts +8 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/undo.d.ts +60 -5
- package/dist/undo.d.ts.map +1 -1
- package/dist/undo.js +344 -75
- package/dist/undo.js.map +1 -1
- package/dist/version-bar.d.ts +1 -0
- package/dist/version-bar.d.ts.map +1 -1
- package/dist/version-bar.js +2 -1
- package/dist/version-bar.js.map +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +1 -1
- package/dist/ws-client.d.ts +21 -0
- package/dist/ws-client.d.ts.map +1 -1
- package/dist/ws-client.js +192 -8
- package/dist/ws-client.js.map +1 -1
- package/dist/ws-frame-limit.d.ts +1 -0
- package/dist/ws-frame-limit.d.ts.map +1 -1
- package/dist/ws-frame-limit.js.map +1 -1
- package/docs/app-manifest.md +121 -17
- package/docs/custom-chrome.md +24 -16
- package/docs/hosted-ui-advanced.md +38 -6
- package/docs/imported-documents.md +104 -51
- package/docs/presence.md +16 -4
- package/docs/schemas.md +8 -5
- package/docs/services.md +6 -6
- package/docs/streams.md +139 -150
- package/docs/text-fields.md +26 -19
- package/package.json +5 -2
- package/src/acting-organization.ts +6 -12
- package/src/api-fetch.ts +53 -3
- package/src/auth-flow.ts +1 -1
- package/src/batched-get.ts +117 -0
- package/src/call-service.ts +78 -3
- package/src/client.ts +1092 -121
- package/src/document-checkpoints.ts +307 -65
- package/src/document-commands.ts +5 -5
- package/src/document-contents.ts +27 -1
- package/src/document-set-subscription.ts +274 -0
- package/src/document-signals.ts +47 -5
- package/src/document-streams.ts +85 -16
- package/src/document-thumbnail-capture.ts +118 -32
- package/src/documents-namespace.ts +539 -84
- package/src/documents.ts +2017 -348
- package/src/errors.ts +17 -6
- package/src/fetch-retry.ts +5 -0
- package/src/file-choice.ts +26 -0
- package/src/guest-link.ts +362 -1
- package/src/handle-types.ts +73 -47
- package/src/handles.ts +253 -83
- package/src/hosted-ui.ts +211 -50
- package/src/import-contract.ts +227 -0
- package/src/import-guest.ts +193 -0
- package/src/import-report.ts +24 -0
- package/src/importer-wait.ts +4 -0
- package/src/importer.ts +386 -131
- package/src/in-development.ts +10 -0
- package/src/index.ts +76 -16
- package/src/init-keys.ts +6 -1
- package/src/input-documents.ts +819 -0
- package/src/inputs-status.ts +598 -0
- package/src/inputs.ts +1090 -0
- package/src/internal.ts +7 -0
- package/src/join-content.ts +135 -0
- package/src/join-stamp.ts +12 -0
- package/src/manifest-drift.ts +54 -0
- package/src/open-document.ts +2346 -930
- package/src/page-restore.ts +38 -0
- package/src/placement.ts +54 -0
- package/src/presence.ts +21 -5
- package/src/read-only-document.ts +133 -0
- package/src/record-paging.ts +147 -25
- package/src/references.ts +207 -0
- package/src/request-lane.ts +59 -0
- package/src/sealed-arrow.ts +843 -0
- package/src/sealed-cache.ts +267 -0
- package/src/sealed-encode.ts +1015 -0
- package/src/sealed-handles.ts +486 -0
- package/src/sealed-store.ts +1330 -0
- package/src/serializer.ts +8 -20
- package/src/session-toolbar.ts +463 -72
- package/src/session.ts +740 -99
- package/src/sha256.ts +65 -0
- package/src/stable-handles.ts +235 -0
- package/src/staged-seal.ts +90 -0
- package/src/subscriber-delivery.ts +37 -0
- package/src/tail-op-import.ts +30 -0
- package/src/topic-lifecycle.ts +38 -2
- package/src/trial-replace-dialog.ts +163 -0
- package/src/types.ts +6 -6
- package/src/undo.ts +386 -73
- package/src/version-bar.ts +5 -1
- package/src/version.ts +1 -1
- package/src/ws-client.ts +208 -16
- package/src/ws-frame-limit.ts +1 -0
package/src/internal.ts
CHANGED
|
@@ -23,8 +23,15 @@ export {
|
|
|
23
23
|
export { decodeIdToken } from './jwt.js';
|
|
24
24
|
export {
|
|
25
25
|
readDocumentContents,
|
|
26
|
+
readJoinContents,
|
|
26
27
|
type DocumentContents,
|
|
27
28
|
} from './document-contents.js';
|
|
29
|
+
export { decodeJoinContent, type JoinContent } from './join-content.js';
|
|
30
|
+
export { SEALED_ACCEPT } from './sealed-store.js';
|
|
31
|
+
export { decodeRecordValue } from './record-paging.js';
|
|
32
|
+
// `Brass.documents.list` is internal, so what it takes and returns is too.
|
|
33
|
+
export type { DocumentListing, ListDocumentsQuery } from './documents-namespace.js';
|
|
34
|
+
export type { DocumentList } from './document-set-subscription.js';
|
|
28
35
|
export {
|
|
29
36
|
fetchWithRetry,
|
|
30
37
|
RETRY_READ,
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// The one path from a join's `snapshot` field to the content it carries.
|
|
2
|
+
//
|
|
3
|
+
// The open, the reload, a line's resync, a guest's pull and a version checkout
|
|
4
|
+
// all read the same envelope, and each is handed back a content source in the
|
|
5
|
+
// encoding the server stated. The SDK never infers the encoding from bytes the
|
|
6
|
+
// wire already named, and a kind this build does not read is an upgrade error
|
|
7
|
+
// rather than a guess: treating unreadable content as empty would let a reader
|
|
8
|
+
// edit over content that is there.
|
|
9
|
+
|
|
10
|
+
import { BrassError } from './errors.js';
|
|
11
|
+
import { fetchSealedBytes, forgetSealedBytes } from './sealed-cache.js';
|
|
12
|
+
import {
|
|
13
|
+
type SealedContent,
|
|
14
|
+
SealedStoreError,
|
|
15
|
+
decodeSealedContent,
|
|
16
|
+
frameVersion,
|
|
17
|
+
loadSealedCodec,
|
|
18
|
+
needsSealedCodec,
|
|
19
|
+
NEWEST_VERSION,
|
|
20
|
+
} from './sealed-store.js';
|
|
21
|
+
|
|
22
|
+
/** What a join's snapshot field holds, decoded as far as the wire says. */
|
|
23
|
+
export type JoinContent =
|
|
24
|
+
/** No snapshot: the branch's state is its tail alone. */
|
|
25
|
+
| { kind: 'none' }
|
|
26
|
+
/** CRDT snapshot bytes, for the engine to import. */
|
|
27
|
+
| { kind: 'crdt'; bytes: Uint8Array }
|
|
28
|
+
/** Frozen content, read with no CRDT engine. */
|
|
29
|
+
| { kind: 'frozen'; content: SealedContent };
|
|
30
|
+
|
|
31
|
+
/** Fetches a signed URL's body. The open passes one that reports progress. */
|
|
32
|
+
export type ContentFetch = (url: string) => Promise<Uint8Array>;
|
|
33
|
+
|
|
34
|
+
/** The error a snapshot this build cannot read is reported as. */
|
|
35
|
+
function contentTooNewError(): BrassError {
|
|
36
|
+
return new BrassError(
|
|
37
|
+
'Opening this needs a newer version of this app. Update to the latest @brass-build/client to open it.',
|
|
38
|
+
{ status: 426 },
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The encoding a join's snapshot field states, read off the wire without
|
|
43
|
+
* fetching anything. Throws the 426 upgrade error for a kind this build does
|
|
44
|
+
* not read. */
|
|
45
|
+
export function joinEncoding(snapshot: unknown): JoinContent['kind'] {
|
|
46
|
+
if (snapshot === null || snapshot === undefined) return 'none';
|
|
47
|
+
if (typeof snapshot !== 'object') throw contentTooNewError();
|
|
48
|
+
switch ((snapshot as { kind?: unknown }).kind) {
|
|
49
|
+
case 'inline':
|
|
50
|
+
case 'presigned':
|
|
51
|
+
return 'crdt';
|
|
52
|
+
case 'sealed':
|
|
53
|
+
return 'frozen';
|
|
54
|
+
default:
|
|
55
|
+
throw contentTooNewError();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Decode a join's snapshot field.
|
|
61
|
+
*
|
|
62
|
+
* `inline` and `presigned` are CRDT snapshots; `sealed` is sealed content in
|
|
63
|
+
* any frame this build reads. A frame newer than that, or a kind this build
|
|
64
|
+
* does not know, throws the 426 upgrade error.
|
|
65
|
+
*/
|
|
66
|
+
export async function decodeJoinContent(
|
|
67
|
+
snapshot: unknown,
|
|
68
|
+
fetchBody: ContentFetch,
|
|
69
|
+
): Promise<JoinContent> {
|
|
70
|
+
if (joinEncoding(snapshot) === 'none') return { kind: 'none' };
|
|
71
|
+
const snap = snapshot as { kind?: unknown; data_b64?: unknown; url?: unknown };
|
|
72
|
+
switch (snap.kind) {
|
|
73
|
+
case 'inline':
|
|
74
|
+
if (typeof snap.data_b64 !== 'string') throw malformed();
|
|
75
|
+
return { kind: 'crdt', bytes: base64ToBytes(snap.data_b64) };
|
|
76
|
+
case 'presigned':
|
|
77
|
+
if (typeof snap.url !== 'string') throw malformed();
|
|
78
|
+
return { kind: 'crdt', bytes: await fetchBody(snap.url) };
|
|
79
|
+
default: {
|
|
80
|
+
if (typeof snap.url !== 'string') throw malformed();
|
|
81
|
+
const url = snap.url;
|
|
82
|
+
const raw = (snapshot as { content_id?: unknown }).content_id;
|
|
83
|
+
const contentId = typeof raw === 'string' && raw !== '' ? raw : undefined;
|
|
84
|
+
const bytes = await fetchSealedBytes(contentId, url, fetchBody);
|
|
85
|
+
if (needsSealedCodec(bytes)) await loadSealedCodec();
|
|
86
|
+
try {
|
|
87
|
+
return { kind: 'frozen', content: await decodeFrozen(bytes) };
|
|
88
|
+
} catch (err) {
|
|
89
|
+
// A kept copy that will not decode is dropped and fetched again, so a
|
|
90
|
+
// damaged entry costs one download rather than the document.
|
|
91
|
+
if (contentId === undefined || isUpgradeError(err)) throw err;
|
|
92
|
+
await forgetSealedBytes(contentId);
|
|
93
|
+
const fresh = await fetchSealedBytes(contentId, url, fetchBody);
|
|
94
|
+
if (needsSealedCodec(fresh)) await loadSealedCodec();
|
|
95
|
+
return { kind: 'frozen', content: await decodeFrozen(fresh) };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Frozen bytes as content, with a frame newer than this build reported as
|
|
102
|
+
* the upgrade error rather than as corrupt content. */
|
|
103
|
+
async function decodeFrozen(bytes: Uint8Array): Promise<SealedContent> {
|
|
104
|
+
const version = frameVersion(bytes);
|
|
105
|
+
if (version !== null && version > NEWEST_VERSION) throw contentTooNewError();
|
|
106
|
+
try {
|
|
107
|
+
return await decodeSealedContent(bytes);
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (err instanceof SealedStoreError) {
|
|
110
|
+
throw new BrassError('This document could not be read: its stored content is damaged.', {
|
|
111
|
+
status: 500,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function isUpgradeError(err: unknown): boolean {
|
|
119
|
+
return err instanceof BrassError && err.status === 426;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function malformed(): BrassError {
|
|
123
|
+
return new BrassError('Malformed document snapshot', { status: 500 });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Uses `atob` (present in modern browsers, Node 20+, and jsdom) rather than
|
|
127
|
+
// Node-only `Buffer`, which would force a browser-bundle polyfill.
|
|
128
|
+
export function base64ToBytes(b64: string): Uint8Array {
|
|
129
|
+
const bin = atob(b64);
|
|
130
|
+
const bytes = new Uint8Array(bin.length);
|
|
131
|
+
for (let i = 0; i < bin.length; i++) {
|
|
132
|
+
bytes[i] = bin.charCodeAt(i);
|
|
133
|
+
}
|
|
134
|
+
return bytes;
|
|
135
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The document's `updated_at` as the join that produced a copy read it. The
|
|
2
|
+
// join reads the document's row before its content, so this stamp is never
|
|
3
|
+
// later than what the copy holds.
|
|
4
|
+
const stamps = new WeakMap<object, string>();
|
|
5
|
+
|
|
6
|
+
export function setJoinStamp(doc: object, updatedAt: unknown): void {
|
|
7
|
+
if (typeof updatedAt === 'string') stamps.set(doc, updatedAt);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function joinStampOf(doc: object): string | undefined {
|
|
11
|
+
return stamps.get(doc);
|
|
12
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ApiFetch } from './api-fetch.js';
|
|
2
|
+
import { inDevelopment } from './in-development.js';
|
|
3
|
+
|
|
4
|
+
const LOCAL_MANIFEST_PATH = '/.well-known/brass-app.json';
|
|
5
|
+
|
|
6
|
+
let checked = false;
|
|
7
|
+
|
|
8
|
+
export interface ManifestDriftDeps {
|
|
9
|
+
appId: string;
|
|
10
|
+
origin: string;
|
|
11
|
+
apiFetch: ApiFetch;
|
|
12
|
+
fetchImpl?: typeof fetch;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Development builds only. Brass enforces the manifest it read from the
|
|
16
|
+
// app's published origin, so a local edit has no effect until publish.
|
|
17
|
+
// Runs once per page load and never holds up its caller.
|
|
18
|
+
export function checkManifestDrift(deps: ManifestDriftDeps): void {
|
|
19
|
+
if (checked || !inDevelopment()) return;
|
|
20
|
+
checked = true;
|
|
21
|
+
void compare(deps).catch(() => undefined);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function compare(deps: ManifestDriftDeps): Promise<void> {
|
|
25
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
26
|
+
const response = await fetchImpl(`${deps.origin}${LOCAL_MANIFEST_PATH}`, {
|
|
27
|
+
cache: 'no-store',
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) return;
|
|
30
|
+
let manifest: unknown;
|
|
31
|
+
try {
|
|
32
|
+
manifest = JSON.parse(await response.text());
|
|
33
|
+
} catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (manifest === null || typeof manifest !== 'object' || Array.isArray(manifest)) return;
|
|
37
|
+
const { differing } = await deps.apiFetch<{ differing?: unknown }>({
|
|
38
|
+
method: 'POST',
|
|
39
|
+
path: `/apps/${encodeURIComponent(deps.appId)}/manifest-drift`,
|
|
40
|
+
body: { manifest },
|
|
41
|
+
idempotent: true,
|
|
42
|
+
});
|
|
43
|
+
if (!Array.isArray(differing) || differing.length === 0) return;
|
|
44
|
+
const fields = differing.filter((f): f is string => typeof f === 'string');
|
|
45
|
+
if (fields.length === 0) return;
|
|
46
|
+
console.warn(
|
|
47
|
+
`[brass] This page's ${LOCAL_MANIFEST_PATH} differs from the manifest Brass is enforcing for this app in: ${fields.join(', ')}. Brass reads the manifest from the published app, so the change applies once the app is published.`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @internal Test seam: forget that this page load already checked. */
|
|
52
|
+
export function _resetManifestDriftCheck(): void {
|
|
53
|
+
checked = false;
|
|
54
|
+
}
|