@fanfare-io/fanfare-sdk-core 0.1.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/LICENSE +202 -0
- package/README.md +44 -0
- package/dist/adapters/google-analytics.d.ts +12 -0
- package/dist/adapters/google-analytics.js +1 -0
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/types.d.ts +9 -0
- package/dist/appointments/appointment.module.d.ts +32 -0
- package/dist/appointments/appointment.module.js +1 -0
- package/dist/appointments/index.d.ts +2 -0
- package/dist/appointments/public.d.ts +1 -0
- package/dist/appointments/public.js +1 -0
- package/dist/appointments/types.d.ts +47 -0
- package/dist/auctions/auction.module.d.ts +58 -0
- package/dist/auctions/auction.module.js +1 -0
- package/dist/auctions/index.d.ts +5 -0
- package/dist/auctions/public.d.ts +5 -0
- package/dist/auctions/public.js +1 -0
- package/dist/auctions/types.d.ts +97 -0
- package/dist/auth/auth.module.d.ts +71 -0
- package/dist/auth/auth.module.js +1 -0
- package/dist/auth/index.d.ts +1 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/types.d.ts +112 -0
- package/dist/beacon/batching.d.ts +52 -0
- package/dist/beacon/batching.js +1 -0
- package/dist/beacon/beacon.module.d.ts +58 -0
- package/dist/beacon/beacon.module.js +1 -0
- package/dist/beacon/enrichment.d.ts +16 -0
- package/dist/beacon/enrichment.js +1 -0
- package/dist/beacon/index.d.ts +12 -0
- package/dist/beacon/public.d.ts +9 -0
- package/dist/beacon/public.js +1 -0
- package/dist/beacon/types.d.ts +103 -0
- package/dist/beacon/validation.d.ts +24 -0
- package/dist/beacon/validation.js +1 -0
- package/dist/challenges/challenge.module.d.ts +9 -0
- package/dist/challenges/challenge.module.js +1 -0
- package/dist/challenges/index.d.ts +3 -0
- package/dist/challenges/public.d.ts +9 -0
- package/dist/challenges/public.js +1 -0
- package/dist/challenges/types.d.ts +33 -0
- package/dist/config/index.d.ts +44 -0
- package/dist/config/index.js +1 -0
- package/dist/core/client.d.ts +15 -0
- package/dist/core/client.js +2 -0
- package/dist/core/errors.d.ts +175 -0
- package/dist/core/errors.js +1 -0
- package/dist/core/http.d.ts +87 -0
- package/dist/core/http.js +1 -0
- package/dist/core/logger.d.ts +46 -0
- package/dist/core/logger.js +1 -0
- package/dist/core/money.d.ts +10 -0
- package/dist/core/money.js +1 -0
- package/dist/core/parse-response.d.ts +62 -0
- package/dist/core/parse-response.js +1 -0
- package/dist/core/utils.d.ts +67 -0
- package/dist/core/utils.js +1 -0
- package/dist/draws/draw.module.d.ts +40 -0
- package/dist/draws/draw.module.js +1 -0
- package/dist/draws/index.d.ts +5 -0
- package/dist/draws/public.d.ts +6 -0
- package/dist/draws/public.js +1 -0
- package/dist/draws/types.d.ts +90 -0
- package/dist/draws/types.js +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.js +1 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.js +1 -0
- package/dist/experiences/distribution-monitor.runtime.d.ts +12 -0
- package/dist/experiences/distribution-monitor.runtime.js +1 -0
- package/dist/experiences/distribution-monitor.types.d.ts +62 -0
- package/dist/experiences/experience.module.d.ts +88 -0
- package/dist/experiences/experience.module.js +1 -0
- package/dist/experiences/index.d.ts +3 -0
- package/dist/experiences/index.js +1 -0
- package/dist/experiences/journey-contract.fixtures.d.ts +9 -0
- package/dist/experiences/journey-view.d.ts +22 -0
- package/dist/experiences/journey-view.js +1 -0
- package/dist/experiences/journey.d.ts +89 -0
- package/dist/experiences/journey.js +1 -0
- package/dist/experiences/journey.machine.d.ts +79 -0
- package/dist/experiences/journey.machine.js +1 -0
- package/dist/experiences/journey.types.d.ts +395 -0
- package/dist/experiences/public.d.ts +13 -0
- package/dist/experiences/public.js +1 -0
- package/dist/experiences/types.d.ts +161 -0
- package/dist/handoff/handoff.module.d.ts +184 -0
- package/dist/handoff/handoff.module.js +1 -0
- package/dist/handoff/index.d.ts +5 -0
- package/dist/handoff/index.js +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -0
- package/dist/internals.d.ts +11 -0
- package/dist/internals.js +1 -0
- package/dist/queues/index.d.ts +5 -0
- package/dist/queues/index.js +1 -0
- package/dist/queues/qualitative-bucket.d.ts +12 -0
- package/dist/queues/qualitative-bucket.js +1 -0
- package/dist/queues/queue.module.d.ts +42 -0
- package/dist/queues/queue.module.js +1 -0
- package/dist/queues/types.d.ts +112 -0
- package/dist/queues/types.js +1 -0
- package/dist/security/admission-proof.d.ts +15 -0
- package/dist/security/admission-proof.js +1 -0
- package/dist/state/capability-token-registry.d.ts +44 -0
- package/dist/state/capability-token-registry.js +1 -0
- package/dist/state/events.d.ts +342 -0
- package/dist/state/events.js +1 -0
- package/dist/state/store.d.ts +48 -0
- package/dist/state/store.js +1 -0
- package/dist/sync/broadcast-transport.d.ts +19 -0
- package/dist/sync/broadcast-transport.js +1 -0
- package/dist/sync/cross-tab-coordinator.d.ts +44 -0
- package/dist/sync/cross-tab-coordinator.js +1 -0
- package/dist/sync/index.d.ts +8 -0
- package/dist/sync/localstorage-transport.d.ts +32 -0
- package/dist/sync/localstorage-transport.js +1 -0
- package/dist/sync/protocol-transport.d.ts +51 -0
- package/dist/sync/protocol-transport.js +1 -0
- package/dist/sync/protocol.d.ts +254 -0
- package/dist/sync/protocol.js +1 -0
- package/dist/sync/recovery.d.ts +68 -0
- package/dist/sync/transport-factory.d.ts +16 -0
- package/dist/sync/transport-factory.js +1 -0
- package/dist/sync/transport-interface.d.ts +33 -0
- package/dist/sync/transport.d.ts +49 -0
- package/dist/sync/transport.js +1 -0
- package/dist/test-utils/harness-scenarios.d.ts +71 -0
- package/dist/test-utils/harness-scenarios.js +1 -0
- package/dist/test-utils/index.d.ts +12 -0
- package/dist/test-utils/index.js +1 -0
- package/dist/test-utils/mock-journey.d.ts +63 -0
- package/dist/test-utils/mock-journey.js +1 -0
- package/dist/test-utils/mock-sdk-vitest.d.ts +80 -0
- package/dist/test-utils/mock-sdk-vitest.js +1 -0
- package/dist/test-utils/mock-sdk.d.ts +22 -0
- package/dist/test-utils/mock-sdk.js +1 -0
- package/dist/test-utils/mock-server.d.ts +105 -0
- package/dist/test-utils/mock-server.js +1 -0
- package/dist/test-utils/sdk-factory.d.ts +3 -0
- package/dist/test-utils/sdk-factory.js +1 -0
- package/dist/test-utils/verification-scenarios.d.ts +43 -0
- package/dist/test-utils/verification-scenarios.js +1 -0
- package/dist/test-utils/verification-state.d.ts +1 -0
- package/dist/test-utils/verification-state.js +1 -0
- package/dist/theme.d.ts +8 -0
- package/dist/theme.js +1 -0
- package/dist/timed-releases/index.d.ts +6 -0
- package/dist/timed-releases/public.d.ts +6 -0
- package/dist/timed-releases/public.js +1 -0
- package/dist/timed-releases/timed-release.module.d.ts +31 -0
- package/dist/timed-releases/timed-release.module.js +1 -0
- package/dist/timed-releases/types.d.ts +70 -0
- package/dist/timed-releases/types.js +1 -0
- package/dist/types/distribution-type.d.ts +45 -0
- package/dist/types/index.d.ts +178 -0
- package/dist/utils/fingerprint.module.d.ts +27 -0
- package/dist/utils/fingerprint.module.js +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +1 -0
- package/dist/waitlists/index.d.ts +2 -0
- package/dist/waitlists/public.d.ts +6 -0
- package/dist/waitlists/public.js +1 -0
- package/dist/waitlists/types.d.ts +50 -0
- package/dist/waitlists/types.js +1 -0
- package/dist/waitlists/waitlist.module.d.ts +17 -0
- package/dist/waitlists/waitlist.module.js +1 -0
- package/package.json +205 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { AuctionStatus } from '../auctions/types';
|
|
2
|
+
import { Session } from '../auth/types';
|
|
3
|
+
import { DrawResult } from '../draws/types';
|
|
4
|
+
import { SequenceOutcome } from '../experiences/journey.types';
|
|
5
|
+
import { QueueConsumerState } from '../queues/types';
|
|
6
|
+
import { SdkDistributionType } from '../types/distribution-type';
|
|
7
|
+
export interface AuthEvents {
|
|
8
|
+
"auth:authenticated": {
|
|
9
|
+
session: Session;
|
|
10
|
+
isNew: boolean;
|
|
11
|
+
};
|
|
12
|
+
"auth:refreshed": {
|
|
13
|
+
session: Session;
|
|
14
|
+
};
|
|
15
|
+
"auth:logout": {
|
|
16
|
+
reason?: string;
|
|
17
|
+
};
|
|
18
|
+
"auth:error": {
|
|
19
|
+
error: Error;
|
|
20
|
+
context: string;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export interface QueueEvents {
|
|
24
|
+
"queue:entered": {
|
|
25
|
+
queueId: string;
|
|
26
|
+
position: number;
|
|
27
|
+
estimatedWaitTime?: number;
|
|
28
|
+
};
|
|
29
|
+
"queue:left": {
|
|
30
|
+
queueId: string;
|
|
31
|
+
reason?: string;
|
|
32
|
+
};
|
|
33
|
+
"queue:position-changed": {
|
|
34
|
+
queueId: string;
|
|
35
|
+
position: number;
|
|
36
|
+
previousPosition: number;
|
|
37
|
+
};
|
|
38
|
+
"queue:granted": {
|
|
39
|
+
queueId: string;
|
|
40
|
+
grant: string;
|
|
41
|
+
};
|
|
42
|
+
"queue:expired": {
|
|
43
|
+
queueId: string;
|
|
44
|
+
};
|
|
45
|
+
"queue:error": {
|
|
46
|
+
queueId: string;
|
|
47
|
+
error: Error;
|
|
48
|
+
};
|
|
49
|
+
"queue:status-updated": {
|
|
50
|
+
queueId: string;
|
|
51
|
+
status: QueueConsumerState;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export interface DrawEvents {
|
|
55
|
+
"draw:entered": {
|
|
56
|
+
drawId: string;
|
|
57
|
+
entryNumber?: string;
|
|
58
|
+
};
|
|
59
|
+
"draw:left": {
|
|
60
|
+
drawId: string;
|
|
61
|
+
};
|
|
62
|
+
"draw:won": {
|
|
63
|
+
drawId: string;
|
|
64
|
+
result: DrawResult;
|
|
65
|
+
};
|
|
66
|
+
"draw:lost": {
|
|
67
|
+
drawId: string;
|
|
68
|
+
result: DrawResult;
|
|
69
|
+
};
|
|
70
|
+
"draw:expired": {
|
|
71
|
+
drawId: string;
|
|
72
|
+
};
|
|
73
|
+
"draw:error": {
|
|
74
|
+
drawId: string;
|
|
75
|
+
error: Error;
|
|
76
|
+
};
|
|
77
|
+
"draw:status-updated": {
|
|
78
|
+
drawId: string;
|
|
79
|
+
status: string;
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
export interface AuctionEvents {
|
|
83
|
+
"auction:entered": {
|
|
84
|
+
auctionId: string;
|
|
85
|
+
};
|
|
86
|
+
"auction:bid-placed": {
|
|
87
|
+
auctionId: string;
|
|
88
|
+
amount: string;
|
|
89
|
+
status: string;
|
|
90
|
+
};
|
|
91
|
+
"auction:outbid": {
|
|
92
|
+
auctionId: string;
|
|
93
|
+
yourBid: string;
|
|
94
|
+
highestBid: string;
|
|
95
|
+
};
|
|
96
|
+
"auction:winning": {
|
|
97
|
+
auctionId: string;
|
|
98
|
+
amount: string;
|
|
99
|
+
highestBid: string;
|
|
100
|
+
};
|
|
101
|
+
"auction:won": {
|
|
102
|
+
auctionId: string;
|
|
103
|
+
winningBid: string;
|
|
104
|
+
};
|
|
105
|
+
"auction:lost": {
|
|
106
|
+
auctionId: string;
|
|
107
|
+
highestBid: string;
|
|
108
|
+
};
|
|
109
|
+
"auction:left": {
|
|
110
|
+
auctionId: string;
|
|
111
|
+
};
|
|
112
|
+
"auction:error": {
|
|
113
|
+
auctionId: string;
|
|
114
|
+
error: Error;
|
|
115
|
+
};
|
|
116
|
+
"auction:status-updated": {
|
|
117
|
+
auctionId: string;
|
|
118
|
+
status: AuctionStatus;
|
|
119
|
+
};
|
|
120
|
+
"auction:watching-started": {
|
|
121
|
+
auctionId: string;
|
|
122
|
+
intervalMs: number;
|
|
123
|
+
};
|
|
124
|
+
"auction:watching-stopped": {
|
|
125
|
+
auctionId: string;
|
|
126
|
+
};
|
|
127
|
+
"auction:bid-updated": {
|
|
128
|
+
auctionId: string;
|
|
129
|
+
highestBid: string;
|
|
130
|
+
bidCount: number;
|
|
131
|
+
};
|
|
132
|
+
"auction:history-fetched": {
|
|
133
|
+
auctionId: string;
|
|
134
|
+
bidCount: number;
|
|
135
|
+
};
|
|
136
|
+
"auction:auto-rebid-enabled": {
|
|
137
|
+
auctionId: string;
|
|
138
|
+
maxBid: string;
|
|
139
|
+
increment: string;
|
|
140
|
+
};
|
|
141
|
+
"auction:auto-rebid-disabled": {
|
|
142
|
+
auctionId: string;
|
|
143
|
+
finalRebidCount: number;
|
|
144
|
+
};
|
|
145
|
+
"auction:auto-rebid-placed": {
|
|
146
|
+
auctionId: string;
|
|
147
|
+
amount: string;
|
|
148
|
+
remainingBudget: string;
|
|
149
|
+
status: string;
|
|
150
|
+
};
|
|
151
|
+
"auction:auto-rebid-max-reached": {
|
|
152
|
+
auctionId: string;
|
|
153
|
+
maxBid: string;
|
|
154
|
+
wouldNeedToBid: string;
|
|
155
|
+
};
|
|
156
|
+
"auction:auto-rebid-failed": {
|
|
157
|
+
auctionId: string;
|
|
158
|
+
error: Error;
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
export interface WaitlistEvents {
|
|
162
|
+
"waitlist:entered": {
|
|
163
|
+
waitlistId: string;
|
|
164
|
+
sequenceId: string;
|
|
165
|
+
enteredAt: string;
|
|
166
|
+
};
|
|
167
|
+
"waitlist:left": {
|
|
168
|
+
waitlistId: string;
|
|
169
|
+
};
|
|
170
|
+
"waitlist:error": {
|
|
171
|
+
waitlistId: string;
|
|
172
|
+
error: string;
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export interface TimedReleaseEvents {
|
|
176
|
+
"timed_release:entered": {
|
|
177
|
+
timedReleaseId: string;
|
|
178
|
+
enteredAt: string;
|
|
179
|
+
};
|
|
180
|
+
"timed_release:left": {
|
|
181
|
+
timedReleaseId: string;
|
|
182
|
+
};
|
|
183
|
+
"timed_release:completed": {
|
|
184
|
+
timedReleaseId: string;
|
|
185
|
+
completedAt: string;
|
|
186
|
+
};
|
|
187
|
+
"timed_release:error": {
|
|
188
|
+
timedReleaseId: string;
|
|
189
|
+
error: Error;
|
|
190
|
+
};
|
|
191
|
+
"timed_release:status-updated": {
|
|
192
|
+
timedReleaseId: string;
|
|
193
|
+
status: string;
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
export interface ExperienceEvents {
|
|
197
|
+
"experience:entered": {
|
|
198
|
+
experienceId: string;
|
|
199
|
+
enteredAt: string;
|
|
200
|
+
};
|
|
201
|
+
"experience:left": {
|
|
202
|
+
experienceId: string;
|
|
203
|
+
};
|
|
204
|
+
"experience:sequence-selected": {
|
|
205
|
+
sequenceId: string;
|
|
206
|
+
};
|
|
207
|
+
"experience:distribution-entered": {
|
|
208
|
+
distributionType: SdkDistributionType;
|
|
209
|
+
distributionId: string;
|
|
210
|
+
};
|
|
211
|
+
"experience:error": {
|
|
212
|
+
experienceId: string;
|
|
213
|
+
error: string;
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Categorisation of a `journey:error` event. New kinds may be added; consumers
|
|
218
|
+
* should use a default branch or the wildcard listener to avoid silent drops.
|
|
219
|
+
*/
|
|
220
|
+
export type JourneyErrorKind = "protocolError";
|
|
221
|
+
/**
|
|
222
|
+
* Payload shape for `journey:error`. `protocolError` is emitted by the shared
|
|
223
|
+
* response-parse helper (`core/parse-response.ts`) when a server response
|
|
224
|
+
* fails valibot validation. `endpoint` identifies the call site for triage;
|
|
225
|
+
* `issues` carries the raw valibot issue list for diagnosis.
|
|
226
|
+
*
|
|
227
|
+
* Defined as a type alias (not an interface) so the structural shape
|
|
228
|
+
* satisfies `Record<string, unknown>` consumers like the Google Analytics
|
|
229
|
+
* adapter that subscribes via a wide event listener.
|
|
230
|
+
*/
|
|
231
|
+
export type JourneyErrorPayload = {
|
|
232
|
+
kind: JourneyErrorKind;
|
|
233
|
+
endpoint: string;
|
|
234
|
+
message: string;
|
|
235
|
+
requestId?: string;
|
|
236
|
+
issues?: unknown;
|
|
237
|
+
};
|
|
238
|
+
export interface JourneyEvents {
|
|
239
|
+
"journey:granted": {
|
|
240
|
+
experienceId: string;
|
|
241
|
+
sequenceId: string;
|
|
242
|
+
grant: string;
|
|
243
|
+
};
|
|
244
|
+
"journey:grant-claimed": {
|
|
245
|
+
experienceId: string;
|
|
246
|
+
sequenceId: string;
|
|
247
|
+
grant: string;
|
|
248
|
+
};
|
|
249
|
+
"journey:ended": {
|
|
250
|
+
experienceId: string;
|
|
251
|
+
sequenceId: string;
|
|
252
|
+
outcome: SequenceOutcome;
|
|
253
|
+
};
|
|
254
|
+
"journey:phase-changed": {
|
|
255
|
+
experienceId: string;
|
|
256
|
+
journeyStage: string;
|
|
257
|
+
sequencePhase?: string;
|
|
258
|
+
};
|
|
259
|
+
"journey:error": JourneyErrorPayload;
|
|
260
|
+
}
|
|
261
|
+
export interface NetworkEvents {
|
|
262
|
+
"network:online": Record<string, never>;
|
|
263
|
+
"network:offline": Record<string, never>;
|
|
264
|
+
"network:error": {
|
|
265
|
+
error: Error;
|
|
266
|
+
request?: {
|
|
267
|
+
url: string;
|
|
268
|
+
method: string;
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
export interface StateEvents {
|
|
273
|
+
"state:restored": {
|
|
274
|
+
experiences: string[];
|
|
275
|
+
};
|
|
276
|
+
"state:cleared": Record<string, never>;
|
|
277
|
+
"state:sync": {
|
|
278
|
+
key: string;
|
|
279
|
+
value: Session | null;
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
export interface BeaconEvents {
|
|
283
|
+
"beacon:queued": {
|
|
284
|
+
event: {
|
|
285
|
+
eventName: string;
|
|
286
|
+
eventId?: string;
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
"beacon:sent": {
|
|
290
|
+
count: number;
|
|
291
|
+
};
|
|
292
|
+
"beacon:error": {
|
|
293
|
+
error: unknown;
|
|
294
|
+
event?: unknown;
|
|
295
|
+
events?: unknown;
|
|
296
|
+
count?: number;
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
export interface SDKEvents extends AuthEvents, QueueEvents, DrawEvents, AuctionEvents, WaitlistEvents, TimedReleaseEvents, ExperienceEvents, JourneyEvents, NetworkEvents, StateEvents, BeaconEvents {
|
|
300
|
+
}
|
|
301
|
+
export type SDKEventKey = keyof SDKEvents;
|
|
302
|
+
export type SDKEventData<K extends SDKEventKey> = SDKEvents[K];
|
|
303
|
+
type EventListener<K extends SDKEventKey> = (data: SDKEventData<K>) => void;
|
|
304
|
+
type WildcardListener = (event: SDKEventKey, data: SDKEventData<SDKEventKey>) => void;
|
|
305
|
+
export declare class EventBus {
|
|
306
|
+
private listeners;
|
|
307
|
+
private logger;
|
|
308
|
+
/**
|
|
309
|
+
* Subscribe to an event
|
|
310
|
+
*/
|
|
311
|
+
on<K extends SDKEventKey>(event: K, listener: EventListener<K>): () => void;
|
|
312
|
+
on(event: "*", listener: WildcardListener): () => void;
|
|
313
|
+
/**
|
|
314
|
+
* Subscribe to an event once
|
|
315
|
+
*/
|
|
316
|
+
once<K extends SDKEventKey>(event: K, listener: EventListener<K>): () => void;
|
|
317
|
+
/**
|
|
318
|
+
* Emit an event
|
|
319
|
+
*/
|
|
320
|
+
emit<K extends SDKEventKey>(event: K, data: SDKEventData<K>): void;
|
|
321
|
+
/**
|
|
322
|
+
* Remove all listeners for an event
|
|
323
|
+
*/
|
|
324
|
+
off(event?: SDKEventKey | "*"): void;
|
|
325
|
+
/**
|
|
326
|
+
* Get listener count for an event
|
|
327
|
+
*/
|
|
328
|
+
listenerCount(event?: SDKEventKey | "*"): number;
|
|
329
|
+
/**
|
|
330
|
+
* Get all registered events
|
|
331
|
+
*/
|
|
332
|
+
events(): (SDKEventKey | "*")[];
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Get event bus instance
|
|
336
|
+
*/
|
|
337
|
+
export declare function getEventBus(): EventBus;
|
|
338
|
+
/**
|
|
339
|
+
* Reset event bus (for testing)
|
|
340
|
+
*/
|
|
341
|
+
export declare function resetEventBus(): void;
|
|
342
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getLogger as e}from"../core/logger.js";class t{constructor(){this.listeners=/* @__PURE__ */new Map,this.logger=e()}on(e,t){this.listeners.has(e)||this.listeners.set(e,/* @__PURE__ */new Set);const r=this.listeners.get(e);return r&&(r.add(t),this.logger.debug("Event listener added",{event:e,listenerCount:r.size})),()=>{const r=this.listeners.get(e);r&&(r.delete(t),0===r.size&&this.listeners.delete(e),this.logger.debug("Event listener removed",{event:e}))}}once(e,t){const r=this.on(e,e=>{r(),t(e)});return r}emit(e,t){this.logger.debug("Event emitted",{event:e,data:t});const r=this.listeners.get(e);r&&r.forEach(r=>{try{r(t)}catch(s){this.logger.error("Error in event listener",{event:e,error:s})}});const s=this.listeners.get("*");s&&s.forEach(r=>{try{r(e,t)}catch(s){this.logger.error("Error in wildcard listener",{event:e,error:s})}})}off(e){e?(this.listeners.delete(e),this.logger.debug("All listeners removed for event",{event:e})):(this.listeners.clear(),this.logger.debug("All event listeners cleared"))}listenerCount(e){if(e)return this.listeners.get(e)?.size||0;let t=0;return this.listeners.forEach(e=>{t+=e.size}),t}events(){return Array.from(this.listeners.keys())}}let r=null;function s(){return r||(r=new t),r}export{t as EventBus,s as getEventBus};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Session } from '../auth/types';
|
|
2
|
+
import { JourneySnapshot } from '../experiences/journey.types';
|
|
3
|
+
import { ExperienceSession } from '../experiences/types';
|
|
4
|
+
import { CapabilityTokenRecord, DistributionCapabilityBinding } from './capability-token-registry';
|
|
5
|
+
export interface SDKState {
|
|
6
|
+
session: Session | null;
|
|
7
|
+
refreshToken: string | null;
|
|
8
|
+
beaconToken: string | null;
|
|
9
|
+
activeExperiences: Record<string, ExperienceSession>;
|
|
10
|
+
activeJourneys: Record<string, JourneySnapshot>;
|
|
11
|
+
capabilityTokens: Record<string, CapabilityTokenRecord>;
|
|
12
|
+
distributionCapabilityIndex: Record<string, DistributionCapabilityBinding>;
|
|
13
|
+
versionMap: Record<string, number>;
|
|
14
|
+
setSession: (session: Session | null) => void;
|
|
15
|
+
setRefreshToken: (token: string | null) => void;
|
|
16
|
+
setBeaconToken: (token: string | null) => void;
|
|
17
|
+
addActiveExperience: (experienceId: string, session: ExperienceSession) => void;
|
|
18
|
+
removeActiveExperience: (experienceId: string) => void;
|
|
19
|
+
updateExperienceSession: (experienceId: string, updates: Partial<ExperienceSession>) => void;
|
|
20
|
+
clearActiveExperiences: () => void;
|
|
21
|
+
setJourneyState: (experienceId: string, journey: JourneySnapshot) => void;
|
|
22
|
+
removeJourney: (experienceId: string) => void;
|
|
23
|
+
clearJourneys: () => void;
|
|
24
|
+
upsertCapabilityToken: (key: string, record: CapabilityTokenRecord) => void;
|
|
25
|
+
bindDistribution: (distributionId: string, binding: DistributionCapabilityBinding) => void;
|
|
26
|
+
clearCapabilityContext: (experienceId: string, sequenceId: string) => void;
|
|
27
|
+
clearExperienceCapabilityContext: (experienceId: string) => void;
|
|
28
|
+
clearExpiredCapabilityTokens: (nowMs: number) => void;
|
|
29
|
+
clearAll: () => void;
|
|
30
|
+
}
|
|
31
|
+
export declare const useSDKStore: Omit<import('zustand/vanilla').StoreApi<SDKState>, "setState" | "persist"> & {
|
|
32
|
+
setState(partial: SDKState | Partial<SDKState> | ((state: SDKState) => SDKState | Partial<SDKState>), replace?: false | undefined): unknown;
|
|
33
|
+
setState(state: SDKState | ((state: SDKState) => SDKState), replace: true): unknown;
|
|
34
|
+
persist: {
|
|
35
|
+
setOptions: (options: Partial<import('zustand/middleware').PersistOptions<SDKState, unknown, unknown>>) => void;
|
|
36
|
+
clearStorage: () => void;
|
|
37
|
+
rehydrate: () => Promise<void> | void;
|
|
38
|
+
hasHydrated: () => boolean;
|
|
39
|
+
onHydrate: (fn: (state: SDKState) => void) => () => void;
|
|
40
|
+
onFinishHydration: (fn: (state: SDKState) => void) => () => void;
|
|
41
|
+
getOptions: () => Partial<import('zustand/middleware').PersistOptions<SDKState, unknown, unknown>>;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Reset SDK store (for testing)
|
|
46
|
+
*/
|
|
47
|
+
export declare function resetSDKStore(): void;
|
|
48
|
+
export declare const getSDKStore: () => SDKState;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{persist as e,createJSONStorage as i}from"zustand/middleware";import{createStore as n}from"zustand/vanilla";import{isBrowser as t,isStorageAvailable as s}from"../core/utils.js";import{getEventBus as r}from"./events.js";const o=["session","activeJourneys","versionMap"],a=["session"];function c(e){if(!e||"object"!=typeof e)return{};const i=e,n={};for(const t of a)t in i&&(n[t]=i[t]);return n}function p(e){if(!e||"object"!=typeof e)return{};const i=e,n={};for(const t of o)t in i&&(n[t]="versionMap"===t?c(i[t]):i[t]);return n}const l=n()(e((e,i)=>({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{},setSession:n=>{e(e=>({session:n,versionMap:{...e.versionMap,session:(e.versionMap.session||0)+1}})),n&&r().emit("auth:authenticated",{session:n,isNew:!i().session})},setRefreshToken:i=>{e(e=>({refreshToken:i,versionMap:{...e.versionMap,refreshToken:(e.versionMap.refreshToken||0)+1}}))},setBeaconToken:i=>{e({beaconToken:i})},addActiveExperience:(i,n)=>{e(e=>({activeExperiences:{...e.activeExperiences,[i]:n},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}))},removeActiveExperience:i=>{e(e=>{const{[i]:n,...t}=e.activeExperiences;return{activeExperiences:t,versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}})},updateExperienceSession:(i,n)=>{e(e=>{const t=e.activeExperiences[i];return t?{activeExperiences:{...e.activeExperiences,[i]:{...t,...n}},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}:e})},clearActiveExperiences:()=>{e(e=>({activeExperiences:{},versionMap:{...e.versionMap,activeExperiences:(e.versionMap.activeExperiences||0)+1}}))},setJourneyState:(i,n)=>{e(e=>({activeJourneys:{...e.activeJourneys,[i]:n}}))},removeJourney:i=>{e(e=>{const{[i]:n,...t}=e.activeJourneys;return{activeJourneys:t}})},clearJourneys:()=>{e(()=>({activeJourneys:{}}))},upsertCapabilityToken:(i,n)=>{e(e=>({capabilityTokens:{...e.capabilityTokens,[i]:n},versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1}}))},bindDistribution:(i,n)=>{e(e=>({distributionCapabilityIndex:{...e.distributionCapabilityIndex,[i]:n},versionMap:{...e.versionMap,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}))},clearCapabilityContext:(i,n)=>{e(e=>{const t=`${i}:${n}`,{[t]:s,...r}=e.capabilityTokens,o={};for(const[i,n]of Object.entries(e.distributionCapabilityIndex))n.sequenceKey!==t&&(o[i]=n);return{capabilityTokens:r,distributionCapabilityIndex:o,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearExperienceCapabilityContext:i=>{e(e=>{const n={};for(const[s,r]of Object.entries(e.capabilityTokens))r.experienceId!==i&&(n[s]=r);const t={};for(const[s,r]of Object.entries(e.distributionCapabilityIndex))r.experienceId!==i&&(t[s]=r);return{capabilityTokens:n,distributionCapabilityIndex:t,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearExpiredCapabilityTokens:i=>{e(e=>{const n=/* @__PURE__ */new Set,t={};for(const[r,o]of Object.entries(e.capabilityTokens))o.expiresAtMs&&o.expiresAtMs<=i?n.add(r):t[r]=o;if(0===n.size)return e;const s={};for(const[i,r]of Object.entries(e.distributionCapabilityIndex))n.has(r.sequenceKey)||(s[i]=r);return{capabilityTokens:t,distributionCapabilityIndex:s,versionMap:{...e.versionMap,capabilityTokens:(e.versionMap.capabilityTokens||0)+1,distributionCapabilityIndex:(e.versionMap.distributionCapabilityIndex||0)+1}}})},clearAll:()=>{e({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{}}),r().emit("state:cleared",{})}}),{name:"fanfare-sdk-state",storage:i(()=>(()=>{if(t()&&s("localStorage"))return window.localStorage;const e={};return{getItem:i=>Object.prototype.hasOwnProperty.call(e,i)?e[i]:null,setItem:(i,n)=>{e[i]=n},removeItem:i=>{delete e[i]}}})()),partialize:e=>p(e),version:3,merge:(e,i)=>({...i,...p(e)}),onRehydrateStorage:()=>e=>{if(e){const i=e.activeJourneys??{};r().emit("state:restored",{experiences:Object.keys(i)})}}}));function v(){l.setState({session:null,refreshToken:null,beaconToken:null,activeExperiences:{},activeJourneys:{},capabilityTokens:{},distributionCapabilityIndex:{},versionMap:{}}),b=null}let b=null;const u=()=>b||(b=new Proxy({},{get:(e,i)=>Reflect.has(e,i)?e[i]:l.getState()[i],has:(e,i)=>Reflect.has(e,i)||i in l.getState(),ownKeys:e=>{const i=new Set(Reflect.ownKeys(l.getState()));for(const n of Reflect.ownKeys(e))i.add(n);return Array.from(i)},getOwnPropertyDescriptor:(e,i)=>{const n=Object.getOwnPropertyDescriptor(e,i);if(n)return n;const t=Object.getOwnPropertyDescriptor(l.getState(),i);return t?{...t,configurable:!0}:void 0}}),b);export{u as getSDKStore,v as resetSDKStore,l as useSDKStore};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ITransport } from './transport-interface';
|
|
2
|
+
/**
|
|
3
|
+
* Check if BroadcastChannel API is supported
|
|
4
|
+
*/
|
|
5
|
+
export declare function isBroadcastChannelSupported(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* BroadcastChannel transport implementation
|
|
8
|
+
*/
|
|
9
|
+
export declare class BroadcastChannelTransport implements ITransport {
|
|
10
|
+
private channelName;
|
|
11
|
+
private channel;
|
|
12
|
+
private messageHandler?;
|
|
13
|
+
private logger;
|
|
14
|
+
constructor(channelName: string);
|
|
15
|
+
send(message: unknown): void;
|
|
16
|
+
onMessage(handler: (event: MessageEvent) => void): () => void;
|
|
17
|
+
close(): void;
|
|
18
|
+
isAvailable(): boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getLogger as e}from"../core/logger.js";import{isBrowser as a}from"../core/utils.js";function n(){if(!a())return!1;try{return"undefined"!=typeof window&&"BroadcastChannel"in window&&"function"==typeof window.BroadcastChannel}catch{return!1}}class s{constructor(a){if(this.channelName=a,this.channel=null,this.logger=e(),n())try{this.channel=new BroadcastChannel(a),this.logger.debug("BroadcastChannelTransport initialized",{channelName:a})}catch(s){this.logger.warn("Failed to create BroadcastChannel",{error:s,channelName:a}),this.channel=null}else this.logger.debug("BroadcastChannel not supported")}send(e){if(this.channel)try{this.channel.postMessage(e),this.logger.debug("Message sent via BroadcastChannel",{message:e})}catch(a){this.logger.warn("Failed to send message via BroadcastChannel",{error:a,message:e})}else this.logger.debug("BroadcastChannel not available, message not sent",{message:e})}onMessage(e){return this.channel?(this.messageHandler=e,this.channel.onmessage=e=>{this.logger.debug("Message received via BroadcastChannel",{data:e.data}),this.messageHandler&&this.messageHandler(e)},()=>{this.channel&&(this.channel.onmessage=null),this.messageHandler=void 0,this.logger.debug("BroadcastChannel listener removed")}):(this.logger.debug("BroadcastChannel not available, listener not added"),()=>{})}close(){if(this.channel){try{this.channel.close(),this.logger.debug("BroadcastChannel closed",{channelName:this.channelName})}catch(e){this.logger.warn("Error closing BroadcastChannel",{error:e})}this.channel=null}this.messageHandler=void 0}isAvailable(){return null!==this.channel}}export{s as BroadcastChannelTransport,n as isBroadcastChannelSupported};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ExperienceJourney } from '../experiences/journey';
|
|
2
|
+
interface CrossTabCoordinatorOptions {
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
channelName?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class CrossTabCoordinator {
|
|
7
|
+
private readonly transport;
|
|
8
|
+
private readonly logger;
|
|
9
|
+
private readonly enabled;
|
|
10
|
+
private readonly localOnly;
|
|
11
|
+
private readonly liveJourneys;
|
|
12
|
+
private readonly activeTabs;
|
|
13
|
+
private readonly runtimeOwners;
|
|
14
|
+
private readonly runtimeCursors;
|
|
15
|
+
private readonly unsubscribers;
|
|
16
|
+
private heartbeatTimer?;
|
|
17
|
+
private bootstrapTimer?;
|
|
18
|
+
private isApplyingRemoteUpdate;
|
|
19
|
+
private leaderId;
|
|
20
|
+
private bootstrapSettled;
|
|
21
|
+
constructor(options: CrossTabCoordinatorOptions);
|
|
22
|
+
registerJourney(journey: ExperienceJourney): void;
|
|
23
|
+
unregisterJourney(experienceId: string): void;
|
|
24
|
+
close(): void;
|
|
25
|
+
isEnabled(): boolean;
|
|
26
|
+
getTabId(): string;
|
|
27
|
+
isLeader(): boolean;
|
|
28
|
+
private handleMessage;
|
|
29
|
+
private handleLocalStateChange;
|
|
30
|
+
private applyBootstrapResponse;
|
|
31
|
+
private applyVersionedStoreField;
|
|
32
|
+
private applyJourneyUpsert;
|
|
33
|
+
private applyJourneyRemove;
|
|
34
|
+
private applyRuntimeState;
|
|
35
|
+
private syncRuntimeOwnership;
|
|
36
|
+
private stopRuntimeOwner;
|
|
37
|
+
private sendHeartbeat;
|
|
38
|
+
private requestBootstrap;
|
|
39
|
+
private markBootstrapSettled;
|
|
40
|
+
private recordHeartbeat;
|
|
41
|
+
private pruneDeadTabs;
|
|
42
|
+
private updateLeader;
|
|
43
|
+
}
|
|
44
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getLogger as e}from"../core/logger.js";import{deepClone as t}from"../core/utils.js";import{useSDKStore as i}from"../state/store.js";import{isHeartbeatMessage as s,isDurableBootstrapRequestMessage as r,isDurableBootstrapResponseMessage as n,isSessionUpdateMessage as a,isCapabilityTokensUpdateMessage as o,isDistributionCapabilityIndexUpdateMessage as p,isJourneyUpsertMessage as d,isJourneyRemoveMessage as h,isRuntimeStateMessage as l}from"./protocol.js";import{ProtocolTransport as u}from"./protocol-transport.js";class c{constructor(c){if(this.logger=e(),this.liveJourneys=/* @__PURE__ */new Map,this.activeTabs=/* @__PURE__ */new Map,this.runtimeOwners=/* @__PURE__ */new Map,this.runtimeCursors=/* @__PURE__ */new Map,this.unsubscribers=[],this.isApplyingRemoteUpdate=!1,this.leaderId=null,this.bootstrapSettled=!1,this.handleMessage=e=>{if(s(e))return this.recordHeartbeat(e.source,e.payload.active),void this.markBootstrapSettled();if(this.recordHeartbeat(e.source,!0),this.markBootstrapSettled(),r(e)){if(this.isLeader()){const e=i.getState();this.transport.sendDurableBootstrapResponse({session:t(e.session),sessionVersion:e.versionMap.session||0,capabilityTokens:t(e.capabilityTokens),capabilityTokensVersion:e.versionMap.capabilityTokens||0,distributionCapabilityIndex:t(e.distributionCapabilityIndex),distributionCapabilityIndexVersion:e.versionMap.distributionCapabilityIndex||0,journeys:Object.fromEntries(Object.entries(e.activeJourneys).map(([e,i])=>[e,{snapshot:t(i),version:i.revision}]))})}}else n(e)?this.applyBootstrapResponse(e.payload):a(e)?this.applyVersionedStoreField("session",e.payload.value,e.payload.version):o(e)?this.applyVersionedStoreField("capabilityTokens",e.payload.value,e.payload.version):p(e)?this.applyVersionedStoreField("distributionCapabilityIndex",e.payload.value,e.payload.version):d(e)?this.applyJourneyUpsert(e.payload.experienceId,e.payload.snapshot,e.payload.version):h(e)?this.applyJourneyRemove(e.payload.experienceId,e.payload.version):l(e)&&this.applyRuntimeState(e.payload)},this.transport=new u(c.channelName),this.enabled=(c.enabled??!0)&&this.transport.isAvailable(),this.localOnly=!this.enabled,this.unsubscribers.push(i.subscribe((e,t)=>{this.isApplyingRemoteUpdate||this.handleLocalStateChange(e,t)})),this.enabled)return this.unsubscribers.push(this.transport.onMessage(this.handleMessage)),this.sendHeartbeat(!0),this.heartbeatTimer=setInterval(()=>{this.sendHeartbeat(!0),this.pruneDeadTabs()},2e3),this.requestBootstrap(),void(this.bootstrapTimer=setTimeout(()=>{this.markBootstrapSettled()},100));this.leaderId=this.transport.getTabId(),this.activeTabs.set(this.transport.getTabId(),Date.now()),this.bootstrapSettled=!0}registerJourney(e){this.liveJourneys.set(e.getExperienceId(),e),this.syncRuntimeOwnership()}unregisterJourney(e){this.stopRuntimeOwner(e,!1),this.liveJourneys.delete(e),this.runtimeCursors.delete(e)}close(){this.enabled&&this.sendHeartbeat(!1),this.bootstrapTimer&&(clearTimeout(this.bootstrapTimer),this.bootstrapTimer=void 0),this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=void 0);for(const e of Array.from(this.runtimeOwners.keys()))this.stopRuntimeOwner(e,!1);for(const e of this.unsubscribers)e();this.unsubscribers.length=0,this.transport.close()}isEnabled(){return this.enabled}getTabId(){return this.transport.getTabId()}isLeader(){return this.leaderId===this.transport.getTabId()}handleLocalStateChange(e,i){this.enabled&&e.session!==i.session&&this.transport.sendSessionUpdate(t(e.session),e.versionMap.session||0),this.enabled&&e.capabilityTokens!==i.capabilityTokens&&this.transport.sendCapabilityTokensUpdate(t(e.capabilityTokens),e.versionMap.capabilityTokens||0),this.enabled&&e.distributionCapabilityIndex!==i.distributionCapabilityIndex&&this.transport.sendDistributionCapabilityIndexUpdate(t(e.distributionCapabilityIndex),e.versionMap.distributionCapabilityIndex||0);const s=e.activeJourneys,r=i.activeJourneys,n=/* @__PURE__ */new Set([...Object.keys(s),...Object.keys(r)]);for(const a of n){const e=s[a],i=r[a];!this.enabled||!e||i&&e.revision===i.revision?this.enabled&&!e&&i&&this.transport.sendJourneyRemove(a,i.revision+1):this.transport.sendJourneyUpsert(a,t(e),e.revision)}s!==r&&this.syncRuntimeOwnership()}applyBootstrapResponse(e){this.applyVersionedStoreField("session",e.session,e.sessionVersion),this.applyVersionedStoreField("capabilityTokens",e.capabilityTokens,e.capabilityTokensVersion),this.applyVersionedStoreField("distributionCapabilityIndex",e.distributionCapabilityIndex,e.distributionCapabilityIndexVersion);for(const[t,i]of Object.entries(e.journeys))this.applyJourneyUpsert(t,i.snapshot,i.version)}applyVersionedStoreField(e,t,s){const r=i.getState();s<=(r.versionMap[e]||0)||(this.isApplyingRemoteUpdate=!0,i.setState({[e]:t,versionMap:{...r.versionMap,[e]:s}}),this.isApplyingRemoteUpdate=!1)}applyJourneyUpsert(e,t,s){const r=i.getState(),n=r.activeJourneys[e];n&&n.revision>=s||(this.isApplyingRemoteUpdate=!0,i.setState({activeJourneys:{...r.activeJourneys,[e]:t}}),this.isApplyingRemoteUpdate=!1,this.liveJourneys.get(e)?.reconcileSyncedSnapshot(t),this.syncRuntimeOwnership())}applyJourneyRemove(e,t){const s=i.getState(),r=s.activeJourneys[e];if(r&&r.revision>=t)return;const{[e]:n,...a}=s.activeJourneys;this.isApplyingRemoteUpdate=!0,i.setState({activeJourneys:a}),this.isApplyingRemoteUpdate=!1,this.liveJourneys.get(e)?.reconcileSyncedSnapshot(null),this.syncRuntimeOwnership()}applyRuntimeState(e){if(e.leaderId!==this.leaderId)return;const t=this.liveJourneys.get(e.experienceId);if(!t||this.isLeader())return;const i=t.getRuntimeBinding();if(!i||i.sequenceId!==e.sequenceId||i.participationId!==e.participationId||i.participationType!==e.participationType)return;const s=this.runtimeCursors.get(e.experienceId);s&&s.leaderId===e.leaderId&&s.runtimeVersion>=e.runtimeVersion||(this.runtimeCursors.set(e.experienceId,{leaderId:e.leaderId,runtimeVersion:e.runtimeVersion}),t.applyRuntimeState(e.state??null))}syncRuntimeOwnership(){if(this.bootstrapSettled){for(const[e,i]of this.liveJourneys.entries()){const s=i.getRuntimeBinding(),r=this.runtimeOwners.get(e);if(!this.isLeader()||!s){r?this.stopRuntimeOwner(e,!0):s||i.applyRuntimeState(null);continue}const n=`${s.sequenceId}:${s.participationType}:${s.participationId}`;if(r?.bindingKey===n)continue;r&&this.stopRuntimeOwner(e,!0),i.startRuntimeMonitoring();const a=i.getRuntimeDisplayAtom();if(!a)continue;let o=0;const p=i=>{o+=1,this.enabled&&this.transport.sendRuntimeState({experienceId:e,sequenceId:s.sequenceId,participationId:s.participationId,participationType:s.participationType,leaderId:this.transport.getTabId(),runtimeVersion:o,state:t(i.get())})},d=a.listen(()=>{p(a)});p(a),this.runtimeOwners.set(e,{bindingKey:n,sequenceId:s.sequenceId,participationId:s.participationId,participationType:s.participationType,unsubscribe:d,runtimeVersion:o})}for(const e of Array.from(this.runtimeOwners.keys()))this.liveJourneys.has(e)||this.stopRuntimeOwner(e,!0)}}stopRuntimeOwner(e,t){const i=this.runtimeOwners.get(e),s=this.liveJourneys.get(e);i?(i.unsubscribe(),t&&this.enabled&&this.isLeader()&&this.transport.sendRuntimeState({experienceId:e,sequenceId:i.sequenceId,participationId:i.participationId,participationType:i.participationType,leaderId:this.transport.getTabId(),runtimeVersion:i.runtimeVersion+1,state:null}),s?.stopRuntimeMonitoring(!0),this.runtimeOwners.delete(e)):this.runtimeOwners.delete(e)}sendHeartbeat(e){this.enabled&&(this.transport.sendHeartbeat(e),this.recordHeartbeat(this.transport.getTabId(),e))}requestBootstrap(){this.enabled&&this.transport.sendDurableBootstrapRequest()}markBootstrapSettled(){this.bootstrapSettled||(this.bootstrapSettled=!0,this.bootstrapTimer&&(clearTimeout(this.bootstrapTimer),this.bootstrapTimer=void 0),this.syncRuntimeOwnership())}recordHeartbeat(e,t){t||e===this.transport.getTabId()?this.activeTabs.set(e,Date.now()):this.activeTabs.delete(e),this.updateLeader()}pruneDeadTabs(){const e=Date.now();let t=!1;for(const[i,s]of this.activeTabs.entries())i!==this.transport.getTabId()&&e-s>6e3&&(this.activeTabs.delete(i),t=!0);t&&this.updateLeader()}updateLeader(){if(this.localOnly)return void(this.leaderId=this.transport.getTabId());const e=[this.transport.getTabId(),...this.activeTabs.keys()].sort()[0]??this.transport.getTabId();e!==this.leaderId&&(this.leaderId=e,this.runtimeCursors.clear(),this.logger.info("Cross-tab leader changed",{leaderId:e,tabId:this.transport.getTabId(),isLeader:this.isLeader()}),this.isLeader()||this.requestBootstrap(),this.syncRuntimeOwnership())}}export{c as CrossTabCoordinator};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-tab coordination internals.
|
|
3
|
+
*/
|
|
4
|
+
export { CrossTabCoordinator } from './cross-tab-coordinator';
|
|
5
|
+
export { DEFAULT_CHANNEL_NAME, PROTOCOL_VERSION, SyncMessageSchema, isCapabilityTokensUpdateMessage, isDistributionCapabilityIndexUpdateMessage, isDurableBootstrapRequestMessage, isDurableBootstrapResponseMessage, isHeartbeatMessage, isJourneyRemoveMessage, isJourneyUpsertMessage, isRuntimeStateMessage, isSessionUpdateMessage, type CapabilityTokensUpdateMessage, type DistributionCapabilityIndexUpdateMessage, type DurableBootstrapRequestMessage, type DurableBootstrapResponseMessage, type HeartbeatMessage, type JourneyRemoveMessage, type JourneyUpsertMessage, type RuntimeStateMessage, type SessionUpdateMessage, type SyncMessage, } from './protocol';
|
|
6
|
+
export { ProtocolTransport, type OutgoingMessage } from './protocol-transport';
|
|
7
|
+
export { RecoveryManager, type RecoveryStrategy, type UpdateQueue } from './recovery';
|
|
8
|
+
export { InterTabTransport } from './transport';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ITransport } from './transport-interface';
|
|
2
|
+
/**
|
|
3
|
+
* Check if localStorage is available and working
|
|
4
|
+
*/
|
|
5
|
+
export declare function isLocalStorageSupported(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* localStorage transport implementation
|
|
8
|
+
*
|
|
9
|
+
* Uses localStorage with storage events for cross-tab communication.
|
|
10
|
+
* Messages are stored with incrementing counters to maintain order.
|
|
11
|
+
*/
|
|
12
|
+
export declare class LocalStorageTransport implements ITransport {
|
|
13
|
+
private readonly channel;
|
|
14
|
+
private readonly tabId;
|
|
15
|
+
private messageHandler?;
|
|
16
|
+
private storageHandler?;
|
|
17
|
+
private cleanupInterval?;
|
|
18
|
+
private lastProcessedCounter;
|
|
19
|
+
private logger;
|
|
20
|
+
constructor(channel: string);
|
|
21
|
+
private generateTabId;
|
|
22
|
+
private getCounterKey;
|
|
23
|
+
private getMessageKey;
|
|
24
|
+
private initializeCounter;
|
|
25
|
+
private getNextCounter;
|
|
26
|
+
private startCleanupInterval;
|
|
27
|
+
private cleanupOldMessages;
|
|
28
|
+
send(message: unknown): void;
|
|
29
|
+
onMessage(handler: (event: MessageEvent) => void): () => void;
|
|
30
|
+
close(): void;
|
|
31
|
+
isAvailable(): boolean;
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{getLogger as e}from"../core/logger.js";import{isStorageAvailable as t}from"../core/utils.js";function a(){return t("localStorage")}class s{constructor(t){this.lastProcessedCounter=0,this.logger=e(),this.channel=t,this.tabId=this.generateTabId(),a()?(this.startCleanupInterval(),this.initializeCounter(),this.logger.debug("LocalStorageTransport initialized",{channel:t,tabId:this.tabId})):this.logger.warn("localStorage not supported")}generateTabId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}getCounterKey(){return`fanfare-sdk:${this.channel}:counter`}getMessageKey(e){return`fanfare-sdk:${this.channel}:msg:${e}`}initializeCounter(){try{const e=localStorage.getItem(this.getCounterKey());e?this.lastProcessedCounter=parseInt(e,10):localStorage.setItem(this.getCounterKey(),"0")}catch(e){this.logger.warn("Failed to initialize counter",{error:e})}}getNextCounter(){try{const e=this.getCounterKey(),t=parseInt(localStorage.getItem(e)||"0",10)+1;return localStorage.setItem(e,t.toString()),t}catch(e){return this.logger.warn("Failed to get next counter",{error:e}),Date.now()}}startCleanupInterval(){"undefined"!=typeof window&&window.setInterval?this.cleanupInterval=window.setInterval(()=>{this.cleanupOldMessages()},2e3):"undefined"!=typeof setInterval&&(this.cleanupInterval=setInterval(()=>{this.cleanupOldMessages()},2e3))}cleanupOldMessages(){if(a())try{const e=Date.now(),t=5e3,a=[];for(let s=0;s<localStorage.length;s++){const r=localStorage.key(s);if(r&&r.startsWith(`fanfare-sdk:${this.channel}:msg:`))try{const s=localStorage.getItem(r);if(!s)continue;e-JSON.parse(s).timestamp>t&&a.push(r)}catch{a.push(r)}}a.forEach(e=>{try{localStorage.removeItem(e),this.logger.debug("Removed old message",{key:e})}catch(t){this.logger.warn("Failed to remove old message",{key:e,error:t})}})}catch(e){this.logger.warn("Cleanup failed",{error:e})}}send(e){if(a())try{const t=this.getNextCounter(),a={tabId:this.tabId,timestamp:Date.now(),data:e},s=this.getMessageKey(t);localStorage.setItem(s,JSON.stringify(a)),this.logger.debug("Message sent via localStorage",{key:s,message:e})}catch(t){if(this.logger.warn("Failed to send message via localStorage",{error:t,message:e}),t instanceof Error&&"QuotaExceededError"===t.name){this.cleanupOldMessages();try{const t=this.getNextCounter(),a={tabId:this.tabId,timestamp:Date.now(),data:e},s=this.getMessageKey(t);localStorage.setItem(s,JSON.stringify(a))}catch{}}}else this.logger.debug("localStorage not available, message not sent",{message:e})}onMessage(e){return a()?(this.messageHandler=e,this.storageHandler=e=>{if(e.key&&e.newValue&&e.key.startsWith(`fanfare-sdk:${this.channel}:msg:`))try{const t=JSON.parse(e.newValue);if(t.tabId===this.tabId)return;const a=e.key.match(/msg:(\d+)$/);if(a){const s=parseInt(a[1],10);if(s>this.lastProcessedCounter){this.lastProcessedCounter=s,this.logger.debug("Message received via localStorage",{key:e.key,data:t.data,fromTab:t.tabId});const a=new MessageEvent("message",{data:t.data,origin:window.location.origin});this.messageHandler&&this.messageHandler(a)}}}catch(t){this.logger.warn("Failed to process storage event",{error:t,key:e.key})}},window.addEventListener("storage",this.storageHandler),()=>{this.storageHandler&&window.removeEventListener("storage",this.storageHandler),this.messageHandler=void 0,this.storageHandler=void 0,this.logger.debug("localStorage listener removed")}):(this.logger.debug("localStorage not available, listener not added"),()=>{})}close(){this.storageHandler&&(window.removeEventListener("storage",this.storageHandler),this.storageHandler=void 0),this.messageHandler=void 0,this.cleanupInterval&&("undefined"!=typeof window&&window.clearInterval?window.clearInterval(this.cleanupInterval):"undefined"!=typeof clearInterval&&clearInterval(this.cleanupInterval),this.cleanupInterval=void 0),this.cleanupOldMessages(),this.logger.debug("LocalStorageTransport closed",{channel:this.channel})}isAvailable(){return a()}}export{s as LocalStorageTransport,a as isLocalStorageSupported};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { CapabilityTokensUpdateMessage, DistributionCapabilityIndexUpdateMessage, DurableBootstrapRequestMessage, DurableBootstrapResponseMessage, HeartbeatMessage, JourneyRemoveMessage, JourneyUpsertMessage, RuntimeStateMessage, SessionUpdateMessage, SyncMessage } from './protocol';
|
|
2
|
+
/**
|
|
3
|
+
* Message types that can be sent (without metadata)
|
|
4
|
+
*/
|
|
5
|
+
export type OutgoingMessage = Omit<DurableBootstrapRequestMessage, "id" | "timestamp" | "source" | "version"> | Omit<DurableBootstrapResponseMessage, "id" | "timestamp" | "source" | "version"> | Omit<SessionUpdateMessage, "id" | "timestamp" | "source" | "version"> | Omit<CapabilityTokensUpdateMessage, "id" | "timestamp" | "source" | "version"> | Omit<DistributionCapabilityIndexUpdateMessage, "id" | "timestamp" | "source" | "version"> | Omit<JourneyUpsertMessage, "id" | "timestamp" | "source" | "version"> | Omit<JourneyRemoveMessage, "id" | "timestamp" | "source" | "version"> | Omit<RuntimeStateMessage, "id" | "timestamp" | "source" | "version"> | Omit<HeartbeatMessage, "id" | "timestamp" | "source" | "version">;
|
|
6
|
+
/**
|
|
7
|
+
* Protocol-aware transport for typed message exchange
|
|
8
|
+
*/
|
|
9
|
+
export declare class ProtocolTransport {
|
|
10
|
+
private transport;
|
|
11
|
+
private tabId;
|
|
12
|
+
private logger;
|
|
13
|
+
constructor(channelName?: string);
|
|
14
|
+
/**
|
|
15
|
+
* Send a message with automatic metadata addition and validation
|
|
16
|
+
*/
|
|
17
|
+
sendMessage(message: OutgoingMessage): void;
|
|
18
|
+
/**
|
|
19
|
+
* Register a message handler with automatic validation
|
|
20
|
+
* @returns Unsubscribe function
|
|
21
|
+
*/
|
|
22
|
+
onMessage(callback: (message: SyncMessage) => void): () => void;
|
|
23
|
+
/**
|
|
24
|
+
* Close the transport and clean up resources
|
|
25
|
+
*/
|
|
26
|
+
close(): void;
|
|
27
|
+
/**
|
|
28
|
+
* Check if transport is available
|
|
29
|
+
*/
|
|
30
|
+
isAvailable(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Get the tab ID for this instance
|
|
33
|
+
*/
|
|
34
|
+
getTabId(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Get the number of registered listeners
|
|
37
|
+
*/
|
|
38
|
+
getListenerCount(): number;
|
|
39
|
+
sendDurableBootstrapRequest(): void;
|
|
40
|
+
sendDurableBootstrapResponse(payload: DurableBootstrapResponseMessage["payload"]): void;
|
|
41
|
+
sendSessionUpdate(value: unknown, version: number): void;
|
|
42
|
+
sendCapabilityTokensUpdate(value: unknown, version: number): void;
|
|
43
|
+
sendDistributionCapabilityIndexUpdate(value: unknown, version: number): void;
|
|
44
|
+
sendJourneyUpsert(experienceId: string, snapshot: unknown, version: number): void;
|
|
45
|
+
sendJourneyRemove(experienceId: string, version: number): void;
|
|
46
|
+
sendRuntimeState(payload: RuntimeStateMessage["payload"]): void;
|
|
47
|
+
/**
|
|
48
|
+
* Send a heartbeat message
|
|
49
|
+
*/
|
|
50
|
+
sendHeartbeat(active: boolean): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import*as e from"valibot";import{getLogger as s}from"../core/logger.js";import{generateId as t}from"../core/utils.js";import{DEFAULT_CHANNEL_NAME as r,SyncMessageSchema as o,PROTOCOL_VERSION as a}from"./protocol.js";import{InterTabTransport as i}from"./transport.js";class n{constructor(e=r){this.logger=s(),this.transport=new i(e),this.tabId=t(),this.logger.debug("ProtocolTransport initialized",{tabId:this.tabId,channelName:e})}sendMessage(s){const r={...s,id:t(),timestamp:Date.now(),source:this.tabId,version:a};try{const t=e.parse(o,r);this.transport.send(t),this.logger.debug("Protocol message sent",{type:s.type,id:r.id,source:this.tabId})}catch(i){this.logger.error("Invalid message format",{error:i,message:s})}}onMessage(s){return this.transport.on(t=>{try{const r=e.parse(o,t);if(r.source===this.tabId)return void this.logger.debug("Ignoring message from self",{type:r.type,id:r.id});this.logger.debug("Protocol message received",{type:r.type,id:r.id,source:r.source}),s(r)}catch(r){this.logger.warn("Received invalid message",{error:r,rawMessage:t})}})}close(){this.logger.debug("Closing ProtocolTransport",{tabId:this.tabId}),this.transport.close()}isAvailable(){return this.transport.isAvailable()}getTabId(){return this.tabId}getListenerCount(){return this.transport.getListenerCount()}sendDurableBootstrapRequest(){this.sendMessage({type:"durable:bootstrap:request",payload:{}})}sendDurableBootstrapResponse(e){this.sendMessage({type:"durable:bootstrap:response",payload:e})}sendSessionUpdate(e,s){this.sendMessage({type:"session:update",payload:{value:e,version:s}})}sendCapabilityTokensUpdate(e,s){this.sendMessage({type:"capability-tokens:update",payload:{value:e,version:s}})}sendDistributionCapabilityIndexUpdate(e,s){this.sendMessage({type:"distribution-capability-index:update",payload:{value:e,version:s}})}sendJourneyUpsert(e,s,t){this.sendMessage({type:"journey:upsert",payload:{experienceId:e,snapshot:s,version:t}})}sendJourneyRemove(e,s){this.sendMessage({type:"journey:remove",payload:{experienceId:e,version:s}})}sendRuntimeState(e){this.sendMessage({type:"runtime:state",payload:e})}sendHeartbeat(e){this.sendMessage({type:"heartbeat",payload:{active:e}})}}export{n as ProtocolTransport};
|