@braidlabs/core 0.7.0 → 0.8.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 (52) hide show
  1. package/package.json +1 -1
  2. package/src/adapters/compat-adapter.js +51 -4
  3. package/src/adapters/compat-adapter.js.map +1 -1
  4. package/src/compat/database-partition.d.ts +43 -0
  5. package/src/compat/database-partition.js +165 -0
  6. package/src/compat/database-partition.js.map +1 -0
  7. package/src/compat/fragment-boundary.js +133 -25
  8. package/src/compat/fragment-boundary.js.map +1 -1
  9. package/src/compat/html-policy.js +10 -5
  10. package/src/compat/html-policy.js.map +1 -1
  11. package/src/compat/script-execution.js +5 -4
  12. package/src/compat/script-execution.js.map +1 -1
  13. package/src/compat/shared-globals.d.ts +1 -1
  14. package/src/compat/shared-globals.js +2 -2
  15. package/src/compat/shared-globals.js.map +1 -1
  16. package/src/config.d.ts +13 -2
  17. package/src/config.js.map +1 -1
  18. package/src/elements/fragment-slot.d.ts +41 -15
  19. package/src/elements/fragment-slot.js +195 -9
  20. package/src/elements/fragment-slot.js.map +1 -1
  21. package/src/elements/leave-guard.d.ts +49 -0
  22. package/src/elements/leave-guard.js +122 -0
  23. package/src/elements/leave-guard.js.map +1 -0
  24. package/src/elements/overlay-layer.d.ts +45 -0
  25. package/src/elements/overlay-layer.js +112 -0
  26. package/src/elements/overlay-layer.js.map +1 -0
  27. package/src/env/create-env.js +2 -1
  28. package/src/env/create-env.js.map +1 -1
  29. package/src/env/fragment-env.d.ts +6 -0
  30. package/src/index.d.ts +3 -0
  31. package/src/index.js +14 -0
  32. package/src/index.js.map +1 -1
  33. package/src/realm/realm-manager.js +2 -1
  34. package/src/realm/realm-manager.js.map +1 -1
  35. package/src/realm/sandbox-realm.d.ts +5 -7
  36. package/src/realm/sandbox-realm.js +104 -39
  37. package/src/realm/sandbox-realm.js.map +1 -1
  38. package/src/trusted-types.d.ts +28 -0
  39. package/src/trusted-types.js +58 -0
  40. package/src/trusted-types.js.map +1 -0
  41. package/src/weave/capabilities.d.ts +10 -3
  42. package/src/weave/capabilities.js +19 -3
  43. package/src/weave/capabilities.js.map +1 -1
  44. package/src/weave/context-bridge.d.ts +2 -0
  45. package/src/weave/context-bridge.js +1 -1
  46. package/src/weave/context-bridge.js.map +1 -1
  47. package/src/weave/guest.d.ts +2 -0
  48. package/src/weave/guest.js +10 -5
  49. package/src/weave/guest.js.map +1 -1
  50. package/src/weave/messages.d.ts +24 -0
  51. package/src/weave/messages.js +22 -0
  52. package/src/weave/messages.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@braidlabs/core",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Braid client runtime — compose independently deployed frontend apps into one page. This build ships the compat adapter (the contained web-fragments-style emulation layer) as the default adapter.",
5
5
  "keywords": [
6
6
  "micro-frontends",
@@ -1,8 +1,9 @@
1
1
  import { compatMetadataSymbol } from '../compat/metadata.js';
2
2
  import { initializeRealmContext } from '../compat/realm-patches.js';
3
- import { executeScriptsInFragmentContent } from '../compat/script-execution.js';
3
+ import { executeScriptsInFragmentContent, markStartedInPlace } from '../compat/script-execution.js';
4
4
  import { installRealmGlobals } from '../compat/shared-globals.js';
5
5
  import { isDevMode } from '../config.js';
6
+ import { trustedHTML, trustedScript } from '../trusted-types.js';
6
7
  /**
7
8
  * The compat adapter — the emulation layer, contained.
8
9
  *
@@ -88,17 +89,63 @@ export const compatAdapter = {
88
89
  * saw a bare markup snippet.
89
90
  */
90
91
  export function parseFragmentContent(html, mainDocument) {
91
- const parsed = new DOMParser().parseFromString(html, 'text/html');
92
+ // markup the gateway prepared, parsed where nothing runs: Braid's own write (see trusted-types.ts)
93
+ const parsed = new DOMParser().parseFromString(trustedHTML(html), 'text/html');
92
94
  const braidHtml = parsed.body.querySelector('braid-html');
93
95
  if (braidHtml) {
94
- return mainDocument.importNode(braidHtml, true);
96
+ return readyScripts(mainDocument.importNode(braidHtml, true));
95
97
  }
96
98
  const wrapper = mainDocument.createElement('braid-html');
97
99
  const braidBody = mainDocument.createElement('braid-body');
98
100
  braidBody.append(...[...parsed.body.childNodes].map((node) => mainDocument.importNode(node, true)));
99
101
  wrapper.append(braidBody);
100
- return wrapper;
102
+ return readyScripts(wrapper);
101
103
  }
104
+ /**
105
+ * Readies the fragment's scripts in markup imported from the parser's document, before any of it
106
+ * reaches the page.
107
+ *
108
+ * - **Only what the gateway neutralized is kept.** The gateway gives every script `type="inert"`,
109
+ * an SVG one included; one without it slipped past the rewriter, and nothing here can tell what
110
+ * it would do in the host, so it is dropped (in dev, with a warning).
111
+ * - **Each inline script gets its own text back through Braid's Trusted Types policy.** Imported
112
+ * from another document, a script no longer counts as parser-written, and a host enforcing
113
+ * Trusted Types would refuse to mark it started below. Without a policy nothing changes.
114
+ * - **Each is marked started now, while the tree is detached**, so there is no moment at which a
115
+ * blessed script is in the page unmarked. The fragment's realm runs fresh copies; these never run.
116
+ */
117
+ function readyScripts(root) {
118
+ for (const script of root.querySelectorAll('script')) {
119
+ if (script.getAttribute('type') !== 'inert') {
120
+ if (isDevMode())
121
+ console.warn('[braid] a fragment script was not neutralized by the gateway and is dropped', script);
122
+ script.remove();
123
+ continue;
124
+ }
125
+ if (script.hasAttribute('src'))
126
+ continue;
127
+ const text = script.textContent ?? '';
128
+ const trusted = trustedScript(text);
129
+ if (trusted === text)
130
+ continue;
131
+ if (script.namespaceURI === XHTML) {
132
+ script.textContent = trusted;
133
+ }
134
+ else if (!script.hasAttribute('href') && !script.hasAttribute('xlink:href')) {
135
+ // An SVG script's text cannot be given back through a policy (only an HTML script's setters
136
+ // take one), so a host enforcing Trusted Types would report it. Its realm runs an HTML copy
137
+ // whatever it is here, so the inert one in the page becomes an HTML script too.
138
+ const html = root.ownerDocument.createElement('script');
139
+ for (const attribute of script.attributes)
140
+ html.setAttribute(attribute.name, attribute.value);
141
+ html.textContent = trusted;
142
+ script.replaceWith(html);
143
+ }
144
+ }
145
+ markStartedInPlace(root);
146
+ return root;
147
+ }
148
+ const XHTML = 'http://www.w3.org/1999/xhtml';
102
149
  /**
103
150
  * Resolves once every image that holds a document's load event has loaded or failed.
104
151
  *
@@ -1 +1 @@
1
- {"version":3,"file":"compat-adapter.js","sourceRoot":"","sources":["../../../../../libs/core/src/adapters/compat-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compat-adapter.js","sourceRoot":"","sources":["../../../../../libs/core/src/adapters/compat-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,4BAA4B,CAAC;AACpE,OAAO,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACpG,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEjE;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,aAAa,GAAqB;IAC7C,IAAI,EAAE,QAAQ;IACd,SAAS,EAAE,aAAa;IACxB,oBAAoB,EAAE,IAAI;IAE1B,KAAK,CAAC,IAAI,CAAC,GAAuB;QAChC,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,EAAE,GAC3I,GAAG,CAAC;QAEN,MAAM,kBAAkB,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE;YACnD,CAAC,oBAAoB,CAAC,EAAE,EAAE,kBAAkB,EAAE,SAA+B,EAAE;SAChF,CAAqB,CAAC;QAEvB,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,uFAAuF;YACvF,uFAAuF;YACvF,yFAAyF;YACzF,uFAAuF;YACvF,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,IAAI,EAAE,EAAE,WAAW,CAAC,aAAa,CAAC,CAAC;YAE5E,uFAAuF;YACvF,wEAAwE;YACxE,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;QAED,yFAAyF;QACzF,+EAA+E;QAC/E,MAAM,uBAAuB,GAAG,IAAI,eAAe,EAAE,CAAC;QACtD,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,uBAAuB,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAExF,oFAAoF;QACpF,0CAA0C;QAC1C,MAAM,YAAY,GAAG,sBAAsB,CACzC,KAAK,EACL,kBAAkB,EAClB,WAAW,EACX,KAAK,EACL,uBAAuB,EACvB,aAAa,IAAI,EAAE,EACnB,gBAAgB,EAChB,UAAU,CACX,CAAC;QAEF,6FAA6F;QAC7F,IAAI,YAAY;YAAE,mBAAmB,CAAC,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;QAE9E,qFAAqF;QACrF,+BAA+B,CAAC,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QAE7D,4FAA4F;QAC5F,6FAA6F;QAC7F,oBAAoB;QACpB,YAAY,CAAC,YAAY,EAAE,CAAC;QAE5B,0EAA0E;QAC1E,kBAAkB,CAAC,oBAAoB,CAAC,CAAC,kBAAkB,GAAG,aAAa,CAAC;QAC5E,WAAW,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAChG,WAAW,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QAE/F,8FAA8F;QAC9F,+FAA+F;QAC/F,EAAE;QACF,4FAA4F;QAC5F,0FAA0F;QAC1F,mFAAmF;QACnF,KAAK,aAAa,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;YACxC,wFAAwF;YACxF,UAAU,CAAC,GAAG,EAAE;gBACd,IAAI,MAAM,CAAC,OAAO;oBAAE,OAAO;gBAC3B,kBAAkB,CAAC,oBAAoB,CAAC,CAAC,kBAAkB,GAAG,UAAU,CAAC;gBACzE,WAAW,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,kBAAkB,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;gBAChG,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;gBAErF,IAAI,SAAS,EAAE,EAAE,CAAC;oBAChB,OAAO,CAAC,KAAK,CAAC,UAAU,UAAU,0BAA0B,EAAE,EAAE,UAAU,EAAE,kBAAkB,EAAE,CAAC,CAAC;gBACpG,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY,EAAE,YAAsB;IACvE,mGAAmG;IACnG,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC,eAAe,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAC;IAE/E,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAC1D,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,YAAY,CAAC,YAAY,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAgB,CAAC,CAAC;IAC/E,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,YAAY,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;IAC3D,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACpG,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC1B,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,SAAS,YAAY,CAAoB,IAAO;IAC9C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrD,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,OAAO,EAAE,CAAC;YAC5C,IAAI,SAAS,EAAE;gBAAE,OAAO,CAAC,IAAI,CAAC,6EAA6E,EAAE,MAAM,CAAC,CAAC;YACrH,MAAM,CAAC,MAAM,EAAE,CAAC;YAChB,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,YAAY,CAAC,KAAK,CAAC;YAAE,SAAS;QACzC,MAAM,IAAI,GAAG,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;QACtC,MAAM,OAAO,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,OAAO,KAAK,IAAI;YAAE,SAAS;QAC/B,IAAI,MAAM,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;YAClC,MAAM,CAAC,WAAW,GAAG,OAAO,CAAC;QAC/B,CAAC;aAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,CAAC;YAC9E,4FAA4F;YAC5F,4FAA4F;YAC5F,gFAAgF;YAChF,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YACxD,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,UAAU;gBAAE,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC;YAC9F,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;YAC3B,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,KAAK,GAAG,8BAA8B,CAAC;AAE7C;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,WAAwB;IACpD,MAAM,MAAM,GAAG,WAAW,CAAC,gBAAgB,CAAmB,6BAA6B,CAAC,CAAC;IAC7F,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC3B,CAAC;IAED,MAAM,aAAa,GAAG,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9C,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACnC,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACnB,OAAO,EAAE,CAAC;YACZ,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,gBAAgB,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;gBAChD,KAAK,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Per-fragment IndexedDB and Cache Storage, for a compat fragment granted
3
+ * `capabilities.storage: { databases: true }`.
4
+ *
5
+ * A compat realm is a same-origin iframe, so its `indexedDB` and `caches` are the origin's: two
6
+ * fragments that both open a database called `app` open the same one, and the host sees it too.
7
+ * `compat/storage-partition.ts` already gives each partition its own Web Storage and cookies; this
8
+ * does the same for the databases.
9
+ *
10
+ * - **Where the browser has Storage Buckets** (Chromium), the realm gets a bucket of its own —
11
+ * `navigator.storageBuckets.open()` — and that bucket's `indexedDB` and `caches`. The browser
12
+ * keeps the data apart, and evicts, quotas and deletes it per bucket, so one fragment filling its
13
+ * store does not cost another its data.
14
+ * - **Elsewhere**, the realm's own factory and cache storage are wrapped to prefix every name with
15
+ * the partition, and to list only the partition's own. `IDBDatabase.name` and a cache's key
16
+ * still show the prefix: an accident barrier, like the rest of the trusted tier, not a boundary.
17
+ *
18
+ * Installed before any fragment code runs. Opt-in, so a fragment's existing databases stay where
19
+ * they are until it asks.
20
+ */
21
+ export type DatabasePartitionKind = 'bucket' | 'prefixed';
22
+ /** Gives the realm window its partition's `indexedDB` and `caches`; resolves with which kind. */
23
+ export declare function installDatabasePartition(realm: Window, partition: string): Promise<DatabasePartitionKind>;
24
+ /**
25
+ * A bucket name for a partition: lowercase letters, digits, `-` and `_`, at most 64 characters, as
26
+ * the API requires. The readable part may collide after that folding; the hash of the exact
27
+ * partition keeps two partitions apart.
28
+ */
29
+ export declare function bucketName(partition: string): string;
30
+ /** An `IDBFactory` whose databases are the partition's: same prototype, so `instanceof` holds. */
31
+ export declare function prefixedFactory(native: IDBFactory, prefix: string): IDBFactory;
32
+ /** A `CacheStorage` whose caches are the partition's. `match` looks only in those. */
33
+ export declare function prefixedCacheStorage(native: CacheStorage, prefix: string): CacheStorage;
34
+ /**
35
+ * Deletes every fragment's partitioned IndexedDB and Cache Storage on this origin — the buckets,
36
+ * and the prefixed databases and caches of a browser without them. For sign-out on a shared device:
37
+ * a partitioned fragment's data lives where the origin's own `indexedDB.databases()` and
38
+ * `caches.keys()` do not list it, so a sign-out routine that clears those leaves it behind.
39
+ *
40
+ * Only Braid's own: realm buckets matched by their full name format, databases and caches by their
41
+ * `braid:<partition>:` prefix.
42
+ */
43
+ export declare function clearBraidDatabases(scope?: Window): Promise<void>;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Per-fragment IndexedDB and Cache Storage, for a compat fragment granted
3
+ * `capabilities.storage: { databases: true }`.
4
+ *
5
+ * A compat realm is a same-origin iframe, so its `indexedDB` and `caches` are the origin's: two
6
+ * fragments that both open a database called `app` open the same one, and the host sees it too.
7
+ * `compat/storage-partition.ts` already gives each partition its own Web Storage and cookies; this
8
+ * does the same for the databases.
9
+ *
10
+ * - **Where the browser has Storage Buckets** (Chromium), the realm gets a bucket of its own —
11
+ * `navigator.storageBuckets.open()` — and that bucket's `indexedDB` and `caches`. The browser
12
+ * keeps the data apart, and evicts, quotas and deletes it per bucket, so one fragment filling its
13
+ * store does not cost another its data.
14
+ * - **Elsewhere**, the realm's own factory and cache storage are wrapped to prefix every name with
15
+ * the partition, and to list only the partition's own. `IDBDatabase.name` and a cache's key
16
+ * still show the prefix: an accident barrier, like the rest of the trusted tier, not a boundary.
17
+ *
18
+ * Installed before any fragment code runs. Opt-in, so a fragment's existing databases stay where
19
+ * they are until it asks.
20
+ */
21
+ /** Gives the realm window its partition's `indexedDB` and `caches`; resolves with which kind. */
22
+ export async function installDatabasePartition(realm, partition) {
23
+ const realmWindow = realm;
24
+ const buckets = realmWindow.navigator.storageBuckets;
25
+ const prefix = `braid:${encodeURIComponent(partition)}:`;
26
+ // Where a partition's data already is, it stays: a browser that gains buckets, or a bucket that
27
+ // was refused once, would otherwise hand the fragment an empty store and strand the old one.
28
+ if (typeof buckets?.open === 'function' && !(await hasPrefixedData(realmWindow, prefix))) {
29
+ try {
30
+ const bucket = await buckets.open(bucketName(partition));
31
+ define(realm, 'indexedDB', bucket.indexedDB);
32
+ define(realm, 'caches', bucket.caches);
33
+ return 'bucket';
34
+ }
35
+ catch {
36
+ // refused (an opaque origin, a quota policy): fall back to prefixing
37
+ }
38
+ }
39
+ if (realmWindow.indexedDB)
40
+ define(realm, 'indexedDB', prefixedFactory(realmWindow.indexedDB, prefix));
41
+ if (realmWindow.caches)
42
+ define(realm, 'caches', prefixedCacheStorage(realmWindow.caches, prefix));
43
+ return 'prefixed';
44
+ }
45
+ /**
46
+ * A bucket name for a partition: lowercase letters, digits, `-` and `_`, at most 64 characters, as
47
+ * the API requires. The readable part may collide after that folding; the hash of the exact
48
+ * partition keeps two partitions apart.
49
+ */
50
+ export function bucketName(partition) {
51
+ const readable = partition.toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40);
52
+ return `${BUCKET_NAMESPACE}${readable || 'p'}_${fnv1a(partition)}`;
53
+ }
54
+ /**
55
+ * Realm database buckets, and only them: a fixed namespace no other bucket of Braid's uses (the
56
+ * service worker's are `braid-sw-…`), matched in full, so clearing never takes an app's own bucket.
57
+ */
58
+ const BUCKET_NAMESPACE = 'braid-db_';
59
+ const BUCKET_FORMAT = /^braid-db_[a-z0-9-]{1,40}_[0-9a-f]{8}$/;
60
+ const PREFIX_FORMAT = /^braid:[^:]+:/;
61
+ function fnv1a(text) {
62
+ let hash = 0x811c9dc5;
63
+ for (let i = 0; i < text.length; i++) {
64
+ hash ^= text.charCodeAt(i);
65
+ hash = Math.imul(hash, 0x01000193) >>> 0;
66
+ }
67
+ return hash.toString(16).padStart(8, '0');
68
+ }
69
+ function define(realm, name, value) {
70
+ Object.defineProperty(realm, name, { configurable: true, enumerable: true, get: () => value });
71
+ }
72
+ /** An `IDBFactory` whose databases are the partition's: same prototype, so `instanceof` holds. */
73
+ export function prefixedFactory(native, prefix) {
74
+ return Object.create(Object.getPrototypeOf(native), {
75
+ open: {
76
+ value: (name, version) => (version === undefined ? native.open(prefix + name) : native.open(prefix + name, version)),
77
+ },
78
+ deleteDatabase: { value: (name) => native.deleteDatabase(prefix + name) },
79
+ // only where the native factory has it, so feature detection tells the truth
80
+ ...(typeof native.databases === 'function'
81
+ ? {
82
+ databases: {
83
+ value: async () => (await native.databases())
84
+ .filter((info) => info.name?.startsWith(prefix))
85
+ .map((info) => ({ ...info, name: info.name.slice(prefix.length) })),
86
+ },
87
+ }
88
+ : // shadowing the prototype's, which would throw on this object anyway
89
+ { databases: { value: undefined } }),
90
+ cmp: { value: (first, second) => native.cmp(first, second) },
91
+ });
92
+ }
93
+ /** A `CacheStorage` whose caches are the partition's. `match` looks only in those. */
94
+ export function prefixedCacheStorage(native, prefix) {
95
+ const own = async () => (await native.keys()).filter((name) => name.startsWith(prefix));
96
+ return Object.create(Object.getPrototypeOf(native), {
97
+ open: { value: (name) => native.open(prefix + name) },
98
+ has: { value: (name) => native.has(prefix + name) },
99
+ delete: { value: (name) => native.delete(prefix + name) },
100
+ keys: { value: async () => (await own()).map((name) => name.slice(prefix.length)) },
101
+ match: {
102
+ value: async (request, options) => {
103
+ if (options?.cacheName !== undefined)
104
+ return native.match(request, { ...options, cacheName: prefix + options.cacheName });
105
+ for (const name of await own()) {
106
+ const found = await (await native.open(name)).match(request, options);
107
+ if (found)
108
+ return found;
109
+ }
110
+ return undefined;
111
+ },
112
+ },
113
+ });
114
+ }
115
+ /** Whether the partition already keeps databases or caches under prefixed names. */
116
+ async function hasPrefixedData(realm, prefix) {
117
+ const has = async (names) => {
118
+ try {
119
+ return (await names()).some((name) => name?.startsWith(prefix));
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ };
125
+ const factory = realm.indexedDB;
126
+ const caches = realm.caches;
127
+ return ((typeof factory?.databases === 'function' && (await has(async () => (await factory.databases()).map((info) => info.name)))) ||
128
+ (caches !== undefined && (await has(() => caches.keys()))));
129
+ }
130
+ /**
131
+ * Deletes every fragment's partitioned IndexedDB and Cache Storage on this origin — the buckets,
132
+ * and the prefixed databases and caches of a browser without them. For sign-out on a shared device:
133
+ * a partitioned fragment's data lives where the origin's own `indexedDB.databases()` and
134
+ * `caches.keys()` do not list it, so a sign-out routine that clears those leaves it behind.
135
+ *
136
+ * Only Braid's own: realm buckets matched by their full name format, databases and caches by their
137
+ * `braid:<partition>:` prefix.
138
+ */
139
+ export async function clearBraidDatabases(scope = window) {
140
+ const realmWindow = scope;
141
+ const buckets = realmWindow.navigator.storageBuckets;
142
+ if (buckets && typeof buckets.keys === 'function') {
143
+ for (const name of await buckets.keys())
144
+ if (BUCKET_FORMAT.test(name))
145
+ await buckets.delete(name);
146
+ }
147
+ const factory = realmWindow.indexedDB;
148
+ if (factory && typeof factory.databases === 'function') {
149
+ for (const { name } of await factory.databases()) {
150
+ if (name && PREFIX_FORMAT.test(name)) {
151
+ await new Promise((resolve) => {
152
+ const request = factory.deleteDatabase(name);
153
+ request.onsuccess = request.onerror = request.onblocked = () => resolve();
154
+ });
155
+ }
156
+ }
157
+ }
158
+ const cacheStorage = realmWindow.caches;
159
+ if (cacheStorage) {
160
+ for (const name of await cacheStorage.keys())
161
+ if (PREFIX_FORMAT.test(name))
162
+ await cacheStorage.delete(name);
163
+ }
164
+ }
165
+ //# sourceMappingURL=database-partition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-partition.js","sourceRoot":"","sources":["../../../../../libs/core/src/compat/database-partition.ts"],"names":[],"mappings":"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"}
@@ -48,12 +48,112 @@ const native = {
48
48
  elementAfter: Element.prototype.after,
49
49
  shadowRootInnerHTML: Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'innerHTML'),
50
50
  };
51
+ /**
52
+ * Every boundary's stamped prototypes and installed wrappers, page-wide. A node that moves between
53
+ * fragments carries a stamp from each, and delegation has to see past all of them.
54
+ */
55
+ const allStampedProtos = new WeakSet();
56
+ const allBoundaryWrappers = new WeakSet();
57
+ /**
58
+ * The built-in prototypes the intercepted methods are defined on. A method found on one of these is
59
+ * the platform's — or a host page's later patch of it, which fragment nodes have never run.
60
+ */
61
+ const PLATFORM_PROTOTYPES = new Set([Node.prototype, Element.prototype, HTMLElement.prototype, DocumentFragment.prototype]);
62
+ /**
63
+ * The method a node would have without any boundary's stamp: whatever its prototype chain defines
64
+ * below the lowest stamp, read at call time — or the native method, when that is the platform's.
65
+ *
66
+ * A custom element may override insertion on its own class — Stencil's scoped components do, to
67
+ * move light-DOM children into the element's inner slot. The stamp sits above that class, so
68
+ * calling the native method unconditionally would skip the override: children land on the host
69
+ * instead of in the slot, and an `insertBefore` whose reference the component had relocated throws
70
+ * `NotFoundError`.
71
+ *
72
+ * Past the *lowest* stamp, of any boundary, so a node stamped by fragment A, then B, then A again
73
+ * never delegates from one boundary's wrapper to another's and back. Unstamped receivers (a shadow
74
+ * root's own-property overrides) get the native method.
75
+ */
76
+ function unstampedMethod(node, name, nativeMethod) {
77
+ let belowStamps;
78
+ for (let proto = Object.getPrototypeOf(node); proto !== null; proto = Object.getPrototypeOf(proto)) {
79
+ if (allStampedProtos.has(proto))
80
+ belowStamps = Object.getPrototypeOf(proto);
81
+ }
82
+ for (let owner = belowStamps; owner; owner = Object.getPrototypeOf(owner)) {
83
+ if (!Object.hasOwn(owner, name))
84
+ continue;
85
+ if (PLATFORM_PROTOTYPES.has(owner))
86
+ return nativeMethod;
87
+ const method = Object.getOwnPropertyDescriptor(owner, name)?.value;
88
+ return typeof method === 'function' && !allBoundaryWrappers.has(method) ? method : nativeMethod;
89
+ }
90
+ return nativeMethod;
91
+ }
92
+ /**
93
+ * Custom elements stamped before their definition existed, page-wide by tag name, and what restamps
94
+ * each one.
95
+ *
96
+ * Upgrading an element replaces its prototype with the definition's, which drops the stamp. Most
97
+ * upgrades happen during an insertion the boundary stamps after anyway; this catches the rest — an
98
+ * element already in a fragment's DOM when its definition arrives *in the host's registry*, which is
99
+ * the one that upgrades fragment DOM: a shared component bundle the host loads. A compat fragment's
100
+ * own `customElements.define` goes to its realm's registry and never resolves here.
101
+ *
102
+ * Many dashed names are never defined at all (every Angular component host), so nothing here may
103
+ * keep a boundary alive: one `whenDefined` per name for the page, closing over only the waiting
104
+ * set, and each element's restamper held weakly by the element.
105
+ */
106
+ const awaitingDefinition = new Map();
107
+ const restampers = new WeakMap();
108
+ function restampWhenDefined(element, restamp) {
109
+ const alreadyWaiting = restampers.has(element);
110
+ restampers.set(element, restamp);
111
+ if (alreadyWaiting)
112
+ return;
113
+ const name = element.localName;
114
+ let waiting = awaitingDefinition.get(name);
115
+ if (!waiting) {
116
+ const entry = { elements: new Set(), pruneAt: 1024 };
117
+ awaitingDefinition.set(name, (waiting = entry));
118
+ customElements.whenDefined(name).then(() => {
119
+ awaitingDefinition.delete(name);
120
+ for (const ref of entry.elements) {
121
+ const defined = ref.deref();
122
+ if (defined)
123
+ restampers.get(defined)?.(defined);
124
+ }
125
+ }, () => awaitingDefinition.delete(name));
126
+ }
127
+ // a long-lived list of a never-defined element leaves dead refs behind; sweep them as it grows
128
+ if (waiting.elements.size >= waiting.pruneAt) {
129
+ for (const ref of waiting.elements)
130
+ if (!ref.deref())
131
+ waiting.elements.delete(ref);
132
+ waiting.pruneAt = Math.max(1024, waiting.elements.size * 2);
133
+ }
134
+ waiting.elements.add(new WeakRef(element));
135
+ }
51
136
  export function createFragmentBoundary(options) {
52
137
  const { realmDocument, shadowRoot, braidDocumentElement, abortSignal, htmlPolicy } = options;
53
138
  const stampedNodes = new WeakSet();
54
139
  const stampedProtos = new Map();
55
140
  const stampedProtoSet = new WeakSet();
56
141
  let warnedAboutUnstampedEntry = false;
142
+ /** Restamps an upgraded element, unless it has left this fragment or the fragment is gone. */
143
+ function restampIfStillInFragment(element) {
144
+ if (!abortSignal.aborted && isInFragmentDom(element))
145
+ stampNode(element);
146
+ }
147
+ /**
148
+ * Stamps what an insertion put in place: anything in the fragment's DOM, or in a detached tree,
149
+ * which may enter it later. Not a node a component's own insertion override placed in the host's
150
+ * DOM instead (a portal, an overlay) — that is the host's, and stays on pristine prototypes.
151
+ */
152
+ function stampInserted(node) {
153
+ if (node.isConnected && !isInFragmentDom(node))
154
+ return;
155
+ stampTree(node);
156
+ }
57
157
  /** A node is inside this fragment's DOM iff its plain root is the fragment's shadow root. */
58
158
  function isInFragmentDom(node) {
59
159
  return native.getRootNode.call(node) === shadowRoot;
@@ -174,13 +274,13 @@ export function createFragmentBoundary(options) {
174
274
  * `compatDomInsertion`). In all cases the inserted subtree is stamped so the stamped set
175
275
  * stays closed.
176
276
  */
177
- function makeSingleNodeInsertion(nativeFn) {
277
+ function makeSingleNodeInsertion(name, nativeFn) {
178
278
  return function boundaryInsertion(childNode) {
179
279
  const args = arguments;
180
280
  const futureStamps = nodesToStampAfterInsertion(childNode);
181
281
  const doInsertTheNode = () => {
182
- const result = nativeFn.apply(this, args);
183
- futureStamps.forEach(stampTree);
282
+ const result = unstampedMethod(this, name, nativeFn).apply(this, args);
283
+ futureStamps.forEach(stampInserted);
184
284
  return result;
185
285
  };
186
286
  if (!isInFragmentDom(this)) {
@@ -195,8 +295,8 @@ export function createFragmentBoundary(options) {
195
295
  const args = arguments;
196
296
  const futureStamps = nodesToStampAfterInsertion(newChild);
197
297
  const doInsertTheNode = () => {
198
- const result = native.replaceChild.apply(this, args);
199
- futureStamps.forEach(stampTree);
298
+ const result = unstampedMethod(this, 'replaceChild', native.replaceChild).apply(this, args);
299
+ futureStamps.forEach(stampInserted);
200
300
  return result;
201
301
  };
202
302
  if (!isInFragmentDom(this)) {
@@ -210,8 +310,8 @@ export function createFragmentBoundary(options) {
210
310
  function boundaryInsertAdjacentElement(where, element) {
211
311
  const args = arguments;
212
312
  const doInsertTheNode = () => {
213
- const result = native.insertAdjacentElement.apply(this, args);
214
- stampTree(element);
313
+ const result = unstampedMethod(this, 'insertAdjacentElement', native.insertAdjacentElement).apply(this, args);
314
+ stampInserted(element);
215
315
  return result;
216
316
  };
217
317
  if (!isInFragmentDom(this)) {
@@ -224,14 +324,14 @@ export function createFragmentBoundary(options) {
224
324
  * replaceWith): the native insertion runs once, after every node argument has been
225
325
  * preprocessed.
226
326
  */
227
- function makeMultiNodeInsertion(nativeFn) {
327
+ function makeMultiNodeInsertion(name, nativeFn) {
228
328
  return function boundaryMultiInsertion(...nodes) {
229
329
  const args = arguments;
230
330
  const nodeArgs = nodes.filter((node) => typeof node !== 'string');
231
331
  const futureStamps = nodeArgs.flatMap(nodesToStampAfterInsertion);
232
332
  const doInsertTheNodes = () => {
233
- nativeFn.apply(this, args);
234
- futureStamps.forEach(stampTree);
333
+ unstampedMethod(this, name, nativeFn).apply(this, args);
334
+ futureStamps.forEach(stampInserted);
235
335
  };
236
336
  if (!isInFragmentDom(this)) {
237
337
  return doInsertTheNodes();
@@ -272,18 +372,18 @@ export function createFragmentBoundary(options) {
272
372
  };
273
373
  }
274
374
  const elementOverrides = {
275
- appendChild: { configurable: true, writable: true, value: makeSingleNodeInsertion(native.appendChild) },
276
- insertBefore: { configurable: true, writable: true, value: makeSingleNodeInsertion(native.insertBefore) },
375
+ appendChild: { configurable: true, writable: true, value: makeSingleNodeInsertion('appendChild', native.appendChild) },
376
+ insertBefore: { configurable: true, writable: true, value: makeSingleNodeInsertion('insertBefore', native.insertBefore) },
277
377
  replaceChild: { configurable: true, writable: true, value: boundaryReplaceChild },
278
378
  insertAdjacentElement: { configurable: true, writable: true, value: boundaryInsertAdjacentElement },
279
- append: { configurable: true, writable: true, value: makeMultiNodeInsertion(native.elementAppend) },
280
- prepend: { configurable: true, writable: true, value: makeMultiNodeInsertion(native.elementPrepend) },
379
+ append: { configurable: true, writable: true, value: makeMultiNodeInsertion('append', native.elementAppend) },
380
+ prepend: { configurable: true, writable: true, value: makeMultiNodeInsertion('prepend', native.elementPrepend) },
281
381
  replaceChildren: {
282
382
  configurable: true,
283
383
  writable: true,
284
- value: makeMultiNodeInsertion(native.elementReplaceChildren),
384
+ value: makeMultiNodeInsertion('replaceChildren', native.elementReplaceChildren),
285
385
  },
286
- replaceWith: { configurable: true, writable: true, value: makeMultiNodeInsertion(native.elementReplaceWith) },
386
+ replaceWith: { configurable: true, writable: true, value: makeMultiNodeInsertion('replaceWith', native.elementReplaceWith) },
287
387
  insertAdjacentHTML: {
288
388
  configurable: true,
289
389
  writable: true,
@@ -314,7 +414,7 @@ export function createFragmentBoundary(options) {
314
414
  }
315
415
  else {
316
416
  // also what throws for an unknown position, or outside an element with no parent
317
- native.insertAdjacentHTML.call(this, position, text);
417
+ unstampedMethod(this, 'insertAdjacentHTML', native.insertAdjacentHTML).call(this, position, text);
318
418
  }
319
419
  stampTree(inside ? this : this.parentNode ?? this);
320
420
  },
@@ -323,7 +423,7 @@ export function createFragmentBoundary(options) {
323
423
  configurable: true,
324
424
  writable: true,
325
425
  value: function boundaryCloneNode(deep) {
326
- const clone = native.cloneNode.call(this, deep);
426
+ const clone = unstampedMethod(this, 'cloneNode', native.cloneNode).call(this, deep);
327
427
  inheritProvenance(this, clone, Boolean(deep));
328
428
  stampTree(clone);
329
429
  return clone;
@@ -350,6 +450,10 @@ export function createFragmentBoundary(options) {
350
450
  },
351
451
  ...makeHtmlParsingOverrides(native.elementInnerHTML),
352
452
  };
453
+ for (const descriptor of Object.values(elementOverrides)) {
454
+ if (typeof descriptor.value === 'function')
455
+ allBoundaryWrappers.add(descriptor.value);
456
+ }
353
457
  /**
354
458
  * What a URL set from fragment script resolves against, read when it is set: the namespace from
355
459
  * the realm's base, and the fragment's own `<base href>` from its markup — the two the gateway
@@ -362,13 +466,16 @@ export function createFragmentBoundary(options) {
362
466
  function stampNode(node) {
363
467
  if (node.nodeType !== Node.ELEMENT_NODE)
364
468
  return;
365
- if (stampedNodes.has(node))
366
- return;
469
+ // by prototype, not by membership: an upgrade replaces a stamped element's prototype, and the
470
+ // next stamping pass that reaches it should put the stamp back
367
471
  const proto = Object.getPrototypeOf(node);
368
472
  if (proto === null || stampedProtoSet.has(proto)) {
369
473
  stampedNodes.add(node);
370
474
  return;
371
475
  }
476
+ if (proto === HTMLElement.prototype && node.localName.includes('-')) {
477
+ restampWhenDefined(node, restampIfStillInFragment);
478
+ }
372
479
  let stampedProto = stampedProtos.get(proto);
373
480
  if (!stampedProto) {
374
481
  // a subresource URL the fragment's code sets resolves into its namespace, as one in its
@@ -379,6 +486,7 @@ export function createFragmentBoundary(options) {
379
486
  });
380
487
  stampedProtos.set(proto, stampedProto);
381
488
  stampedProtoSet.add(stampedProto);
489
+ allStampedProtos.add(stampedProto);
382
490
  }
383
491
  Object.setPrototypeOf(node, stampedProto);
384
492
  stampedNodes.add(node);
@@ -399,15 +507,15 @@ export function createFragmentBoundary(options) {
399
507
  * (reporting the main document) — only insertions are intercepted.
400
508
  */
401
509
  Object.defineProperties(shadowRoot, {
402
- appendChild: { configurable: true, writable: true, value: makeSingleNodeInsertion(native.appendChild) },
403
- insertBefore: { configurable: true, writable: true, value: makeSingleNodeInsertion(native.insertBefore) },
510
+ appendChild: { configurable: true, writable: true, value: makeSingleNodeInsertion('appendChild', native.appendChild) },
511
+ insertBefore: { configurable: true, writable: true, value: makeSingleNodeInsertion('insertBefore', native.insertBefore) },
404
512
  replaceChild: { configurable: true, writable: true, value: boundaryReplaceChild },
405
- append: { configurable: true, writable: true, value: makeMultiNodeInsertion(native.fragmentAppend) },
406
- prepend: { configurable: true, writable: true, value: makeMultiNodeInsertion(native.fragmentPrepend) },
513
+ append: { configurable: true, writable: true, value: makeMultiNodeInsertion('append', native.fragmentAppend) },
514
+ prepend: { configurable: true, writable: true, value: makeMultiNodeInsertion('prepend', native.fragmentPrepend) },
407
515
  replaceChildren: {
408
516
  configurable: true,
409
517
  writable: true,
410
- value: makeMultiNodeInsertion(native.fragmentReplaceChildren),
518
+ value: makeMultiNodeInsertion('replaceChildren', native.fragmentReplaceChildren),
411
519
  },
412
520
  ...makeHtmlParsingOverrides(native.shadowRootInnerHTML),
413
521
  });