attaform 0.20.2 → 0.21.1

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 (150) hide show
  1. package/dist/chunks/dev-key-collision-warnings.cjs +58 -0
  2. package/dist/chunks/dev-key-collision-warnings.cjs.map +1 -0
  3. package/dist/chunks/dev-key-collision-warnings.mjs +55 -0
  4. package/dist/chunks/dev-key-collision-warnings.mjs.map +1 -0
  5. package/dist/chunks/devtools.cjs +1 -1
  6. package/dist/chunks/devtools.mjs +1 -1
  7. package/dist/chunks/fingerprint.cjs +186 -0
  8. package/dist/chunks/fingerprint.cjs.map +1 -0
  9. package/dist/chunks/fingerprint.mjs +184 -0
  10. package/dist/chunks/fingerprint.mjs.map +1 -0
  11. package/dist/chunks/fingerprint2.cjs +162 -0
  12. package/dist/chunks/fingerprint2.cjs.map +1 -0
  13. package/dist/chunks/fingerprint2.mjs +160 -0
  14. package/dist/chunks/fingerprint2.mjs.map +1 -0
  15. package/dist/chunks/indexeddb.cjs +1 -1
  16. package/dist/chunks/indexeddb.mjs +1 -1
  17. package/dist/chunks/local-storage.cjs +1 -1
  18. package/dist/chunks/local-storage.mjs +1 -1
  19. package/dist/chunks/multi-tab-sync.cjs +367 -0
  20. package/dist/chunks/multi-tab-sync.cjs.map +1 -0
  21. package/dist/chunks/multi-tab-sync.mjs +364 -0
  22. package/dist/chunks/multi-tab-sync.mjs.map +1 -0
  23. package/dist/chunks/session-storage.cjs +1 -1
  24. package/dist/chunks/session-storage.mjs +1 -1
  25. package/dist/chunks/wire-persistence.cjs +396 -0
  26. package/dist/chunks/wire-persistence.cjs.map +1 -0
  27. package/dist/chunks/wire-persistence.mjs +394 -0
  28. package/dist/chunks/wire-persistence.mjs.map +1 -0
  29. package/dist/esbuild.cjs +28 -0
  30. package/dist/esbuild.cjs.map +1 -0
  31. package/dist/esbuild.d.cts +56 -0
  32. package/dist/esbuild.d.mts +56 -0
  33. package/dist/esbuild.d.ts +56 -0
  34. package/dist/esbuild.mjs +26 -0
  35. package/dist/esbuild.mjs.map +1 -0
  36. package/dist/index.cjs +5 -3
  37. package/dist/index.cjs.map +1 -1
  38. package/dist/index.d.cts +66 -70
  39. package/dist/index.d.mts +66 -70
  40. package/dist/index.d.ts +66 -70
  41. package/dist/index.mjs +5 -5
  42. package/dist/nuxt.d.cts +1 -1
  43. package/dist/nuxt.d.mts +1 -1
  44. package/dist/nuxt.d.ts +1 -1
  45. package/dist/rollup.cjs +24 -0
  46. package/dist/rollup.cjs.map +1 -0
  47. package/dist/rollup.d.cts +35 -0
  48. package/dist/rollup.d.mts +35 -0
  49. package/dist/rollup.d.ts +35 -0
  50. package/dist/rollup.mjs +22 -0
  51. package/dist/rollup.mjs.map +1 -0
  52. package/dist/rspack.cjs +10 -0
  53. package/dist/rspack.cjs.map +1 -0
  54. package/dist/rspack.d.cts +40 -0
  55. package/dist/rspack.d.mts +40 -0
  56. package/dist/rspack.d.ts +40 -0
  57. package/dist/rspack.mjs +8 -0
  58. package/dist/rspack.mjs.map +1 -0
  59. package/dist/runtime/plugins/attaform.cjs +2 -2
  60. package/dist/runtime/plugins/attaform.mjs +2 -2
  61. package/dist/shared/attaform.BJGA_UOS.mjs +37 -0
  62. package/dist/shared/attaform.BJGA_UOS.mjs.map +1 -0
  63. package/dist/shared/attaform.BRGIpZo4.cjs +26 -0
  64. package/dist/shared/attaform.BRGIpZo4.cjs.map +1 -0
  65. package/dist/shared/{attaform.DAKrGhxc.cjs → attaform.BSkvn43g.cjs} +101 -417
  66. package/dist/shared/attaform.BSkvn43g.cjs.map +1 -0
  67. package/dist/shared/{attaform.sWm8B15V.d.mts → attaform.BWfliRIK.d.cts} +172 -2
  68. package/dist/shared/{attaform.BGk8cfw2.mjs → attaform.Be8NZG9M.mjs} +178 -21
  69. package/dist/shared/attaform.Be8NZG9M.mjs.map +1 -0
  70. package/dist/shared/{attaform.D2SCCd4O.cjs → attaform.Bq5sX7TF.cjs} +2 -2
  71. package/dist/shared/{attaform.D2SCCd4O.cjs.map → attaform.Bq5sX7TF.cjs.map} +1 -1
  72. package/dist/shared/{attaform.ceGEAEMk.d.ts → attaform.Bv7dRDWK.d.ts} +172 -2
  73. package/dist/shared/attaform.C3Doa9Pt.mjs +24 -0
  74. package/dist/shared/attaform.C3Doa9Pt.mjs.map +1 -0
  75. package/dist/shared/{attaform.B_hph5AE.cjs → attaform.CICFZ1iS.cjs} +178 -20
  76. package/dist/shared/attaform.CICFZ1iS.cjs.map +1 -0
  77. package/dist/shared/attaform.CQN9R62B.cjs +39 -0
  78. package/dist/shared/attaform.CQN9R62B.cjs.map +1 -0
  79. package/dist/shared/{attaform.CwLjUqmQ.cjs → attaform.ClXwitZj.cjs} +735 -960
  80. package/dist/shared/attaform.ClXwitZj.cjs.map +1 -0
  81. package/dist/shared/{attaform.99cfHcIt.d.cts → attaform.D0dWZsJt.d.cts} +349 -77
  82. package/dist/shared/{attaform.99cfHcIt.d.mts → attaform.D0dWZsJt.d.mts} +349 -77
  83. package/dist/shared/{attaform.99cfHcIt.d.ts → attaform.D0dWZsJt.d.ts} +349 -77
  84. package/dist/shared/{attaform.z5j3LwJz.cjs → attaform.D32WwKk6.cjs} +216 -35
  85. package/dist/shared/attaform.D32WwKk6.cjs.map +1 -0
  86. package/dist/shared/{attaform.C5aYC_T8.mjs → attaform.DMEP_ENr.mjs} +39 -392
  87. package/dist/shared/attaform.DMEP_ENr.mjs.map +1 -0
  88. package/dist/shared/{attaform.tiWEVznj.mjs → attaform.DR6RmxWZ.mjs} +725 -962
  89. package/dist/shared/attaform.DR6RmxWZ.mjs.map +1 -0
  90. package/dist/shared/{attaform.Dt7dEcHk.mjs → attaform.DozgVlCE.mjs} +89 -405
  91. package/dist/shared/attaform.DozgVlCE.mjs.map +1 -0
  92. package/dist/shared/{attaform.DN5CvZrg.d.ts → attaform.Duecg2NO.d.mts} +2 -2
  93. package/dist/shared/attaform.DuzQYscR.d.cts +41 -0
  94. package/dist/shared/attaform.DuzQYscR.d.mts +41 -0
  95. package/dist/shared/attaform.DuzQYscR.d.ts +41 -0
  96. package/dist/shared/{attaform.BXinSW2T.d.mts → attaform.FudOcHaa.d.cts} +2 -2
  97. package/dist/shared/attaform.LEWUFqUw.cjs +54 -0
  98. package/dist/shared/attaform.LEWUFqUw.cjs.map +1 -0
  99. package/dist/shared/{attaform.CywE4y8x.d.cts → attaform.MtrpT6Ki.d.ts} +2 -2
  100. package/dist/shared/{attaform.DbRgDFa7.d.cts → attaform.NQ8mybyW.d.mts} +172 -2
  101. package/dist/shared/{attaform.Cd4AOfwu.cjs → attaform.S-pYLSo4.cjs} +68 -402
  102. package/dist/shared/attaform.S-pYLSo4.cjs.map +1 -0
  103. package/dist/shared/{attaform.CnrxbkB6.mjs → attaform.Y1ZGhM4k.mjs} +2 -2
  104. package/dist/shared/{attaform.CnrxbkB6.mjs.map → attaform.Y1ZGhM4k.mjs.map} +1 -1
  105. package/dist/shared/{attaform.QG5TG8lB.mjs → attaform.pmtahXKy.mjs} +216 -36
  106. package/dist/shared/attaform.pmtahXKy.mjs.map +1 -0
  107. package/dist/shared/attaform.sHkHv_98.mjs +51 -0
  108. package/dist/shared/attaform.sHkHv_98.mjs.map +1 -0
  109. package/dist/vite.cjs +9 -45
  110. package/dist/vite.cjs.map +1 -1
  111. package/dist/vite.d.cts +36 -0
  112. package/dist/vite.d.mts +36 -0
  113. package/dist/vite.d.ts +36 -0
  114. package/dist/vite.mjs +8 -44
  115. package/dist/vite.mjs.map +1 -1
  116. package/dist/webpack.cjs +10 -0
  117. package/dist/webpack.cjs.map +1 -0
  118. package/dist/webpack.d.cts +37 -0
  119. package/dist/webpack.d.mts +37 -0
  120. package/dist/webpack.d.ts +37 -0
  121. package/dist/webpack.mjs +8 -0
  122. package/dist/webpack.mjs.map +1 -0
  123. package/dist/zod-v3.cjs +3 -3
  124. package/dist/zod-v3.d.cts +3 -3
  125. package/dist/zod-v3.d.mts +3 -3
  126. package/dist/zod-v3.d.ts +3 -3
  127. package/dist/zod-v3.mjs +3 -3
  128. package/dist/zod-v4.cjs +3 -3
  129. package/dist/zod-v4.d.cts +4 -4
  130. package/dist/zod-v4.d.mts +4 -4
  131. package/dist/zod-v4.d.ts +4 -4
  132. package/dist/zod-v4.mjs +3 -3
  133. package/dist/zod.cjs +8 -8
  134. package/dist/zod.cjs.map +1 -1
  135. package/dist/zod.d.cts +52 -10
  136. package/dist/zod.d.mts +52 -10
  137. package/dist/zod.d.ts +52 -10
  138. package/dist/zod.mjs +6 -6
  139. package/dist/zod.mjs.map +1 -1
  140. package/package.json +19 -5
  141. package/dist/shared/attaform.BGk8cfw2.mjs.map +0 -1
  142. package/dist/shared/attaform.B_hph5AE.cjs.map +0 -1
  143. package/dist/shared/attaform.C5aYC_T8.mjs.map +0 -1
  144. package/dist/shared/attaform.Cd4AOfwu.cjs.map +0 -1
  145. package/dist/shared/attaform.CwLjUqmQ.cjs.map +0 -1
  146. package/dist/shared/attaform.DAKrGhxc.cjs.map +0 -1
  147. package/dist/shared/attaform.Dt7dEcHk.mjs.map +0 -1
  148. package/dist/shared/attaform.QG5TG8lB.mjs.map +0 -1
  149. package/dist/shared/attaform.tiWEVznj.mjs.map +0 -1
  150. package/dist/shared/attaform.z5j3LwJz.cjs.map +0 -1
@@ -0,0 +1,367 @@
1
+ 'use strict';
2
+
3
+ const injectWizard = require('../shared/attaform.ClXwitZj.cjs');
4
+ const paths = require('../shared/attaform.D32WwKk6.cjs');
5
+
6
+ const PROTOCOL_VERSION = 1;
7
+ const JOIN_COLLECTION_WINDOW_MS = 50;
8
+ const SNAPSHOT_TIMEOUT_MS = 200;
9
+ const MAX_LEADER_ATTEMPTS = 3;
10
+ const SNAPSHOT_RESPONSE_MIN_INTERVAL_MS = 500;
11
+ function isFileLikeValue(value) {
12
+ if (typeof File !== "undefined" && value instanceof File) return true;
13
+ if (typeof Blob !== "undefined" && value instanceof Blob) return true;
14
+ return false;
15
+ }
16
+ function isInboundShapeAcceptable(schema, path, value) {
17
+ if (isFileLikeValue(value)) return false;
18
+ let kind;
19
+ if (Array.isArray(value)) {
20
+ kind = "array";
21
+ } else if (value !== null && typeof value === "object" && injectWizard.isPlainRecord(value)) {
22
+ kind = "object";
23
+ } else {
24
+ kind = injectWizard.slimKindOf(value);
25
+ }
26
+ const accepted = schema.getSlimPrimitiveTypesAtPath(path);
27
+ if (!accepted.has(kind)) return false;
28
+ if (Array.isArray(value)) {
29
+ for (let i = 0; i < value.length; i++) {
30
+ if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false;
31
+ }
32
+ return true;
33
+ }
34
+ if (injectWizard.isPlainRecord(value)) {
35
+ for (const key of Object.keys(value)) {
36
+ if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false;
37
+ }
38
+ return true;
39
+ }
40
+ return true;
41
+ }
42
+ function diffBlankPaths(prev, curr) {
43
+ const added = [];
44
+ const removed = [];
45
+ const prevSet = new Set(prev);
46
+ for (const k of curr) if (!prevSet.has(k)) added.push(k);
47
+ for (const k of prev) if (!curr.has(k)) removed.push(k);
48
+ return { added, removed };
49
+ }
50
+ function stripSensitivePathsDeep(value, pathSoFar, isSensitivePath) {
51
+ if (isFileLikeValue(value)) return void 0;
52
+ if (value === null || typeof value !== "object") return value;
53
+ if (Array.isArray(value)) {
54
+ return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath));
55
+ }
56
+ const proto = Object.getPrototypeOf(value);
57
+ if (proto !== Object.prototype && proto !== null) return value;
58
+ const out = {};
59
+ const src = value;
60
+ for (const key of Object.keys(src)) {
61
+ const childPath = [...pathSoFar, key];
62
+ if (isSensitivePath(childPath)) {
63
+ injectWizard.safeAssign(out, key, void 0);
64
+ continue;
65
+ }
66
+ injectWizard.safeAssign(out, key, stripSensitivePathsDeep(src[key], childPath, isSensitivePath));
67
+ }
68
+ return out;
69
+ }
70
+ function isStringArray(value) {
71
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
72
+ }
73
+ function isPatchArray(value) {
74
+ return Array.isArray(value) && value.every(
75
+ (p) => p !== null && typeof p === "object" && Array.isArray(p.path)
76
+ );
77
+ }
78
+ function isValidSyncMessage(data) {
79
+ if (data === null || typeof data !== "object") return false;
80
+ const m = data;
81
+ if (m["v"] !== PROTOCOL_VERSION) return false;
82
+ if (typeof m["senderId"] !== "string") return false;
83
+ if (typeof m["kind"] !== "string") return false;
84
+ switch (m["kind"]) {
85
+ case "hello":
86
+ case "announce":
87
+ return true;
88
+ case "requestSnapshot":
89
+ return typeof m["targetId"] === "string";
90
+ case "snapshot":
91
+ return isStringArray(m["blankPaths"]) && "form" in m;
92
+ case "patches":
93
+ return isPatchArray(m["formPatches"]) && isStringArray(m["blankPathsAdded"]) && isStringArray(m["blankPathsRemoved"]);
94
+ default:
95
+ return false;
96
+ }
97
+ }
98
+ function generateSenderId() {
99
+ try {
100
+ return globalThis.crypto.randomUUID();
101
+ } catch {
102
+ return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`;
103
+ }
104
+ }
105
+ function createMultiTabSyncModule(state, channelName, options) {
106
+ if (typeof BroadcastChannel === "undefined") {
107
+ return {
108
+ dispose: () => void 0,
109
+ lifecycle: () => "established",
110
+ senderId: "",
111
+ channelName
112
+ };
113
+ }
114
+ let channel;
115
+ try {
116
+ channel = new BroadcastChannel(channelName);
117
+ } catch {
118
+ return {
119
+ dispose: () => void 0,
120
+ lifecycle: () => "established",
121
+ senderId: "",
122
+ channelName
123
+ };
124
+ }
125
+ const senderId = generateSenderId();
126
+ let lifecycle = "joining";
127
+ let disposed = false;
128
+ const peerIds = /* @__PURE__ */ new Set();
129
+ const lastSnapshotResponseAt = /* @__PURE__ */ new Map();
130
+ let joinCollectionTimer = null;
131
+ let snapshotTimeoutTimer = null;
132
+ let leaderAttempts = 0;
133
+ let prior = {
134
+ form: injectWizard.structuralSnapshot(state.form.value),
135
+ blankPathsSnapshot: [...state.blankPaths]
136
+ };
137
+ function safePost(msg) {
138
+ if (disposed) return;
139
+ try {
140
+ channel.postMessage(msg);
141
+ } catch {
142
+ }
143
+ }
144
+ function refreshPrior() {
145
+ prior = {
146
+ form: injectWizard.structuralSnapshot(state.form.value),
147
+ blankPathsSnapshot: [...state.blankPaths]
148
+ };
149
+ }
150
+ function isPathLocallySuppressed(path) {
151
+ if (options.isSensitivePath(path)) return true;
152
+ const { key } = paths.canonicalizePath([...path]);
153
+ if (options.noSyncPaths.has(key)) return true;
154
+ return false;
155
+ }
156
+ function postPatches() {
157
+ if (lifecycle !== "established") return;
158
+ const next = injectWizard.structuralSnapshot(state.form.value);
159
+ const rawPatches = [];
160
+ injectWizard.diffAndApply(prior.form, next, [], (p) => rawPatches.push(p));
161
+ const safePatches = [];
162
+ for (const p of rawPatches) {
163
+ if (isPathLocallySuppressed(p.path)) continue;
164
+ if ("value" in p && isFileLikeValue(p.value)) continue;
165
+ safePatches.push(p);
166
+ }
167
+ const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths);
168
+ if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {
169
+ prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
170
+ return;
171
+ }
172
+ safePost({
173
+ v: PROTOCOL_VERSION,
174
+ kind: "patches",
175
+ senderId,
176
+ formPatches: safePatches,
177
+ blankPathsAdded: added,
178
+ blankPathsRemoved: removed
179
+ });
180
+ prior = { form: next, blankPathsSnapshot: [...state.blankPaths] };
181
+ }
182
+ const unsubscribeChange = state.onFormChange((_next, meta) => {
183
+ if (disposed) return;
184
+ if (lifecycle !== "established") return;
185
+ if (meta?.crossTab === true) return;
186
+ if (meta?.hydration === true) {
187
+ refreshPrior();
188
+ return;
189
+ }
190
+ postPatches();
191
+ });
192
+ function applyIncomingForm(form, blankPaths) {
193
+ state.blankPaths.clear();
194
+ for (const k of blankPaths) state.blankPaths.add(k);
195
+ state.applyFormReplacement(form, { crossTab: true, persist: false });
196
+ refreshPrior();
197
+ }
198
+ function handlePatches(msg) {
199
+ if (lifecycle !== "established") return;
200
+ const safePatches = [];
201
+ for (const p of msg.formPatches) {
202
+ if (!Array.isArray(p.path)) continue;
203
+ if (isPathLocallySuppressed(p.path)) continue;
204
+ if ("value" in p && !isInboundShapeAcceptable(state.schema, p.path, p.value)) {
205
+ continue;
206
+ }
207
+ safePatches.push(p);
208
+ }
209
+ const safeBlankAdded = [];
210
+ for (const k of msg.blankPathsAdded) {
211
+ const segs = paths.canonicalizePath(k).segments;
212
+ if (isPathLocallySuppressed(segs)) continue;
213
+ safeBlankAdded.push(k);
214
+ }
215
+ const safeBlankRemoved = [];
216
+ for (const k of msg.blankPathsRemoved) {
217
+ const segs = paths.canonicalizePath(k).segments;
218
+ if (isPathLocallySuppressed(segs)) continue;
219
+ safeBlankRemoved.push(k);
220
+ }
221
+ if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {
222
+ return;
223
+ }
224
+ const candidate = injectWizard.applyPatchesForward(state.form.value, safePatches);
225
+ try {
226
+ options.validateForm(state.form.value);
227
+ try {
228
+ options.validateForm(candidate);
229
+ } catch {
230
+ return;
231
+ }
232
+ } catch {
233
+ }
234
+ const nextBlankPaths = new Set(state.blankPaths);
235
+ for (const k of safeBlankRemoved) nextBlankPaths.delete(k);
236
+ for (const k of safeBlankAdded) nextBlankPaths.add(k);
237
+ applyIncomingForm(candidate, [...nextBlankPaths]);
238
+ }
239
+ function handleSnapshot(msg) {
240
+ if (lifecycle !== "joining") return;
241
+ if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return;
242
+ if (snapshotTimeoutTimer !== null) {
243
+ clearTimeout(snapshotTimeoutTimer);
244
+ snapshotTimeoutTimer = null;
245
+ }
246
+ if (joinCollectionTimer !== null) {
247
+ clearTimeout(joinCollectionTimer);
248
+ joinCollectionTimer = null;
249
+ }
250
+ applyIncomingForm(msg.form, msg.blankPaths);
251
+ lifecycle = "established";
252
+ peerIds.clear();
253
+ }
254
+ function respondToHello() {
255
+ safePost({ v: PROTOCOL_VERSION, kind: "announce", senderId });
256
+ }
257
+ function respondToSnapshotRequest(requesterId) {
258
+ const now = Date.now();
259
+ const last = lastSnapshotResponseAt.get(requesterId);
260
+ if (last !== void 0 && now - last < SNAPSHOT_RESPONSE_MIN_INTERVAL_MS) return;
261
+ lastSnapshotResponseAt.set(requesterId, now);
262
+ const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath);
263
+ safePost({
264
+ v: PROTOCOL_VERSION,
265
+ kind: "snapshot",
266
+ senderId,
267
+ form: scrubbedForm,
268
+ blankPaths: [...state.blankPaths]
269
+ });
270
+ }
271
+ channel.onmessage = (event) => {
272
+ if (disposed) return;
273
+ try {
274
+ const data = event.data;
275
+ if (!isValidSyncMessage(data)) return;
276
+ const msg = data;
277
+ if (msg.senderId === senderId) return;
278
+ switch (msg.kind) {
279
+ case "hello":
280
+ if (lifecycle !== "established") return;
281
+ respondToHello();
282
+ break;
283
+ case "announce":
284
+ if (lifecycle === "joining") peerIds.add(msg.senderId);
285
+ break;
286
+ case "requestSnapshot":
287
+ if (lifecycle !== "established") return;
288
+ if (msg.targetId !== senderId) return;
289
+ respondToSnapshotRequest(msg.senderId);
290
+ break;
291
+ case "snapshot":
292
+ handleSnapshot(msg);
293
+ break;
294
+ case "patches":
295
+ handlePatches(msg);
296
+ break;
297
+ }
298
+ } catch {
299
+ }
300
+ };
301
+ function electLeaderAndRequest() {
302
+ if (disposed) return;
303
+ if (peerIds.size === 0) {
304
+ lifecycle = "established";
305
+ refreshPrior();
306
+ return;
307
+ }
308
+ const sorted = [...peerIds].sort();
309
+ const leaderId = sorted[0];
310
+ peerIds.delete(leaderId);
311
+ leaderAttempts++;
312
+ safePost({
313
+ v: PROTOCOL_VERSION,
314
+ kind: "requestSnapshot",
315
+ senderId,
316
+ targetId: leaderId
317
+ });
318
+ snapshotTimeoutTimer = setTimeout(() => {
319
+ snapshotTimeoutTimer = null;
320
+ if (disposed) return;
321
+ if (lifecycle === "established") return;
322
+ if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {
323
+ lifecycle = "established";
324
+ refreshPrior();
325
+ return;
326
+ }
327
+ electLeaderAndRequest();
328
+ }, SNAPSHOT_TIMEOUT_MS);
329
+ }
330
+ function joinFlow() {
331
+ safePost({ v: PROTOCOL_VERSION, kind: "hello", senderId });
332
+ joinCollectionTimer = setTimeout(() => {
333
+ joinCollectionTimer = null;
334
+ if (disposed) return;
335
+ if (lifecycle === "established") return;
336
+ electLeaderAndRequest();
337
+ }, JOIN_COLLECTION_WINDOW_MS);
338
+ }
339
+ joinFlow();
340
+ return {
341
+ dispose: () => {
342
+ if (disposed) return;
343
+ disposed = true;
344
+ if (joinCollectionTimer !== null) {
345
+ clearTimeout(joinCollectionTimer);
346
+ joinCollectionTimer = null;
347
+ }
348
+ if (snapshotTimeoutTimer !== null) {
349
+ clearTimeout(snapshotTimeoutTimer);
350
+ snapshotTimeoutTimer = null;
351
+ }
352
+ unsubscribeChange();
353
+ try {
354
+ channel.close();
355
+ } catch {
356
+ }
357
+ },
358
+ lifecycle: () => lifecycle,
359
+ senderId,
360
+ channelName
361
+ };
362
+ }
363
+ const MULTI_TAB_SYNC_MODULE_KEY = "multiTabSync";
364
+
365
+ exports.MULTI_TAB_SYNC_MODULE_KEY = MULTI_TAB_SYNC_MODULE_KEY;
366
+ exports.createMultiTabSyncModule = createMultiTabSyncModule;
367
+ //# sourceMappingURL=multi-tab-sync.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multi-tab-sync.cjs","sources":["../../src/runtime/core/multi-tab-sync.ts"],"sourcesContent":["import type { AbstractSchema, SlimPrimitiveKind } from '../types/types-api'\nimport type { GenericForm } from '../types/types-core'\nimport type { FormStore } from './create-form-store'\nimport { applyPatchesForward, diffAndApply, structuralSnapshot, type Patch } from './diff-apply'\nimport { isPlainRecord } from './path-walker'\nimport { canonicalizePath, type Path, type PathKey } from './paths'\nimport { safeAssign } from './safe-assign'\nimport { slimKindOf } from './slim-primitive-gate'\n\n/**\n * Cross-tab form-state synchronisation over a `BroadcastChannel`.\n *\n * **Identity model.** Two `useForm({ key, schema })` callsites in\n * same-origin tabs join the same channel by deriving its name from the\n * consumer-supplied `formKey` + the schema fingerprint. Tabs auto-pair\n * without an opt-in flag; the only switch is `multiTab: false` to\n * disable. Anonymous (auto-keyed) forms skip the module entirely (no\n * shared identity → no channel to join).\n *\n * **Handshake.** A joining tab posts `{kind: 'hello'}`. Every\n * established tab responds with `{kind: 'announce', senderId}` (UUID\n * only — cheap). The joining tab waits a short collection window\n * (~50ms), picks the lowest-sorted `senderId` as leader, and posts\n * `{kind: 'requestSnapshot', targetId: leaderId}`. Only the leader\n * replies with `{kind: 'snapshot', form, blankPaths}`. Bandwidth on\n * N-tab join is N tiny announces + 1 snapshot — vs. the naive\n * \"everyone responds with a full snapshot\" which would be O(N) full\n * snapshots.\n *\n * **Steady state.** Every local mutation fires an `onFormChange`\n * listener that diffs against a per-module prior snapshot and posts\n * `{kind: 'patches', formPatches, blankPathsAdded, blankPathsRemoved}`\n * over the channel. Receivers apply via `applyPatchesForward` +\n * `state.applyFormReplacement(merged, { crossTab: true, persist: false })`.\n *\n * **Defenses (see the recipe's Security section for the threat model).**\n * 1. **`senderId` echo drop.** Per-module UUID stamped on every\n * outbound message; receivers drop messages whose `senderId` ===\n * own. Handles intra-tab self-loops (two `useForm({key:'x'})` in\n * one tab) and any UA echo behaviour.\n * 2. **Protocol versioning.** Every message carries `v: 1`; receivers\n * drop unknown versions silently. Lets us evolve the wire format\n * without silently corrupting older tabs running stale bundles.\n * 3. **Sensitive-path filtering.** Outbound strips patches at paths\n * matching `options.isSensitivePath`; inbound REJECTS the same\n * paths (defense in depth — the wire is never trusted, even though\n * the originating tab \"should have\" stripped them).\n * 4. **Path-segment safety.** Inbound rejects patches containing\n * `__proto__` / `constructor` / `prototype` — prototype-pollution\n * defense before `applyPatchesForward` touches the form.\n * 5. **Post-apply schema validate + rollback.** After applying\n * surviving patches, the caller's `validateForm` callback runs; on\n * throw the entire message is dropped and the form state stays\n * where it was.\n * 6. **Per-register opt-out (`noSyncPaths`).** Paths the consumer\n * marked `register('x', { multiTab: false })` are stripped on\n * outbound AND rejected on inbound — symmetric tab-local\n * behaviour for selected fields.\n *\n * **History stays local.** Inbound applies set `meta.crossTab: true`;\n * the history listener updates its diff anchor but does NOT push a\n * delta. `undo()` walks the local user's intent, not a sibling tab's.\n */\n\nexport type MultiTabSyncModule = {\n /** Tear down the channel + outbound listener. */\n dispose(): void\n /**\n * Lifecycle observable for tests. `'joining'` during the\n * mount-time handshake collection window; `'established'` once a\n * snapshot has arrived (or the join timed out into solo-tab mode).\n */\n readonly lifecycle: () => 'joining' | 'established'\n /**\n * The module's UUID. Stamped on every outbound message; receivers\n * drop messages with their own `senderId`. Exposed for tests that\n * need to verify echo-drop or fabricate hostile messages.\n */\n readonly senderId: string\n /** The channel name this module is bound to. Exposed for tests. */\n readonly channelName: string\n}\n\nexport type MultiTabSyncOptions<F> = {\n /**\n * Sensitive-path predicate threaded from the FormStore. Patches at\n * matching paths are stripped from outbound AND rejected on inbound\n * — defense in depth.\n */\n readonly isSensitivePath: (path: Path | PathKey | string) => boolean\n /**\n * Per-register opt-out registry — paths the consumer marked\n * `{ multiTab: false }` are tab-local in BOTH directions. Empty by\n * default; populated by Phase 7's register hook.\n */\n readonly noSyncPaths: ReadonlySet<PathKey>\n /**\n * Post-apply schema validation. Called by the inbound handler on\n * the candidate form value AFTER patches apply but BEFORE the\n * candidate replaces live state. MUST throw on validation failure;\n * the inbound handler catches and rolls back.\n *\n * Adapter implementations should call\n * `state.schema.validateAtPath(form, undefined, { sync: true })` and\n * throw on any non-success-sync result (or skip validation entirely\n * for async-only schemas — see the recipe for rationale).\n */\n readonly validateForm: (form: F) => void\n}\n\n/** Wire-format version. Bumps require coordinated deploys. */\nconst PROTOCOL_VERSION = 1 as const\n\n/** Collection window for `announce` messages during the join flow. */\nconst JOIN_COLLECTION_WINDOW_MS = 50\n/** Timeout per `requestSnapshot` attempt before falling back to next-lowest leader. */\nconst SNAPSHOT_TIMEOUT_MS = 200\n/** Max retries for leader-election before giving up and proceeding solo. */\nconst MAX_LEADER_ATTEMPTS = 3\n/**\n * Minimum gap between snapshot replies to the SAME requesting sender.\n * Each reply is a full-form deep-clone + sensitive-scrub + serialise, so\n * an unthrottled responder is a CPU-amplification target for a hostile\n * same-origin tab that spams `requestSnapshot`. A legitimate joiner\n * sends a leader exactly one request (a lost reply retries the\n * next-lowest leader, not the same one), so this never drops real\n * traffic. Comfortably above `SNAPSHOT_TIMEOUT_MS` for that reason.\n */\nconst SNAPSHOT_RESPONSE_MIN_INTERVAL_MS = 500\n\ntype SyncMessage<F> =\n | { readonly v: 1; readonly kind: 'hello'; readonly senderId: string }\n | { readonly v: 1; readonly kind: 'announce'; readonly senderId: string }\n | {\n readonly v: 1\n readonly kind: 'requestSnapshot'\n readonly senderId: string\n readonly targetId: string\n }\n | {\n readonly v: 1\n readonly kind: 'snapshot'\n readonly senderId: string\n readonly form: F\n readonly blankPaths: readonly PathKey[]\n }\n | {\n readonly v: 1\n readonly kind: 'patches'\n readonly senderId: string\n readonly formPatches: readonly Patch[]\n readonly blankPathsAdded: readonly PathKey[]\n readonly blankPathsRemoved: readonly PathKey[]\n }\n\ntype Lifecycle = 'joining' | 'established'\n\ntype SnapshotState<F> = {\n readonly form: F\n readonly blankPathsSnapshot: ReadonlyArray<PathKey>\n}\n\n/**\n * `File` and `Blob` values are never sent or accepted over the\n * cross-tab channel. Two reasons:\n *\n * 1. **Security.** File blobs are user-private content (passport scans,\n * tax forms, ID documents, attachment uploads). If a sibling tab is\n * open on the same origin (shared computer, forgotten popup, hostile\n * XSS-opened window), an unconditional broadcast lets it capture the\n * selected File before the user even submits. The default-deny stance\n * is the only safe one; a dev who genuinely needs cross-tab file\n * sharing serialises to a string (base64, blob URL) at a different\n * field and accepts the explicit trade-off.\n * 2. **Performance.** `structuredClone` of a multi-megabyte File is\n * measurable on the originator (synchronous clone) and the receiver\n * (synchronous deserialise). The channel is for low-cost coordination\n * signals, not bulk content transfer.\n *\n * This predicate gates both directions: outbound traffic strips\n * File-valued patches and File leaves on snapshot scrubbing, and\n * inbound traffic rejects File-valued patches / File leaves on\n * snapshot apply (defense in depth, in case a peer bundles an older\n * version that didn't strip).\n */\nfunction isFileLikeValue(value: unknown): boolean {\n if (typeof File !== 'undefined' && value instanceof File) return true\n if (typeof Blob !== 'undefined' && value instanceof Blob) return true\n return false\n}\n\n/**\n * Walk an inbound value tree and verify every leaf's primitive kind\n * matches the schema's slim accept-set at its sub-path. Returns\n * `false` on the first mismatch.\n *\n * Mirrors the local-write slim-primitive gate (`isSlimPrimitiveValid`)\n * but without its dev-warn side effect: an inbound rejection is a\n * sibling-tab integrity issue, not a local-write bug, so the warn\n * message (\"writing X to Y\") would mislead the dev about the origin.\n * Cross-tab rejection is a silent drop; the channel converges on\n * the next valid message from another peer.\n *\n * An empty accept-set (unresolvable path or `never`-typed schema\n * leaf) rejects every kind, so a path that doesn't exist in the\n * schema at all also fails here.\n */\nfunction isInboundShapeAcceptable(\n schema: AbstractSchema<unknown, unknown>,\n path: Path,\n value: unknown\n): boolean {\n // File / Blob values never traverse the channel. Even if a hostile\n // or older peer sent one, we drop it before any apply runs.\n if (isFileLikeValue(value)) return false\n let kind: SlimPrimitiveKind\n if (Array.isArray(value)) {\n kind = 'array'\n } else if (value !== null && typeof value === 'object' && isPlainRecord(value)) {\n kind = 'object'\n } else {\n kind = slimKindOf(value)\n }\n const accepted = schema.getSlimPrimitiveTypesAtPath(path)\n if (!accepted.has(kind)) return false\n if (Array.isArray(value)) {\n for (let i = 0; i < value.length; i++) {\n if (!isInboundShapeAcceptable(schema, [...path, i], value[i])) return false\n }\n return true\n }\n if (isPlainRecord(value)) {\n for (const key of Object.keys(value)) {\n if (!isInboundShapeAcceptable(schema, [...path, key], value[key])) return false\n }\n return true\n }\n return true\n}\n\nfunction diffBlankPaths(\n prev: ReadonlyArray<PathKey>,\n curr: ReadonlySet<PathKey>\n): { added: PathKey[]; removed: PathKey[] } {\n const added: PathKey[] = []\n const removed: PathKey[] = []\n const prevSet = new Set<PathKey>(prev)\n for (const k of curr) if (!prevSet.has(k)) added.push(k)\n for (const k of prev) if (!curr.has(k)) removed.push(k)\n return { added, removed }\n}\n\n/**\n * Deep-clone `value` while substituting any leaf whose enclosing path\n * matches `isSensitivePath` with `undefined`. Used to scrub\n * snapshots before posting them — even on the originating tab, a\n * snapshot in response to `hello` should not carry sensitive values\n * to fresh-mount siblings.\n */\nfunction stripSensitivePathsDeep(\n value: unknown,\n pathSoFar: Path,\n isSensitivePath: (p: Path) => boolean\n): unknown {\n // File / Blob values are unconditionally stripped from cross-tab\n // traffic regardless of the path's sensitive-name match. See the\n // `isFileLikeValue` rationale above.\n if (isFileLikeValue(value)) return undefined\n if (value === null || typeof value !== 'object') return value\n if (Array.isArray(value)) {\n return value.map((item, i) => stripSensitivePathsDeep(item, [...pathSoFar, i], isSensitivePath))\n }\n const proto = Object.getPrototypeOf(value)\n if (proto !== Object.prototype && proto !== null) return value\n // Scrub container mirrors `structuralSnapshot`'s allocator. The\n // outgoing snapshot is passed through `structuredClone` by\n // `BroadcastChannel.postMessage` before crossing the tab boundary\n // (which would discard the prototype anyway), but matching the rest\n // of the runtime's `Object.prototype` shape keeps the in-process\n // shape consistent. `safeAssign` lands a literal `__proto__` key as\n // an own data property regardless of the source.\n const out: Record<string, unknown> = {}\n const src = value as Record<string, unknown>\n for (const key of Object.keys(src)) {\n const childPath = [...pathSoFar, key]\n if (isSensitivePath(childPath)) {\n safeAssign(out, key, undefined)\n continue\n }\n safeAssign(out, key, stripSensitivePathsDeep(src[key], childPath, isSensitivePath))\n }\n return out\n}\n\n/** Every element is a string (the on-wire form of a `PathKey`). */\nfunction isStringArray(value: unknown): boolean {\n return Array.isArray(value) && value.every((item) => typeof item === 'string')\n}\n\n/** Every element is a patch-shaped object with an array `path`. */\nfunction isPatchArray(value: unknown): boolean {\n return (\n Array.isArray(value) &&\n value.every(\n (p) => p !== null && typeof p === 'object' && Array.isArray((p as { path?: unknown }).path)\n )\n )\n}\n\n/**\n * Type-guard for incoming messages. Validates the structural shape\n * before dispatch so a hostile sender can't crash the listener with\n * garbage. Rejects messages missing `v` / `kind` / `senderId`, and\n * validates the ELEMENT types of the array payloads — a blank-path\n * array of non-strings (`[null]`) or a patch array of non-patches would\n * otherwise survive the bare `Array.isArray` check and trip a\n * downstream walk (`canonicalizePath(null)` throws). The `onmessage`\n * try/catch is the backstop; this rejects the common shape at the door.\n */\nfunction isValidSyncMessage(data: unknown): data is SyncMessage<unknown> {\n if (data === null || typeof data !== 'object') return false\n const m = data as Record<string, unknown>\n if (m['v'] !== PROTOCOL_VERSION) return false\n if (typeof m['senderId'] !== 'string') return false\n if (typeof m['kind'] !== 'string') return false\n switch (m['kind']) {\n case 'hello':\n case 'announce':\n return true\n case 'requestSnapshot':\n return typeof m['targetId'] === 'string'\n case 'snapshot':\n return isStringArray(m['blankPaths']) && 'form' in m\n case 'patches':\n return (\n isPatchArray(m['formPatches']) &&\n isStringArray(m['blankPathsAdded']) &&\n isStringArray(m['blankPathsRemoved'])\n )\n default:\n return false\n }\n}\n\nfunction generateSenderId(): string {\n try {\n // Available in evergreen browsers and Node 19+. Library code calls\n // it elsewhere (e.g. instanceId allocation), so the dependency is\n // already on the runtime surface.\n return globalThis.crypto.randomUUID()\n } catch {\n // Pathological fallback for ancient environments — collision\n // resistance is reduced but the module still functions; same-tab\n // dedup is the only consumer of senderId equality and intra-tab\n // collisions are vanishingly unlikely.\n return `atta-${Math.random().toString(36).slice(2)}-${Date.now().toString(36)}`\n }\n}\n\n/**\n * Construct a cross-tab sync module bound to `channelName`. Returns a\n * stub no-op module if `BroadcastChannel` is unavailable in the\n * runtime — callers should already have gated on availability +\n * `window.isSecureContext`, but the guard makes the module safe to\n * instantiate from any environment.\n */\nexport function createMultiTabSyncModule<F extends GenericForm>(\n state: FormStore<F, GenericForm>,\n channelName: string,\n options: MultiTabSyncOptions<F>\n): MultiTabSyncModule {\n if (typeof BroadcastChannel === 'undefined') {\n return {\n dispose: () => undefined,\n lifecycle: () => 'established',\n senderId: '',\n channelName,\n }\n }\n\n let channel: BroadcastChannel\n try {\n channel = new BroadcastChannel(channelName)\n } catch {\n return {\n dispose: () => undefined,\n lifecycle: () => 'established',\n senderId: '',\n channelName,\n }\n }\n\n const senderId = generateSenderId()\n let lifecycle: Lifecycle = 'joining'\n let disposed = false\n\n // Ephemeral roster for the join flow. Populated by `announce`\n // messages during the collection window; consumed by the leader\n // election. Cleared once the joining tab transitions to\n // `'established'` (no further use case for the set).\n const peerIds = new Set<string>()\n // Last snapshot-reply timestamp per requesting sender — throttles the\n // expensive scrub+serialise against a spamming peer (SEC-4).\n const lastSnapshotResponseAt = new Map<string, number>()\n let joinCollectionTimer: ReturnType<typeof setTimeout> | null = null\n let snapshotTimeoutTimer: ReturnType<typeof setTimeout> | null = null\n let leaderAttempts = 0\n\n // Per-module prior anchor for outbound diffs. Refreshed after every\n // posted `patches` message AND after every accepted inbound apply\n // (so the next local diff is against post-apply state, not the\n // stale pre-apply form). `structuralSnapshot` walks the object +\n // array spine and leaves non-descendable leaves (BigInt, Date, Map,\n // Set, class instances) by reference: fine as a diff anchor, and\n // BroadcastChannel's `postMessage` structuredClone handles those leaf\n // types when the diff'd patches go over the wire.\n let prior: SnapshotState<F> = {\n form: structuralSnapshot(state.form.value),\n blankPathsSnapshot: [...state.blankPaths],\n }\n\n function safePost(msg: SyncMessage<F>): void {\n if (disposed) return\n try {\n channel.postMessage(msg)\n } catch {\n // Channel closed under our feet (e.g., tab navigating away\n // while a postMessage queues). Drop silently.\n }\n }\n\n function refreshPrior(): void {\n prior = {\n form: structuralSnapshot(state.form.value),\n blankPathsSnapshot: [...state.blankPaths],\n }\n }\n\n function isPathLocallySuppressed(path: Path): boolean {\n // The dangerous-segment guard the SEC-2 audit added here is now\n // load-bearing on `setAtPath`'s proto-less intermediates AND\n // `structuralSnapshot`'s proto-less containers. With both in place\n // the patch-apply pipeline writes `__proto__` / `constructor` /\n // `prototype` segments as ordinary own-property pairs that never\n // walk into `Object.prototype`, so the gate is no longer needed\n // and dropping it lets a legit schema field literally named\n // `prototype` sync cross-tab like every other field.\n if (options.isSensitivePath(path)) return true\n const { key } = canonicalizePath([...path])\n if (options.noSyncPaths.has(key)) return true\n return false\n }\n\n function postPatches(): void {\n if (lifecycle !== 'established') return\n const next = structuralSnapshot(state.form.value)\n const rawPatches: Patch[] = []\n diffAndApply(prior.form, next, [], (p) => rawPatches.push(p))\n const safePatches: Patch[] = []\n for (const p of rawPatches) {\n if (isPathLocallySuppressed(p.path)) continue\n // Strip File / Blob-valued patches before they reach the channel.\n // Same default-deny rationale as the snapshot-side scrub above.\n if ('value' in p && isFileLikeValue((p as { value: unknown }).value)) continue\n safePatches.push(p)\n }\n const { added, removed } = diffBlankPaths(prior.blankPathsSnapshot, state.blankPaths)\n // Suppress noisy zero-delta posts (e.g., the form changed only at\n // a sensitive path — after filtering, nothing remains).\n if (safePatches.length === 0 && added.length === 0 && removed.length === 0) {\n prior = { form: next, blankPathsSnapshot: [...state.blankPaths] }\n return\n }\n safePost({\n v: PROTOCOL_VERSION,\n kind: 'patches',\n senderId,\n formPatches: safePatches,\n blankPathsAdded: added,\n blankPathsRemoved: removed,\n })\n prior = { form: next, blankPathsSnapshot: [...state.blankPaths] }\n }\n\n // Outbound: every local non-crossTab non-hydration mutation diffs\n // against `prior` and posts the resulting patches. Joining tabs\n // suppress their own outbound traffic until they're established —\n // otherwise they'd broadcast pre-handshake noise.\n const unsubscribeChange = state.onFormChange((_next, meta) => {\n if (disposed) return\n if (lifecycle !== 'established') return\n if (meta?.crossTab === true) return\n if (meta?.hydration === true) {\n // Hydration realigned local state; refresh the diff anchor\n // without posting. Siblings hydrate independently to the same\n // value, so channel traffic would be wasted.\n refreshPrior()\n return\n }\n postPatches()\n })\n\n function applyIncomingForm(form: F, blankPaths: ReadonlyArray<PathKey>): void {\n // Sync blank-paths set BEFORE applying the form value so the\n // resulting `onFormChange` emission sees a coherent (form,\n // blankPaths) pair. Listeners that read both during a single\n // microtick (history, persistence, devtools) wouldn't observe\n // mid-update divergence.\n state.blankPaths.clear()\n for (const k of blankPaths) state.blankPaths.add(k)\n state.applyFormReplacement(form, { crossTab: true, persist: false })\n refreshPrior()\n }\n\n function handlePatches(msg: SyncMessage<F> & { kind: 'patches' }): void {\n if (lifecycle !== 'established') return\n const safePatches: Patch[] = []\n for (const p of msg.formPatches) {\n if (!Array.isArray(p.path)) continue\n if (isPathLocallySuppressed(p.path)) continue\n // Slim-shape check on the patch's value at its path. A peer\n // sending a number where the schema expects a string (whether\n // via a buggy bundle or a hostile tab) is rejected before the\n // patch ever lands on the local store. Patches without a\n // `value` field (e.g., `remove` ops) skip the check.\n if (\n 'value' in p &&\n !isInboundShapeAcceptable(state.schema, p.path, (p as { value: unknown }).value)\n ) {\n continue\n }\n safePatches.push(p)\n }\n // Filter blank-path deltas with the same predicate. A sensitive\n // blank-path key shouldn't leak via the membership signal either.\n const safeBlankAdded: PathKey[] = []\n for (const k of msg.blankPathsAdded) {\n const segs = canonicalizePath(k).segments\n if (isPathLocallySuppressed(segs)) continue\n safeBlankAdded.push(k)\n }\n const safeBlankRemoved: PathKey[] = []\n for (const k of msg.blankPathsRemoved) {\n const segs = canonicalizePath(k).segments\n if (isPathLocallySuppressed(segs)) continue\n safeBlankRemoved.push(k)\n }\n if (safePatches.length === 0 && safeBlankAdded.length === 0 && safeBlankRemoved.length === 0) {\n return\n }\n const candidate = applyPatchesForward(state.form.value, safePatches) as F\n // Post-apply schema validation runs ONLY if the pre-apply form\n // was already valid — otherwise a form that mounts in an\n // intentionally-invalid state (empty defaults that violate\n // refinements, mid-edit field-array seeds) would reject every\n // remote update. The local `validateOn` cycle surfaces any\n // resulting errors on the receiver normally.\n try {\n options.validateForm(state.form.value)\n try {\n options.validateForm(candidate)\n } catch {\n // Patches would have invalidated a previously-valid form —\n // rollback. The originating tab's mutation is dropped on\n // this receiver; last-writer-wins re-converges on the next\n // valid mutation.\n return\n }\n } catch {\n // Pre-apply form was already invalid; skip post-validation\n // (no new attack surface — the form was already in an invalid\n // state from the user's POV, the local validate cycle\n // surfaces errors regardless of where the data came from).\n }\n const nextBlankPaths = new Set(state.blankPaths)\n for (const k of safeBlankRemoved) nextBlankPaths.delete(k)\n for (const k of safeBlankAdded) nextBlankPaths.add(k)\n applyIncomingForm(candidate, [...nextBlankPaths])\n }\n\n function handleSnapshot(msg: SyncMessage<F> & { kind: 'snapshot' }): void {\n if (lifecycle !== 'joining') return\n // No deep-validate gate here. Snapshots carry the leader's live\n // state, including mid-edit values that wouldn't parse cleanly yet\n // (a half-typed email, a string still under its min length). Every\n // peer in a shared channel holds the same in-progress state, so a\n // deep-validate gate would force the joiner to drop every leader\n // and fall back to solo mode with empty defaults, silently throwing\n // away the live shared state the user is editing in another tab.\n //\n // The slim-shape check below IS still in force though: an inbound\n // form whose primitive shape doesn't match the schema (e.g., a\n // number where a string is expected) is structural garbage, not a\n // mid-edit value, and would corrupt the local store.\n if (!isInboundShapeAcceptable(state.schema, [], msg.form)) return\n if (snapshotTimeoutTimer !== null) {\n clearTimeout(snapshotTimeoutTimer)\n snapshotTimeoutTimer = null\n }\n if (joinCollectionTimer !== null) {\n clearTimeout(joinCollectionTimer)\n joinCollectionTimer = null\n }\n applyIncomingForm(msg.form, msg.blankPaths)\n lifecycle = 'established'\n peerIds.clear()\n }\n\n function respondToHello(): void {\n safePost({ v: PROTOCOL_VERSION, kind: 'announce', senderId })\n }\n\n function respondToSnapshotRequest(requesterId: string): void {\n // Throttle per requester: a hostile tab that learned our senderId\n // could otherwise spam requests and force a full scrub+serialise on\n // each. A real joiner only asks once, so this is invisible to it.\n const now = Date.now()\n const last = lastSnapshotResponseAt.get(requesterId)\n if (last !== undefined && now - last < SNAPSHOT_RESPONSE_MIN_INTERVAL_MS) return\n lastSnapshotResponseAt.set(requesterId, now)\n const scrubbedForm = stripSensitivePathsDeep(state.form.value, [], options.isSensitivePath) as F\n safePost({\n v: PROTOCOL_VERSION,\n kind: 'snapshot',\n senderId,\n form: scrubbedForm,\n blankPaths: [...state.blankPaths],\n })\n }\n\n channel.onmessage = (event: MessageEvent): void => {\n if (disposed) return\n // Backstop: this callback is invoked by the platform, so any throw\n // escapes uncaught into the consumer app. A same-origin hostile or\n // stale-bundle peer can post a message that survives the shape guard\n // yet trips a downstream walk. Drop it; the channel reconverges on\n // the next valid message. The library never throws from a callback\n // it owns.\n try {\n const data = event.data\n if (!isValidSyncMessage(data)) return\n const msg = data as SyncMessage<F>\n // Echo drop — own messages NEVER apply (intra-tab self-loop +\n // any UA echo behaviour).\n if (msg.senderId === senderId) return\n switch (msg.kind) {\n case 'hello':\n if (lifecycle !== 'established') return\n respondToHello()\n break\n case 'announce':\n if (lifecycle === 'joining') peerIds.add(msg.senderId)\n break\n case 'requestSnapshot':\n if (lifecycle !== 'established') return\n if (msg.targetId !== senderId) return\n respondToSnapshotRequest(msg.senderId)\n break\n case 'snapshot':\n handleSnapshot(msg)\n break\n case 'patches':\n handlePatches(msg)\n break\n }\n } catch {\n // Malformed / hostile inbound message reached a throwing path.\n }\n }\n\n function electLeaderAndRequest(): void {\n if (disposed) return\n if (peerIds.size === 0) {\n // No siblings answered — proceed solo. Local hydration/defaults\n // become this tab's baseline.\n lifecycle = 'established'\n refreshPrior()\n return\n }\n const sorted = [...peerIds].sort()\n const leaderId = sorted[0] as string\n peerIds.delete(leaderId)\n leaderAttempts++\n safePost({\n v: PROTOCOL_VERSION,\n kind: 'requestSnapshot',\n senderId,\n targetId: leaderId,\n })\n snapshotTimeoutTimer = setTimeout(() => {\n snapshotTimeoutTimer = null\n if (disposed) return\n if (lifecycle === 'established') return\n if (leaderAttempts >= MAX_LEADER_ATTEMPTS || peerIds.size === 0) {\n // Out of retries / out of candidates — fall back to solo.\n lifecycle = 'established'\n refreshPrior()\n return\n }\n electLeaderAndRequest()\n }, SNAPSHOT_TIMEOUT_MS)\n }\n\n function joinFlow(): void {\n safePost({ v: PROTOCOL_VERSION, kind: 'hello', senderId })\n joinCollectionTimer = setTimeout(() => {\n joinCollectionTimer = null\n if (disposed) return\n if (lifecycle === 'established') return\n electLeaderAndRequest()\n }, JOIN_COLLECTION_WINDOW_MS)\n }\n\n joinFlow()\n\n return {\n dispose: () => {\n if (disposed) return\n disposed = true\n if (joinCollectionTimer !== null) {\n clearTimeout(joinCollectionTimer)\n joinCollectionTimer = null\n }\n if (snapshotTimeoutTimer !== null) {\n clearTimeout(snapshotTimeoutTimer)\n snapshotTimeoutTimer = null\n }\n unsubscribeChange()\n try {\n channel.close()\n } catch {\n // No-op — close failures are non-recoverable.\n }\n },\n lifecycle: () => lifecycle,\n senderId,\n channelName,\n }\n}\n\n/** Shared module-cache key used by `state.modules.set/get`. */\nexport const MULTI_TAB_SYNC_MODULE_KEY = 'multiTabSync'\n"],"names":["isPlainRecord","slimKindOf","safeAssign","structuralSnapshot","canonicalizePath","diffAndApply","applyPatchesForward"],"mappings":"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"}