@appfunnel-dev/sdk 2.0.0-canary.4 → 2.0.0-canary.5
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/dist/{chunk-AKO6XKXP.js → chunk-AXXG3ODP.js} +10 -3
- package/dist/chunk-AXXG3ODP.js.map +1 -0
- package/dist/{chunk-M6U3FNRW.js → chunk-CFFMZYPE.js} +3 -3
- package/dist/{chunk-M6U3FNRW.js.map → chunk-CFFMZYPE.js.map} +1 -1
- package/dist/chunk-EMMSS5I5.cjs +37 -0
- package/dist/chunk-EMMSS5I5.cjs.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/{chunk-JSRKA375.cjs → chunk-G44BUG6G.cjs} +10 -3
- package/dist/chunk-G44BUG6G.cjs.map +1 -0
- package/dist/{chunk-LJYLGLFS.cjs → chunk-QMAZGLGV.cjs} +2 -35
- package/dist/chunk-QMAZGLGV.cjs.map +1 -0
- package/dist/{chunk-YY375F2B.js → chunk-TNLRQPVY.js} +3 -33
- package/dist/chunk-TNLRQPVY.js.map +1 -0
- package/dist/{chunk-CY4VBSMX.cjs → chunk-VAOR77NW.cjs} +7 -7
- package/dist/{chunk-CY4VBSMX.cjs.map → chunk-VAOR77NW.cjs.map} +1 -1
- package/dist/chunk-WYUDL4FI.cjs +8 -0
- package/dist/chunk-WYUDL4FI.cjs.map +1 -0
- package/dist/chunk-ZZJG4EYL.js +6 -0
- package/dist/chunk-ZZJG4EYL.js.map +1 -0
- package/dist/driver-paddle.cjs +23 -22
- package/dist/driver-paddle.cjs.map +1 -1
- package/dist/driver-paddle.js +3 -2
- package/dist/driver-paddle.js.map +1 -1
- package/dist/driver-stripe.cjs +37 -36
- package/dist/driver-stripe.cjs.map +1 -1
- package/dist/driver-stripe.js +3 -2
- package/dist/driver-stripe.js.map +1 -1
- package/dist/index.cjs +504 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +135 -74
- package/dist/index.d.ts +135 -74
- package/dist/index.js +447 -23
- package/dist/index.js.map +1 -1
- package/dist/{manifest-Cr2y1op6.d.cts → manifest-Dl7faaKe.d.cts} +34 -0
- package/dist/{manifest-Cr2y1op6.d.ts → manifest-Dl7faaKe.d.ts} +34 -0
- package/dist/manifest-entry.cjs +45 -30
- package/dist/manifest-entry.cjs.map +1 -1
- package/dist/manifest-entry.d.cts +1 -1
- package/dist/manifest-entry.d.ts +1 -1
- package/dist/manifest-entry.js +10 -3
- package/dist/manifest-entry.js.map +1 -1
- package/dist/protocol.cjs +13 -0
- package/dist/protocol.cjs.map +1 -0
- package/dist/protocol.d.cts +182 -0
- package/dist/protocol.d.ts +182 -0
- package/dist/protocol.js +4 -0
- package/dist/protocol.js.map +1 -0
- package/package.json +14 -2
- package/dist/chunk-AKO6XKXP.js.map +0 -1
- package/dist/chunk-JSRKA375.cjs.map +0 -1
- package/dist/chunk-LJYLGLFS.cjs.map +0 -1
- package/dist/chunk-YY375F2B.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ComponentType, ButtonHTMLAttributes } from 'react';
|
|
3
|
-
import { V as VariableValue, a as VariableConfig, A as Acquisition, P as Product, D as Direction, F as FunnelContext, R as RuntimeExperiment, b as FunnelSnapshot, c as FunnelDefinition, d as buildContext, e as ProductInput, f as PageMeta, g as PageContext } from './manifest-
|
|
4
|
-
export { B as BrowserContext, h as BuildContextOptions, C as ClickIdContext, i as CompileInput, j as Condition, k as ConditionOp, l as DeviceContext, E as EdgeCondition, m as ExperimentIssue, n as ExperimentResolution, o as ExperimentValidation, p as ExperimentVariant, q as FlowPage, r as FunnelLocales, s as FunnelManifest, G as Gate, I as IdentityContext, t as Interval, L as LocaleContext, M as ManifestEdge, u as ManifestPage, v as ManifestValidation, w as Money, O as OsContext, x as PageType, y as Predicate, z as ResolvedProduct, H as Route, S as SessionContext, J as SystemContext, U as UtmContext, K as assignVariant, N as bucketingSeed, Q as buildAcquisition, T as buildCatalog, W as compileManifest, X as currencyExponent, Y as defineFunnel, Z as definePage, _ as entryGuard, $ as evaluateCondition, a0 as evaluateGate, a1 as expectedPathLength, a2 as fnv1a, a3 as formatMoney, a4 as formatProduct, a5 as hashToUnit, a6 as isRtl, a7 as isVariantKey, a8 as nextPage, a9 as outgoingKeys, aa as pageMeta, ab as parseSlotKey, ac as pickByWeight, ad as resolveExperiments, ae as resolveLocale, af as resolveProduct, ag as resolveRoute, ah as validateExperiments } from './manifest-
|
|
3
|
+
import { V as VariableValue, a as VariableConfig, A as Acquisition, P as Product, D as Direction, F as FunnelContext, R as RuntimeExperiment, b as FunnelSnapshot, c as FunnelDefinition, d as buildContext, e as ProductInput, f as PageMeta, g as PageContext } from './manifest-Dl7faaKe.cjs';
|
|
4
|
+
export { B as BrowserContext, h as BuildContextOptions, C as ClickIdContext, i as CompileInput, j as Condition, k as ConditionOp, l as DeviceContext, E as EdgeCondition, m as ExperimentIssue, n as ExperimentResolution, o as ExperimentValidation, p as ExperimentVariant, q as FlowPage, r as FunnelLocales, s as FunnelManifest, G as Gate, I as IdentityContext, t as Interval, L as LocaleContext, M as ManifestEdge, u as ManifestPage, v as ManifestValidation, w as Money, O as OsContext, x as PageType, y as Predicate, z as ResolvedProduct, H as Route, S as SessionContext, J as SystemContext, U as UtmContext, K as assignVariant, N as bucketingSeed, Q as buildAcquisition, T as buildCatalog, W as compileManifest, X as currencyExponent, Y as defineFunnel, Z as definePage, _ as entryGuard, $ as evaluateCondition, a0 as evaluateGate, a1 as expectedPathLength, a2 as fnv1a, a3 as formatMoney, a4 as formatProduct, a5 as hashToUnit, a6 as isRtl, a7 as isVariantKey, a8 as nextPage, a9 as outgoingKeys, aa as pageMeta, ab as parseSlotKey, ac as pickByWeight, ad as resolveExperiments, ae as resolveLocale, af as resolveProduct, ag as resolveRoute, ah as validateExperiments } from './manifest-Dl7faaKe.cjs';
|
|
5
|
+
import { NavSnapshot } from './protocol.cjs';
|
|
5
6
|
import { a as CheckoutDriver, C as CheckoutDriverContext } from './checkout-Dz8cGkB_.cjs';
|
|
6
7
|
export { b as Checkout, c as CheckoutCallbacks, d as CheckoutHandle, e as CheckoutProps, f as CheckoutRequest, g as CheckoutResult, h as CheckoutResume, i as CheckoutStatus, F as FailureModalProps, j as FailureRoute, M as MockDriverOptions, O as OnFailedMap, U as Upsell, k as UpsellProps, l as UseCheckoutOptions, m as createMockDriver, n as driverWithEmail, u as useCheckout } from './checkout-Dz8cGkB_.cjs';
|
|
7
8
|
import { ToasterProps } from 'sonner';
|
|
@@ -186,6 +187,99 @@ declare function useTracker(): {
|
|
|
186
187
|
track: Tracker['track'];
|
|
187
188
|
};
|
|
188
189
|
|
|
190
|
+
/**
|
|
191
|
+
* The **`window.appfunnel` event bus** — the client-side integration surface
|
|
192
|
+
* (port of v0 `admin/lib/funnel/utils/appfunnelRuntime.ts`).
|
|
193
|
+
*
|
|
194
|
+
* Every taxonomy event the runtime emits is mirrored here, so on-page pixels and
|
|
195
|
+
* custom scripts integrate by **subscribing** — `window.appfunnel.on('purchase.complete', …)`
|
|
196
|
+
* — instead of the funnel hand-injecting pixel config per page. The pre-built pixel
|
|
197
|
+
* loaders (Meta/TikTok/GTM/Clarity) are just subscribers; CAPI stays the server-side
|
|
198
|
+
* default (this is the *client* sink of the same events). See doc 08 §3.
|
|
199
|
+
*
|
|
200
|
+
* Two deliberate properties vs. v0:
|
|
201
|
+
* - **Replay history** (`events`): async pixels load *after* `funnel.start`/`page.view`
|
|
202
|
+
* fire, so a late subscriber would miss them. The bus keeps a bounded history a
|
|
203
|
+
* subscriber can replay (the GTM `dataLayer` trick) — no lost events.
|
|
204
|
+
* - **Subscribe + read + drive.** The window surface exposes `on`/`off`/`events` + read
|
|
205
|
+
* getters, and — resolving the deferred bus-control-scope decision (doc 08 §3/§4) —
|
|
206
|
+
* **flow control**: `goTo`/`goNext`/`goPrevious` + a `navigation()` snapshot, so a host
|
|
207
|
+
* page (the live editor, or an author's own external nav UI) can drive the funnel. It
|
|
208
|
+
* still has **no `emit`** (third-party code can't forge taxonomy events) and no
|
|
209
|
+
* `setVariable` (state stays funnel-owned). Flow control is a no-op (warns once) until a
|
|
210
|
+
* `<FunnelView>` mounts and registers itself via {@link FunnelBus.registerNavigation}.
|
|
211
|
+
*/
|
|
212
|
+
|
|
213
|
+
/** Read accessors the bus exposes — wired to the funnel's store + context. */
|
|
214
|
+
interface BusAccessors {
|
|
215
|
+
getVariable: (key: string) => unknown;
|
|
216
|
+
getVariables: () => Record<string, unknown>;
|
|
217
|
+
getCurrentPageId: () => string | null;
|
|
218
|
+
getCustomerId: () => string | null;
|
|
219
|
+
getVisitorId: () => string | null;
|
|
220
|
+
}
|
|
221
|
+
interface BusEvent {
|
|
222
|
+
event: string;
|
|
223
|
+
data: unknown;
|
|
224
|
+
/** Epoch ms the event was emitted (for ordering / replay). */
|
|
225
|
+
ts: number;
|
|
226
|
+
}
|
|
227
|
+
type Listener = (data: unknown) => void;
|
|
228
|
+
/** The flow-control seam a mounted `<FunnelView>` registers into the bus (SDK-internal). */
|
|
229
|
+
interface NavigationControl {
|
|
230
|
+
/** Jump to a page key. Returns `false` if the key isn't a real page (no navigation happened). */
|
|
231
|
+
goTo(pageKey: string): boolean;
|
|
232
|
+
goNext(): void;
|
|
233
|
+
goPrevious(): void;
|
|
234
|
+
snapshot(): NavSnapshot | null;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The public `window.appfunnel` surface — subscribe + read + drive the flow. It never
|
|
238
|
+
* exposes `emit` (no forged events) or `setVariable` (state stays funnel-owned).
|
|
239
|
+
*/
|
|
240
|
+
interface AppFunnelBusApi extends BusAccessors {
|
|
241
|
+
/** Subscribe to an event (or `'*'` for all). Returns an unsubscribe fn. */
|
|
242
|
+
on(event: string, cb: Listener): () => void;
|
|
243
|
+
off(event: string, cb: Listener): void;
|
|
244
|
+
/** Bounded replay history — read past events, then `on()` for future ones. */
|
|
245
|
+
readonly events: BusEvent[];
|
|
246
|
+
/** Jump the flow to a page key. Returns `false` for an unknown key (or before the flow mounts). */
|
|
247
|
+
goTo(pageKey: string): boolean;
|
|
248
|
+
/** Advance via the current page's `next` predicate (else the linear next). */
|
|
249
|
+
goNext(): void;
|
|
250
|
+
/** Step back to the previous page in flow history. */
|
|
251
|
+
goPrevious(): void;
|
|
252
|
+
/** The current flow snapshot, or `null` before a `<FunnelView>` mounts. */
|
|
253
|
+
navigation(): NavSnapshot | null;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The internal bus: the public {@link AppFunnelBusApi} plus `emit` (SDK-only) and
|
|
257
|
+
* `now` injection. The SDK holds this; only {@link AppFunnelBusApi} reaches `window`.
|
|
258
|
+
*/
|
|
259
|
+
interface FunnelBus extends AppFunnelBusApi {
|
|
260
|
+
emit(event: string, data?: unknown): void;
|
|
261
|
+
/** SDK-only: a mounted `<FunnelView>` registers its flow control; returns an unregister fn. */
|
|
262
|
+
registerNavigation(nav: NavigationControl): () => void;
|
|
263
|
+
}
|
|
264
|
+
/** Create a bus over the given read accessors. `now` is injectable for tests. */
|
|
265
|
+
declare function createBus(accessors: BusAccessors, now?: () => number): FunnelBus;
|
|
266
|
+
/**
|
|
267
|
+
* Wrap a {@link Tracker} so every emitted taxonomy event is mirrored to the bus
|
|
268
|
+
* (the client sink). `identify()` also surfaces a `user.registered` to the bus, so
|
|
269
|
+
* pixels see registration. The server sink (the inner tracker → CAPI) is untouched.
|
|
270
|
+
*/
|
|
271
|
+
declare function withBus(tracker: Tracker, bus: FunnelBus): Tracker;
|
|
272
|
+
declare global {
|
|
273
|
+
interface Window {
|
|
274
|
+
appfunnel?: AppFunnelBusApi;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Attach the bus's public surface to `window.appfunnel` (client-side only).
|
|
279
|
+
* Returns a detach fn for unmount. No-op under SSR.
|
|
280
|
+
*/
|
|
281
|
+
declare function attachBus(bus: FunnelBus): () => void;
|
|
282
|
+
|
|
189
283
|
/**
|
|
190
284
|
* v2 `products` — the **read-only** product catalog.
|
|
191
285
|
*
|
|
@@ -340,6 +434,15 @@ interface FunnelProviderProps {
|
|
|
340
434
|
config?: FunnelDefinition;
|
|
341
435
|
/** Server / session-restored values overriding declared defaults. */
|
|
342
436
|
sessionValues?: Record<string, VariableValue>;
|
|
437
|
+
/**
|
|
438
|
+
* FLASH-FREE HYDRATION (SSR-isolation Option B): when true, the store is seeded with
|
|
439
|
+
* DEFAULTS only for the initial (hydration) render — matching the build-time-prerendered
|
|
440
|
+
* DEFAULT-state markup — and `sessionValues` are applied as a POST-hydration state update in
|
|
441
|
+
* a mount effect (`store.setMany`), so a returning visitor's saved answers pop into
|
|
442
|
+
* already-visible controls without an empty-shell flash or a hydration mismatch. Off (the
|
|
443
|
+
* default) preserves the original behavior: `sessionValues` seed the store at construction
|
|
444
|
+
* (used by SSR itself and by non-prerendered mounts). The client entry sets this to true. */
|
|
445
|
+
deferSessionValues?: boolean;
|
|
343
446
|
/** The platform tracker; defaults to a silent console tracker (tests/dev). */
|
|
344
447
|
tracker?: Tracker;
|
|
345
448
|
/** Computed-context seed (funnel id, test/live mode, server-hydrated utm/session). */
|
|
@@ -364,7 +467,7 @@ interface FunnelProviderProps {
|
|
|
364
467
|
* Mounts a funnel: builds the writable {@link VariableStore} + the read-only
|
|
365
468
|
* {@link FunnelContext} (once) and provides the runtime to the tree via hooks.
|
|
366
469
|
*/
|
|
367
|
-
declare function FunnelProvider({ config, sessionValues, tracker, context: contextOpts, products, experiments, checkout, messages, locale: localeOverride, toaster, store, children, }: FunnelProviderProps): ReactNode;
|
|
470
|
+
declare function FunnelProvider({ config, sessionValues, deferSessionValues, tracker, context: contextOpts, products, experiments, checkout, messages, locale: localeOverride, toaster, store, children, }: FunnelProviderProps): ReactNode;
|
|
368
471
|
/**
|
|
369
472
|
* The imperative handle. Does **not** subscribe — use it in event handlers and
|
|
370
473
|
* effects (`useFunnel().responses.set({...})`). For reactive reads use the hooks.
|
|
@@ -477,9 +580,20 @@ declare function useExperiment(id: string): string | undefined;
|
|
|
477
580
|
*
|
|
478
581
|
* Must live inside `<FunnelProvider>`.
|
|
479
582
|
*/
|
|
480
|
-
declare function FunnelView({ pages, initialKey, layout, fallback, prefetch: prefetchMode, onNavigate, }: {
|
|
583
|
+
declare function FunnelView({ pages, initialKey, trustInitialKey, layout, fallback, prefetch: prefetchMode, onNavigate, }: {
|
|
481
584
|
pages: RuntimePage[];
|
|
482
585
|
initialKey?: string;
|
|
586
|
+
/**
|
|
587
|
+
* FLASH-FREE HYDRATION (SSR-isolation Option B, FIX 1): honor `initialKey` VERBATIM as the entry
|
|
588
|
+
* page, WITHOUT re-evaluating its entry `guard` on the client. The SERVER already resolved the
|
|
589
|
+
* true entry — either by evaluating a declarative guard to pick the prerendered target-vs-bounce,
|
|
590
|
+
* or by running the real guard inside the fallback SSR render — and threaded the resolved key
|
|
591
|
+
* here. With `deferSessionValues` on, the client's initial snapshot is EMPTY (saved answers arrive
|
|
592
|
+
* post-hydration), so re-running the guard here would wrongly bounce a qualified visitor and
|
|
593
|
+
* mismatch the server markup. Set by the client entry (via createFunnelTree) alongside
|
|
594
|
+
* `deferSessionValues`; the guard still protects in-funnel navigation and non-deferred mounts.
|
|
595
|
+
*/
|
|
596
|
+
trustInitialKey?: boolean;
|
|
483
597
|
layout?: ComponentType<{
|
|
484
598
|
children: ReactNode;
|
|
485
599
|
}>;
|
|
@@ -567,6 +681,23 @@ interface MountOpts {
|
|
|
567
681
|
/** The server-resolved session id backing `sessionValues` — seeds the tracker (fallback)
|
|
568
682
|
* and `context.session.id`. */
|
|
569
683
|
sessionId?: string | null;
|
|
684
|
+
/** Mount the cross-origin {@link EmbedBridge} — the Funnel Embed API (flow snapshot + goTo/goNext/
|
|
685
|
+
* goPrevious + screenshot, over the versioned wire protocol). Set ONLY by preview surfaces (the live
|
|
686
|
+
* editor + the sealed `/preview/<buildId>` build); a live prod funnel leaves it off so it can't be
|
|
687
|
+
* driven / can't leak its flow state to an embedder. */
|
|
688
|
+
embed?: boolean;
|
|
689
|
+
/** FLASH-FREE HYDRATION (SSR-isolation Option B): set by the CLIENT entry only. The initial
|
|
690
|
+
* render is seeded with DEFAULTS (matching the build-time default-state prerender it hydrates
|
|
691
|
+
* against), then `sessionValues` are applied post-hydration via `store.setMany` — a returning
|
|
692
|
+
* visitor's saved answers pop into already-visible controls, no empty flash, no mismatch. Unset
|
|
693
|
+
* on SSR (the server render must bake sessionValues into the markup, the original behavior). */
|
|
694
|
+
deferSessionValues?: boolean;
|
|
695
|
+
/** SSR-isolation Option B, FIX 1: render `initialKey` VERBATIM without evaluating its entry guard.
|
|
696
|
+
* Set by the build-time PRERENDER stage so a guarded page (paywall/upsell) prerenders its OWN
|
|
697
|
+
* default-state markup (the renderer decides target-vs-bounce at serve time by evaluating the
|
|
698
|
+
* guard against the visitor's session). Defaults to `deferSessionValues` (the client trusts the
|
|
699
|
+
* server-resolved key); the prerender sets it true explicitly while leaving sessionValues baked. */
|
|
700
|
+
trustInitialKey?: boolean;
|
|
570
701
|
}
|
|
571
702
|
/**
|
|
572
703
|
* The shape a funnel build's `mount.tsx` module must export — the named,
|
|
@@ -758,76 +889,6 @@ type ModalProps = OverlayCommon;
|
|
|
758
889
|
/** A centered dialog. Auto-binds to its `defineModal` — `<Modal>…`. */
|
|
759
890
|
declare function Modal(props: ModalProps): ReactNode;
|
|
760
891
|
|
|
761
|
-
/**
|
|
762
|
-
* The **`window.appfunnel` event bus** — the client-side integration surface
|
|
763
|
-
* (port of v0 `admin/lib/funnel/utils/appfunnelRuntime.ts`).
|
|
764
|
-
*
|
|
765
|
-
* Every taxonomy event the runtime emits is mirrored here, so on-page pixels and
|
|
766
|
-
* custom scripts integrate by **subscribing** — `window.appfunnel.on('purchase.complete', …)`
|
|
767
|
-
* — instead of the funnel hand-injecting pixel config per page. The pre-built pixel
|
|
768
|
-
* loaders (Meta/TikTok/GTM/Clarity) are just subscribers; CAPI stays the server-side
|
|
769
|
-
* default (this is the *client* sink of the same events). See doc 08 §3.
|
|
770
|
-
*
|
|
771
|
-
* Two deliberate properties vs. v0:
|
|
772
|
-
* - **Replay history** (`events`): async pixels load *after* `funnel.start`/`page.view`
|
|
773
|
-
* fire, so a late subscriber would miss them. The bus keeps a bounded history a
|
|
774
|
-
* subscriber can replay (the GTM `dataLayer` trick) — no lost events.
|
|
775
|
-
* - **Subscribe + read only.** The window surface exposes `on`/`off`/`events` + read
|
|
776
|
-
* getters; it has **no `emit`** (third-party code can't forge events) and **no
|
|
777
|
-
* mutating actions** (`setVariable`/navigation) — the funnel can't be driven through
|
|
778
|
-
* it. (v0's full-control Custom-JS API is deferred pending the bus-control-scope
|
|
779
|
-
* decision; doc 08 §3 + §4.)
|
|
780
|
-
*/
|
|
781
|
-
|
|
782
|
-
/** Read accessors the bus exposes — wired to the funnel's store + context. */
|
|
783
|
-
interface BusAccessors {
|
|
784
|
-
getVariable: (key: string) => unknown;
|
|
785
|
-
getVariables: () => Record<string, unknown>;
|
|
786
|
-
getCurrentPageId: () => string | null;
|
|
787
|
-
getCustomerId: () => string | null;
|
|
788
|
-
getVisitorId: () => string | null;
|
|
789
|
-
}
|
|
790
|
-
interface BusEvent {
|
|
791
|
-
event: string;
|
|
792
|
-
data: unknown;
|
|
793
|
-
/** Epoch ms the event was emitted (for ordering / replay). */
|
|
794
|
-
ts: number;
|
|
795
|
-
}
|
|
796
|
-
type Listener = (data: unknown) => void;
|
|
797
|
-
/** The public `window.appfunnel` surface — subscribe + read, never emit or mutate. */
|
|
798
|
-
interface AppFunnelBusApi extends BusAccessors {
|
|
799
|
-
/** Subscribe to an event (or `'*'` for all). Returns an unsubscribe fn. */
|
|
800
|
-
on(event: string, cb: Listener): () => void;
|
|
801
|
-
off(event: string, cb: Listener): void;
|
|
802
|
-
/** Bounded replay history — read past events, then `on()` for future ones. */
|
|
803
|
-
readonly events: BusEvent[];
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* The internal bus: the public {@link AppFunnelBusApi} plus `emit` (SDK-only) and
|
|
807
|
-
* `now` injection. The SDK holds this; only {@link AppFunnelBusApi} reaches `window`.
|
|
808
|
-
*/
|
|
809
|
-
interface FunnelBus extends AppFunnelBusApi {
|
|
810
|
-
emit(event: string, data?: unknown): void;
|
|
811
|
-
}
|
|
812
|
-
/** Create a bus over the given read accessors. `now` is injectable for tests. */
|
|
813
|
-
declare function createBus(accessors: BusAccessors, now?: () => number): FunnelBus;
|
|
814
|
-
/**
|
|
815
|
-
* Wrap a {@link Tracker} so every emitted taxonomy event is mirrored to the bus
|
|
816
|
-
* (the client sink). `identify()` also surfaces a `user.registered` to the bus, so
|
|
817
|
-
* pixels see registration. The server sink (the inner tracker → CAPI) is untouched.
|
|
818
|
-
*/
|
|
819
|
-
declare function withBus(tracker: Tracker, bus: FunnelBus): Tracker;
|
|
820
|
-
declare global {
|
|
821
|
-
interface Window {
|
|
822
|
-
appfunnel?: AppFunnelBusApi;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Attach the bus's public surface to `window.appfunnel` (client-side only).
|
|
827
|
-
* Returns a detach fn for unmount. No-op under SSR.
|
|
828
|
-
*/
|
|
829
|
-
declare function attachBus(bus: FunnelBus): () => void;
|
|
830
|
-
|
|
831
892
|
/**
|
|
832
893
|
* The **event catalog** — the single source of truth for *which* events exist and
|
|
833
894
|
* how each is routed (analytics vs. integrations vs. runtime-only). Pairs with the
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ComponentType, ButtonHTMLAttributes } from 'react';
|
|
3
|
-
import { V as VariableValue, a as VariableConfig, A as Acquisition, P as Product, D as Direction, F as FunnelContext, R as RuntimeExperiment, b as FunnelSnapshot, c as FunnelDefinition, d as buildContext, e as ProductInput, f as PageMeta, g as PageContext } from './manifest-
|
|
4
|
-
export { B as BrowserContext, h as BuildContextOptions, C as ClickIdContext, i as CompileInput, j as Condition, k as ConditionOp, l as DeviceContext, E as EdgeCondition, m as ExperimentIssue, n as ExperimentResolution, o as ExperimentValidation, p as ExperimentVariant, q as FlowPage, r as FunnelLocales, s as FunnelManifest, G as Gate, I as IdentityContext, t as Interval, L as LocaleContext, M as ManifestEdge, u as ManifestPage, v as ManifestValidation, w as Money, O as OsContext, x as PageType, y as Predicate, z as ResolvedProduct, H as Route, S as SessionContext, J as SystemContext, U as UtmContext, K as assignVariant, N as bucketingSeed, Q as buildAcquisition, T as buildCatalog, W as compileManifest, X as currencyExponent, Y as defineFunnel, Z as definePage, _ as entryGuard, $ as evaluateCondition, a0 as evaluateGate, a1 as expectedPathLength, a2 as fnv1a, a3 as formatMoney, a4 as formatProduct, a5 as hashToUnit, a6 as isRtl, a7 as isVariantKey, a8 as nextPage, a9 as outgoingKeys, aa as pageMeta, ab as parseSlotKey, ac as pickByWeight, ad as resolveExperiments, ae as resolveLocale, af as resolveProduct, ag as resolveRoute, ah as validateExperiments } from './manifest-
|
|
3
|
+
import { V as VariableValue, a as VariableConfig, A as Acquisition, P as Product, D as Direction, F as FunnelContext, R as RuntimeExperiment, b as FunnelSnapshot, c as FunnelDefinition, d as buildContext, e as ProductInput, f as PageMeta, g as PageContext } from './manifest-Dl7faaKe.js';
|
|
4
|
+
export { B as BrowserContext, h as BuildContextOptions, C as ClickIdContext, i as CompileInput, j as Condition, k as ConditionOp, l as DeviceContext, E as EdgeCondition, m as ExperimentIssue, n as ExperimentResolution, o as ExperimentValidation, p as ExperimentVariant, q as FlowPage, r as FunnelLocales, s as FunnelManifest, G as Gate, I as IdentityContext, t as Interval, L as LocaleContext, M as ManifestEdge, u as ManifestPage, v as ManifestValidation, w as Money, O as OsContext, x as PageType, y as Predicate, z as ResolvedProduct, H as Route, S as SessionContext, J as SystemContext, U as UtmContext, K as assignVariant, N as bucketingSeed, Q as buildAcquisition, T as buildCatalog, W as compileManifest, X as currencyExponent, Y as defineFunnel, Z as definePage, _ as entryGuard, $ as evaluateCondition, a0 as evaluateGate, a1 as expectedPathLength, a2 as fnv1a, a3 as formatMoney, a4 as formatProduct, a5 as hashToUnit, a6 as isRtl, a7 as isVariantKey, a8 as nextPage, a9 as outgoingKeys, aa as pageMeta, ab as parseSlotKey, ac as pickByWeight, ad as resolveExperiments, ae as resolveLocale, af as resolveProduct, ag as resolveRoute, ah as validateExperiments } from './manifest-Dl7faaKe.js';
|
|
5
|
+
import { NavSnapshot } from './protocol.js';
|
|
5
6
|
import { a as CheckoutDriver, C as CheckoutDriverContext } from './checkout-7Dy6IedP.js';
|
|
6
7
|
export { b as Checkout, c as CheckoutCallbacks, d as CheckoutHandle, e as CheckoutProps, f as CheckoutRequest, g as CheckoutResult, h as CheckoutResume, i as CheckoutStatus, F as FailureModalProps, j as FailureRoute, M as MockDriverOptions, O as OnFailedMap, U as Upsell, k as UpsellProps, l as UseCheckoutOptions, m as createMockDriver, n as driverWithEmail, u as useCheckout } from './checkout-7Dy6IedP.js';
|
|
7
8
|
import { ToasterProps } from 'sonner';
|
|
@@ -186,6 +187,99 @@ declare function useTracker(): {
|
|
|
186
187
|
track: Tracker['track'];
|
|
187
188
|
};
|
|
188
189
|
|
|
190
|
+
/**
|
|
191
|
+
* The **`window.appfunnel` event bus** — the client-side integration surface
|
|
192
|
+
* (port of v0 `admin/lib/funnel/utils/appfunnelRuntime.ts`).
|
|
193
|
+
*
|
|
194
|
+
* Every taxonomy event the runtime emits is mirrored here, so on-page pixels and
|
|
195
|
+
* custom scripts integrate by **subscribing** — `window.appfunnel.on('purchase.complete', …)`
|
|
196
|
+
* — instead of the funnel hand-injecting pixel config per page. The pre-built pixel
|
|
197
|
+
* loaders (Meta/TikTok/GTM/Clarity) are just subscribers; CAPI stays the server-side
|
|
198
|
+
* default (this is the *client* sink of the same events). See doc 08 §3.
|
|
199
|
+
*
|
|
200
|
+
* Two deliberate properties vs. v0:
|
|
201
|
+
* - **Replay history** (`events`): async pixels load *after* `funnel.start`/`page.view`
|
|
202
|
+
* fire, so a late subscriber would miss them. The bus keeps a bounded history a
|
|
203
|
+
* subscriber can replay (the GTM `dataLayer` trick) — no lost events.
|
|
204
|
+
* - **Subscribe + read + drive.** The window surface exposes `on`/`off`/`events` + read
|
|
205
|
+
* getters, and — resolving the deferred bus-control-scope decision (doc 08 §3/§4) —
|
|
206
|
+
* **flow control**: `goTo`/`goNext`/`goPrevious` + a `navigation()` snapshot, so a host
|
|
207
|
+
* page (the live editor, or an author's own external nav UI) can drive the funnel. It
|
|
208
|
+
* still has **no `emit`** (third-party code can't forge taxonomy events) and no
|
|
209
|
+
* `setVariable` (state stays funnel-owned). Flow control is a no-op (warns once) until a
|
|
210
|
+
* `<FunnelView>` mounts and registers itself via {@link FunnelBus.registerNavigation}.
|
|
211
|
+
*/
|
|
212
|
+
|
|
213
|
+
/** Read accessors the bus exposes — wired to the funnel's store + context. */
|
|
214
|
+
interface BusAccessors {
|
|
215
|
+
getVariable: (key: string) => unknown;
|
|
216
|
+
getVariables: () => Record<string, unknown>;
|
|
217
|
+
getCurrentPageId: () => string | null;
|
|
218
|
+
getCustomerId: () => string | null;
|
|
219
|
+
getVisitorId: () => string | null;
|
|
220
|
+
}
|
|
221
|
+
interface BusEvent {
|
|
222
|
+
event: string;
|
|
223
|
+
data: unknown;
|
|
224
|
+
/** Epoch ms the event was emitted (for ordering / replay). */
|
|
225
|
+
ts: number;
|
|
226
|
+
}
|
|
227
|
+
type Listener = (data: unknown) => void;
|
|
228
|
+
/** The flow-control seam a mounted `<FunnelView>` registers into the bus (SDK-internal). */
|
|
229
|
+
interface NavigationControl {
|
|
230
|
+
/** Jump to a page key. Returns `false` if the key isn't a real page (no navigation happened). */
|
|
231
|
+
goTo(pageKey: string): boolean;
|
|
232
|
+
goNext(): void;
|
|
233
|
+
goPrevious(): void;
|
|
234
|
+
snapshot(): NavSnapshot | null;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* The public `window.appfunnel` surface — subscribe + read + drive the flow. It never
|
|
238
|
+
* exposes `emit` (no forged events) or `setVariable` (state stays funnel-owned).
|
|
239
|
+
*/
|
|
240
|
+
interface AppFunnelBusApi extends BusAccessors {
|
|
241
|
+
/** Subscribe to an event (or `'*'` for all). Returns an unsubscribe fn. */
|
|
242
|
+
on(event: string, cb: Listener): () => void;
|
|
243
|
+
off(event: string, cb: Listener): void;
|
|
244
|
+
/** Bounded replay history — read past events, then `on()` for future ones. */
|
|
245
|
+
readonly events: BusEvent[];
|
|
246
|
+
/** Jump the flow to a page key. Returns `false` for an unknown key (or before the flow mounts). */
|
|
247
|
+
goTo(pageKey: string): boolean;
|
|
248
|
+
/** Advance via the current page's `next` predicate (else the linear next). */
|
|
249
|
+
goNext(): void;
|
|
250
|
+
/** Step back to the previous page in flow history. */
|
|
251
|
+
goPrevious(): void;
|
|
252
|
+
/** The current flow snapshot, or `null` before a `<FunnelView>` mounts. */
|
|
253
|
+
navigation(): NavSnapshot | null;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The internal bus: the public {@link AppFunnelBusApi} plus `emit` (SDK-only) and
|
|
257
|
+
* `now` injection. The SDK holds this; only {@link AppFunnelBusApi} reaches `window`.
|
|
258
|
+
*/
|
|
259
|
+
interface FunnelBus extends AppFunnelBusApi {
|
|
260
|
+
emit(event: string, data?: unknown): void;
|
|
261
|
+
/** SDK-only: a mounted `<FunnelView>` registers its flow control; returns an unregister fn. */
|
|
262
|
+
registerNavigation(nav: NavigationControl): () => void;
|
|
263
|
+
}
|
|
264
|
+
/** Create a bus over the given read accessors. `now` is injectable for tests. */
|
|
265
|
+
declare function createBus(accessors: BusAccessors, now?: () => number): FunnelBus;
|
|
266
|
+
/**
|
|
267
|
+
* Wrap a {@link Tracker} so every emitted taxonomy event is mirrored to the bus
|
|
268
|
+
* (the client sink). `identify()` also surfaces a `user.registered` to the bus, so
|
|
269
|
+
* pixels see registration. The server sink (the inner tracker → CAPI) is untouched.
|
|
270
|
+
*/
|
|
271
|
+
declare function withBus(tracker: Tracker, bus: FunnelBus): Tracker;
|
|
272
|
+
declare global {
|
|
273
|
+
interface Window {
|
|
274
|
+
appfunnel?: AppFunnelBusApi;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Attach the bus's public surface to `window.appfunnel` (client-side only).
|
|
279
|
+
* Returns a detach fn for unmount. No-op under SSR.
|
|
280
|
+
*/
|
|
281
|
+
declare function attachBus(bus: FunnelBus): () => void;
|
|
282
|
+
|
|
189
283
|
/**
|
|
190
284
|
* v2 `products` — the **read-only** product catalog.
|
|
191
285
|
*
|
|
@@ -340,6 +434,15 @@ interface FunnelProviderProps {
|
|
|
340
434
|
config?: FunnelDefinition;
|
|
341
435
|
/** Server / session-restored values overriding declared defaults. */
|
|
342
436
|
sessionValues?: Record<string, VariableValue>;
|
|
437
|
+
/**
|
|
438
|
+
* FLASH-FREE HYDRATION (SSR-isolation Option B): when true, the store is seeded with
|
|
439
|
+
* DEFAULTS only for the initial (hydration) render — matching the build-time-prerendered
|
|
440
|
+
* DEFAULT-state markup — and `sessionValues` are applied as a POST-hydration state update in
|
|
441
|
+
* a mount effect (`store.setMany`), so a returning visitor's saved answers pop into
|
|
442
|
+
* already-visible controls without an empty-shell flash or a hydration mismatch. Off (the
|
|
443
|
+
* default) preserves the original behavior: `sessionValues` seed the store at construction
|
|
444
|
+
* (used by SSR itself and by non-prerendered mounts). The client entry sets this to true. */
|
|
445
|
+
deferSessionValues?: boolean;
|
|
343
446
|
/** The platform tracker; defaults to a silent console tracker (tests/dev). */
|
|
344
447
|
tracker?: Tracker;
|
|
345
448
|
/** Computed-context seed (funnel id, test/live mode, server-hydrated utm/session). */
|
|
@@ -364,7 +467,7 @@ interface FunnelProviderProps {
|
|
|
364
467
|
* Mounts a funnel: builds the writable {@link VariableStore} + the read-only
|
|
365
468
|
* {@link FunnelContext} (once) and provides the runtime to the tree via hooks.
|
|
366
469
|
*/
|
|
367
|
-
declare function FunnelProvider({ config, sessionValues, tracker, context: contextOpts, products, experiments, checkout, messages, locale: localeOverride, toaster, store, children, }: FunnelProviderProps): ReactNode;
|
|
470
|
+
declare function FunnelProvider({ config, sessionValues, deferSessionValues, tracker, context: contextOpts, products, experiments, checkout, messages, locale: localeOverride, toaster, store, children, }: FunnelProviderProps): ReactNode;
|
|
368
471
|
/**
|
|
369
472
|
* The imperative handle. Does **not** subscribe — use it in event handlers and
|
|
370
473
|
* effects (`useFunnel().responses.set({...})`). For reactive reads use the hooks.
|
|
@@ -477,9 +580,20 @@ declare function useExperiment(id: string): string | undefined;
|
|
|
477
580
|
*
|
|
478
581
|
* Must live inside `<FunnelProvider>`.
|
|
479
582
|
*/
|
|
480
|
-
declare function FunnelView({ pages, initialKey, layout, fallback, prefetch: prefetchMode, onNavigate, }: {
|
|
583
|
+
declare function FunnelView({ pages, initialKey, trustInitialKey, layout, fallback, prefetch: prefetchMode, onNavigate, }: {
|
|
481
584
|
pages: RuntimePage[];
|
|
482
585
|
initialKey?: string;
|
|
586
|
+
/**
|
|
587
|
+
* FLASH-FREE HYDRATION (SSR-isolation Option B, FIX 1): honor `initialKey` VERBATIM as the entry
|
|
588
|
+
* page, WITHOUT re-evaluating its entry `guard` on the client. The SERVER already resolved the
|
|
589
|
+
* true entry — either by evaluating a declarative guard to pick the prerendered target-vs-bounce,
|
|
590
|
+
* or by running the real guard inside the fallback SSR render — and threaded the resolved key
|
|
591
|
+
* here. With `deferSessionValues` on, the client's initial snapshot is EMPTY (saved answers arrive
|
|
592
|
+
* post-hydration), so re-running the guard here would wrongly bounce a qualified visitor and
|
|
593
|
+
* mismatch the server markup. Set by the client entry (via createFunnelTree) alongside
|
|
594
|
+
* `deferSessionValues`; the guard still protects in-funnel navigation and non-deferred mounts.
|
|
595
|
+
*/
|
|
596
|
+
trustInitialKey?: boolean;
|
|
483
597
|
layout?: ComponentType<{
|
|
484
598
|
children: ReactNode;
|
|
485
599
|
}>;
|
|
@@ -567,6 +681,23 @@ interface MountOpts {
|
|
|
567
681
|
/** The server-resolved session id backing `sessionValues` — seeds the tracker (fallback)
|
|
568
682
|
* and `context.session.id`. */
|
|
569
683
|
sessionId?: string | null;
|
|
684
|
+
/** Mount the cross-origin {@link EmbedBridge} — the Funnel Embed API (flow snapshot + goTo/goNext/
|
|
685
|
+
* goPrevious + screenshot, over the versioned wire protocol). Set ONLY by preview surfaces (the live
|
|
686
|
+
* editor + the sealed `/preview/<buildId>` build); a live prod funnel leaves it off so it can't be
|
|
687
|
+
* driven / can't leak its flow state to an embedder. */
|
|
688
|
+
embed?: boolean;
|
|
689
|
+
/** FLASH-FREE HYDRATION (SSR-isolation Option B): set by the CLIENT entry only. The initial
|
|
690
|
+
* render is seeded with DEFAULTS (matching the build-time default-state prerender it hydrates
|
|
691
|
+
* against), then `sessionValues` are applied post-hydration via `store.setMany` — a returning
|
|
692
|
+
* visitor's saved answers pop into already-visible controls, no empty flash, no mismatch. Unset
|
|
693
|
+
* on SSR (the server render must bake sessionValues into the markup, the original behavior). */
|
|
694
|
+
deferSessionValues?: boolean;
|
|
695
|
+
/** SSR-isolation Option B, FIX 1: render `initialKey` VERBATIM without evaluating its entry guard.
|
|
696
|
+
* Set by the build-time PRERENDER stage so a guarded page (paywall/upsell) prerenders its OWN
|
|
697
|
+
* default-state markup (the renderer decides target-vs-bounce at serve time by evaluating the
|
|
698
|
+
* guard against the visitor's session). Defaults to `deferSessionValues` (the client trusts the
|
|
699
|
+
* server-resolved key); the prerender sets it true explicitly while leaving sessionValues baked. */
|
|
700
|
+
trustInitialKey?: boolean;
|
|
570
701
|
}
|
|
571
702
|
/**
|
|
572
703
|
* The shape a funnel build's `mount.tsx` module must export — the named,
|
|
@@ -758,76 +889,6 @@ type ModalProps = OverlayCommon;
|
|
|
758
889
|
/** A centered dialog. Auto-binds to its `defineModal` — `<Modal>…`. */
|
|
759
890
|
declare function Modal(props: ModalProps): ReactNode;
|
|
760
891
|
|
|
761
|
-
/**
|
|
762
|
-
* The **`window.appfunnel` event bus** — the client-side integration surface
|
|
763
|
-
* (port of v0 `admin/lib/funnel/utils/appfunnelRuntime.ts`).
|
|
764
|
-
*
|
|
765
|
-
* Every taxonomy event the runtime emits is mirrored here, so on-page pixels and
|
|
766
|
-
* custom scripts integrate by **subscribing** — `window.appfunnel.on('purchase.complete', …)`
|
|
767
|
-
* — instead of the funnel hand-injecting pixel config per page. The pre-built pixel
|
|
768
|
-
* loaders (Meta/TikTok/GTM/Clarity) are just subscribers; CAPI stays the server-side
|
|
769
|
-
* default (this is the *client* sink of the same events). See doc 08 §3.
|
|
770
|
-
*
|
|
771
|
-
* Two deliberate properties vs. v0:
|
|
772
|
-
* - **Replay history** (`events`): async pixels load *after* `funnel.start`/`page.view`
|
|
773
|
-
* fire, so a late subscriber would miss them. The bus keeps a bounded history a
|
|
774
|
-
* subscriber can replay (the GTM `dataLayer` trick) — no lost events.
|
|
775
|
-
* - **Subscribe + read only.** The window surface exposes `on`/`off`/`events` + read
|
|
776
|
-
* getters; it has **no `emit`** (third-party code can't forge events) and **no
|
|
777
|
-
* mutating actions** (`setVariable`/navigation) — the funnel can't be driven through
|
|
778
|
-
* it. (v0's full-control Custom-JS API is deferred pending the bus-control-scope
|
|
779
|
-
* decision; doc 08 §3 + §4.)
|
|
780
|
-
*/
|
|
781
|
-
|
|
782
|
-
/** Read accessors the bus exposes — wired to the funnel's store + context. */
|
|
783
|
-
interface BusAccessors {
|
|
784
|
-
getVariable: (key: string) => unknown;
|
|
785
|
-
getVariables: () => Record<string, unknown>;
|
|
786
|
-
getCurrentPageId: () => string | null;
|
|
787
|
-
getCustomerId: () => string | null;
|
|
788
|
-
getVisitorId: () => string | null;
|
|
789
|
-
}
|
|
790
|
-
interface BusEvent {
|
|
791
|
-
event: string;
|
|
792
|
-
data: unknown;
|
|
793
|
-
/** Epoch ms the event was emitted (for ordering / replay). */
|
|
794
|
-
ts: number;
|
|
795
|
-
}
|
|
796
|
-
type Listener = (data: unknown) => void;
|
|
797
|
-
/** The public `window.appfunnel` surface — subscribe + read, never emit or mutate. */
|
|
798
|
-
interface AppFunnelBusApi extends BusAccessors {
|
|
799
|
-
/** Subscribe to an event (or `'*'` for all). Returns an unsubscribe fn. */
|
|
800
|
-
on(event: string, cb: Listener): () => void;
|
|
801
|
-
off(event: string, cb: Listener): void;
|
|
802
|
-
/** Bounded replay history — read past events, then `on()` for future ones. */
|
|
803
|
-
readonly events: BusEvent[];
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* The internal bus: the public {@link AppFunnelBusApi} plus `emit` (SDK-only) and
|
|
807
|
-
* `now` injection. The SDK holds this; only {@link AppFunnelBusApi} reaches `window`.
|
|
808
|
-
*/
|
|
809
|
-
interface FunnelBus extends AppFunnelBusApi {
|
|
810
|
-
emit(event: string, data?: unknown): void;
|
|
811
|
-
}
|
|
812
|
-
/** Create a bus over the given read accessors. `now` is injectable for tests. */
|
|
813
|
-
declare function createBus(accessors: BusAccessors, now?: () => number): FunnelBus;
|
|
814
|
-
/**
|
|
815
|
-
* Wrap a {@link Tracker} so every emitted taxonomy event is mirrored to the bus
|
|
816
|
-
* (the client sink). `identify()` also surfaces a `user.registered` to the bus, so
|
|
817
|
-
* pixels see registration. The server sink (the inner tracker → CAPI) is untouched.
|
|
818
|
-
*/
|
|
819
|
-
declare function withBus(tracker: Tracker, bus: FunnelBus): Tracker;
|
|
820
|
-
declare global {
|
|
821
|
-
interface Window {
|
|
822
|
-
appfunnel?: AppFunnelBusApi;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
/**
|
|
826
|
-
* Attach the bus's public surface to `window.appfunnel` (client-side only).
|
|
827
|
-
* Returns a detach fn for unmount. No-op under SSR.
|
|
828
|
-
*/
|
|
829
|
-
declare function attachBus(bus: FunnelBus): () => void;
|
|
830
|
-
|
|
831
892
|
/**
|
|
832
893
|
* The **event catalog** — the single source of truth for *which* events exist and
|
|
833
894
|
* how each is routed (analytics vs. integrations vs. runtime-only). Pairs with the
|