@joryio/react-native-sdk 0.0.0-stage → 1.3.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/lib/index.d.ts ADDED
@@ -0,0 +1,1098 @@
1
+ /**
2
+ * Mirrors the backend's SUBSCRIPTION_CHANNELS and the web SDK. Five, including
3
+ * viber - it was added to the backend and to no SDK.
4
+ */
5
+ export type SubscriptionChannel = 'email' | 'sms' | 'whatsapp' | 'push' | 'viber';
6
+ /**
7
+ * Wire values are camelCase. The backend 400s on `opted_in` - a
8
+ * NormalizeEnumCasing migration renamed the stored value and the enum NAME,
9
+ * not the wire string, is what changed.
10
+ */
11
+ export type SubscriptionStatus = 'optedIn' | 'subscribed' | 'unsubscribed';
12
+ export interface JoryioConfig {
13
+ /**
14
+ * Ask for notification permission automatically, soon after initialize().
15
+ *
16
+ * **Off by default, and that default is the recommendation.** The system
17
+ * prompt can be shown exactly once and a refusal is permanent short of a trip
18
+ * to Settings, so firing it at launch - before the user has any reason to
19
+ * want notifications from you - is how apps spend their single ask for
20
+ * nothing. Prefer a primer (the `request_push_permission` in-app button) and
21
+ * call `requestPushPermission()` when the user says yes.
22
+ *
23
+ * Turn this on only when notifications ARE the product.
24
+ *
25
+ * Works on both platforms. On iOS the prompt fires straight after
26
+ * initialize(); on Android it waits for the first foreground, because the
27
+ * system prompt there needs an Activity.
28
+ */
29
+ requestPushPermissionAtLaunch?: boolean;
30
+ /** Enable debug logging (default: false) */
31
+ enableDebug?: boolean;
32
+ /**
33
+ * Log level: 'debug' | 'info' | 'warn' | 'error'.
34
+ *
35
+ * No fixed default: left unset, both native bridges derive it from
36
+ * `enableDebug` - 'debug' when that is true, 'error' otherwise. Set it only
37
+ * to override that derivation (for example 'warn' with `enableDebug: true`).
38
+ */
39
+ logLevel?: 'debug' | 'info' | 'warn' | 'error';
40
+ /** Event batch size before auto-flush. Omit to use the native default (50). */
41
+ batchSize?: number;
42
+ /** Event flush interval in ms. Omit to use the native default (5000). */
43
+ flushInterval?: number;
44
+ /**
45
+ * Send each event as soon as it is tracked instead of batching (default:
46
+ * false). Not recommended for production - one request per event.
47
+ */
48
+ sendImmediately?: boolean;
49
+ /**
50
+ * Most events the local queue holds while flushes fail; beyond it the oldest
51
+ * are dropped. Omit to use the native default (1000).
52
+ */
53
+ maxQueueSize?: number;
54
+ /** Per-request HTTP timeout in ms. Omit to use the native default (10000). */
55
+ requestTimeout?: number;
56
+ /** Session timeout in ms. Omit to use the native default (1800000 / 30 min). */
57
+ sessionTimeout?: number;
58
+ /** Automatically track session start (default: true) */
59
+ trackSessionStart?: boolean;
60
+ /** Initial user ID if known at init time */
61
+ userId?: string;
62
+ /**
63
+ * Your own anonymous id, if you already have one for this device.
64
+ *
65
+ * Used for this run and stored only when nothing is stored yet, exactly as
66
+ * the native SDKs and the web SDK treat it - so an id set once at init
67
+ * survives the app dropping the option later. Omit to let the SDK generate
68
+ * one.
69
+ */
70
+ anonymousId?: string;
71
+ /**
72
+ * Enable JWT-based SDK Authentication (default: false). When enabled, supply a
73
+ * customer-minted JWT via `sdkAuthenticationToken` and/or
74
+ * `setSdkAuthenticationToken()`; it is attached to every ingest request.
75
+ */
76
+ enableSdkAuthentication?: boolean;
77
+ /** Initial SDK-authentication JWT, if known at init time. */
78
+ sdkAuthenticationToken?: string;
79
+ /**
80
+ * Allow in-app messages authored as HTML, which can execute JavaScript
81
+ * inside your app.
82
+ *
83
+ * OFF BY DEFAULT. The server strips `<script>` and every `on*` handler
84
+ * before sending, so author JavaScript does not run in normal operation. The
85
+ * document is still rendered in a web view that can execute script, so the
86
+ * residual risk is a sanitiser bypass - and because the document is authored
87
+ * server-side, a compromised marketing account would then mean code
88
+ * executing in every user's session, which is why regulated apps often
89
+ * prohibit it outright.
90
+ *
91
+ * Leaving this off does NOT disable in-app messaging: native messages still
92
+ * display, because they are data your app renders with its own components.
93
+ */
94
+ allowHtmlJsInAppMessages?: boolean;
95
+ /**
96
+ * Tracking consent at start-up. Same option and values as the web, iOS,
97
+ * Android and Unity SDKs.
98
+ *
99
+ * Leave it UNSET unless your consent banner has an answer for this launch.
100
+ * Unset means "no decision here": with nothing stored the SDK runs as
101
+ * `'granted'`, and a `'denied'` stored by an earlier launch stays in force.
102
+ * Passing `'granted'` is an explicit decision and lifts a stored denial.
103
+ *
104
+ * - `'granted'` - normal operation.
105
+ * - `'pending'` - nothing is SENT: events and attribute writes queue on the
106
+ * device (bounded by `maxQueueSize`), and there is no session start, no
107
+ * in-app sync and no push registration until you call
108
+ * `updateConsent('granted')`. `updateConsent('denied')` discards them.
109
+ * Applies to this launch only and never replaces a stored `'denied'`.
110
+ * - `'denied'` - nothing is collected and queued events are cleared. Stored,
111
+ * so it holds on later launches until `updateConsent('granted')` or an
112
+ * explicit `trackingConsent: 'granted'`.
113
+ *
114
+ * Consent and the opt-out ({@link JoryioSDK.optOut}) are separate switches,
115
+ * and any "no" wins: `'granted'` never cancels an opt-out.
116
+ */
117
+ trackingConsent?: TrackingConsent;
118
+ /**
119
+ * Start opted out of tracking (default: false). The same as calling
120
+ * {@link JoryioSDK.optOut} straight after initialize(), without the window in
121
+ * between. An opt-out is stored on the device and survives restarts, so
122
+ * passing `false` here does not undo an earlier `optOut()` - call `optIn()`.
123
+ */
124
+ optOut?: boolean;
125
+ /**
126
+ * Android only: the name of the notification channel Joryio push uses, as
127
+ * people see it in system Settings (Android 8+). Pass it localized - for
128
+ * example from your app's own strings. Omit to use the native default,
129
+ * "Notifications". Ignored on iOS, which has no notification channels.
130
+ */
131
+ notificationChannelName?: string;
132
+ }
133
+ /** Tracking consent values, identical on every Joryio SDK. */
134
+ export type TrackingConsent = 'granted' | 'denied' | 'pending';
135
+ /**
136
+ * Attribute values. `null` UNSETS the attribute - the same as
137
+ * `unsetAttribute(key)` - on both platforms.
138
+ */
139
+ export type UserAttributes = Record<string, string | number | boolean | null>;
140
+ /**
141
+ * Notification permission state, identical on iOS and Android.
142
+ *
143
+ * `unavailable` means we could not ask at all (no Activity, SDK not ready, or a
144
+ * platform with no notification support) - distinct from `denied`, which means
145
+ * the user said no and the prompt will not appear again.
146
+ */
147
+ export type PushPermissionStatus = 'granted' | 'denied' | 'notDetermined' | 'unavailable';
148
+ /** Reason an ingest request was rejected with a 401 `sdk_authentication_error`. */
149
+ export interface SdkAuthError {
150
+ reason: 'missing' | 'invalid' | 'expired' | 'sub_mismatch' | 'unknown';
151
+ /**
152
+ * Raw wire `reason` string, for logging / forward-compat.
153
+ *
154
+ * Worth reading whenever `reason` is `'unknown'`: that value means only that
155
+ * THIS SDK version did not recognise what the server sent, and this is the
156
+ * only place the server's actual word survives.
157
+ */
158
+ rawReason?: string;
159
+ /** The ingest endpoint whose request was rejected. */
160
+ endpoint?: string;
161
+ /**
162
+ * The SDK has STOPPED trying for this request - either no fresh token
163
+ * arrived, or one did and was rejected as well.
164
+ *
165
+ * `false` means the SDK is asking you for a token; `true` means it gave up.
166
+ * Branch on this rather than on the arrival of an error, or a handler that
167
+ * signs the user out fires on the first recoverable rejection.
168
+ *
169
+ * Both platforms emit all four fields. Until 2026-08-31 iOS sent
170
+ * {reason, endpoint, refreshExhausted} and Android sent {reason, rawReason},
171
+ * so half of any handler was dead on whichever platform was not tested.
172
+ */
173
+ refreshExhausted?: boolean;
174
+ }
175
+ /**
176
+ * An in-app message to display.
177
+ *
178
+ * `kind` discriminates the content. BOTH kinds reach JS: a React Native app
179
+ * renders its own UI, so dropping native here would mean native campaigns never
180
+ * display in RN apps while still counting as delivered server-side.
181
+ *
182
+ * Native fields are TEXT, not markup - the server does not HTML-escape them
183
+ * (escaping would show the user `A &amp; B`), so rendering one into a WebView
184
+ * or `dangerouslySetInnerHTML` would reintroduce the injection native avoids.
185
+ * Put them in a <Text>.
186
+ */
187
+ export type InAppMessage = InAppMessageBase & ({
188
+ kind: 'html';
189
+ html: string;
190
+ css: string;
191
+ } | {
192
+ kind: 'native';
193
+ title?: string;
194
+ body: string;
195
+ imageUrl?: string;
196
+ buttons: Array<{
197
+ id: string;
198
+ text: string;
199
+ /**
200
+ * What the button does when your renderer draws it:
201
+ *
202
+ * - `dismiss` - close the message.
203
+ * - `url` - open `url` in the browser.
204
+ * - `deep_link` - route to `url` inside your app.
205
+ * - `request_push_permission` - a push primer's "yes" button: close
206
+ * the message and call `Joryio.requestPushPermission()`.
207
+ *
208
+ * Report the tap with `trackInAppImpression(message.id, 'clicked')`
209
+ * in every case.
210
+ */
211
+ action: InAppButtonAction;
212
+ url?: string;
213
+ }>;
214
+ closeButton: boolean;
215
+ backdropDismissible: boolean;
216
+ /**
217
+ * Optional presentation overrides set by the campaign author.
218
+ *
219
+ * Every field is optional and an absent one means INHERIT your app's
220
+ * own styling - which is why a message with no overrides looks like the
221
+ * rest of your UI. Apply the ones that are present; do not substitute
222
+ * defaults of your own for the ones that are not.
223
+ *
224
+ * `fontFamily` is best effort: apply it only if your app bundles that
225
+ * font, and keep your own typeface when it does not - substituting an
226
+ * arbitrary font looks worse than ignoring the override.
227
+ */
228
+ style?: {
229
+ backgroundColor?: string;
230
+ textColor?: string;
231
+ primaryButtonColor?: string;
232
+ primaryButtonTextColor?: string;
233
+ cornerRadius?: number;
234
+ /** Body size; scale the headline from it to keep the hierarchy. */
235
+ fontSize?: number;
236
+ titleWeight?: 'regular' | 'medium' | 'semibold' | 'bold';
237
+ /**
238
+ * `auto` (or absent) means align by the MESSAGE's own language, not
239
+ * the device locale - `I18nManager.isRTL` is the wrong test, because
240
+ * one workspace sends Hebrew and English to the same app. React
241
+ * Native's `writingDirection: 'auto'` does this for you.
242
+ */
243
+ textAlign?: 'auto' | 'start' | 'center' | 'end';
244
+ /** Apply only if your app bundles it; otherwise keep your own. */
245
+ fontFamily?: string;
246
+ };
247
+ });
248
+ /** Native in-app button actions, spelled as both native bridges send them. */
249
+ export type InAppButtonAction = 'dismiss' | 'url' | 'deep_link' | 'request_push_permission';
250
+ export interface InAppMessageBase {
251
+ id: string;
252
+ name: string;
253
+ type: 'modal' | 'banner' | 'slideup' | 'fullscreen' | 'custom';
254
+ priority: number;
255
+ }
256
+ /** The server's verdict on a form submitted from an HTML in-app message. */
257
+ export interface InAppFormResult {
258
+ ok: boolean;
259
+ message?: string;
260
+ redirect?: string;
261
+ errors?: string[];
262
+ }
263
+ /**
264
+ * Most fire-and-forget calls held while initialize() has not completed. Beyond
265
+ * it the OLDEST are dropped. The same cap and policy as every native SDK's
266
+ * pre-initialization buffer (contract item 20), so a call is not kept by one
267
+ * layer and dropped by the next. Before 1.3.0 the hold was unbounded: an app
268
+ * that never called initialize() grew it for the life of the process.
269
+ */
270
+ export declare const MAX_PRE_INIT_CALLS = 1000;
271
+ /**
272
+ * Outcome of `registerPushToken`. Same four fields as the native
273
+ * `PushRegistrationResult` on Android and iOS.
274
+ */
275
+ export interface PushRegistrationResult {
276
+ success: boolean;
277
+ /** Provider or transport message on failure; absent on success. */
278
+ message?: string;
279
+ /** A bad or missing SDK key - retrying will not fix it. */
280
+ isUnauthorized: boolean;
281
+ /** Transient (offline, DNS, 5xx, not initialized yet): worth another try. */
282
+ isRetryable: boolean;
283
+ }
284
+ declare class JoryioSDK {
285
+ /**
286
+ * Resolves once initialize() has completed. This is the SDK's readiness
287
+ * primitive, and BOTH call shapes are built on it:
288
+ *
289
+ * - fire-and-forget calls (track, identify, …) chain onto it, so they run
290
+ * after init instead of hitting an uninitialized native module;
291
+ * - value-returning calls (getAnonymousId, getUserId, …) AWAIT it, which a
292
+ * callback queue could never do — you cannot buffer something the caller
293
+ * is waiting on the answer to.
294
+ *
295
+ * That gap is what broke React Native in-app messaging: the queue covered the
296
+ * void calls, so every getter called before init threw "Joryio SDK not
297
+ * initialized" straight through to a red box.
298
+ */
299
+ private _ready;
300
+ private _markReady;
301
+ /** True once initialize() has completed SUCCESSFULLY. */
302
+ private _initialized;
303
+ /**
304
+ * True when initialize() ran and FAILED. Distinct from "not yet initialized":
305
+ * that one is temporary and worth waiting for, this one never resolves, so
306
+ * every subsequent call must degrade instead of waiting or throwing.
307
+ */
308
+ private _initFailed;
309
+ /**
310
+ * Fire-and-forget calls made before initialize() succeeded, in call order,
311
+ * each with its {@link CallKind}. An opt-out or a denial made later
312
+ * discards the held `data` calls, and a wipe also the held `identity` and
313
+ * `token` calls, exactly as the native SDKs discard what they hold. A
314
+ * `withdrawal` is kept through all three and delivered (contract v4 36).
315
+ */
316
+ private _pending;
317
+ /** The initialize() call in progress, if any. A second call joins it. */
318
+ private _initInFlight;
319
+ private _warnedPendingOverflow;
320
+ /**
321
+ * A wipeData() was handed to native before its initialize(), which runs it
322
+ * inside init. A token set after it must stay held here and follow init:
323
+ * native would apply it before that wipe, which retires it.
324
+ */
325
+ private _wipeHandedToNative;
326
+ /**
327
+ * The two privacy switches as decided by calls made in this process
328
+ * before initialize() succeeded (contract v7 73): set by optOut()/optIn()
329
+ * and updateConsent('denied')/'granted' exactly as native records them -
330
+ * 'pending' never reopens a denial. null = no decision made here yet.
331
+ */
332
+ private _preInitOptedOut;
333
+ private _preInitDenied;
334
+ /**
335
+ * The two switches as the config of the initialize() IN FLIGHT sets them
336
+ * (contract v9 91): `trackingConsent: 'granted'` lifts a stored denial and
337
+ * 'denied' refuses; `optOut: true` refuses, `optOut: false` lifts nothing.
338
+ * Native applies them when that init runs, so a call made meanwhile is
339
+ * judged by them, between this process's decisions and the stored ones.
340
+ * null = the config says nothing; both cleared when that init settles.
341
+ */
342
+ private _initConfigOptedOut;
343
+ private _initConfigDenied;
344
+ /**
345
+ * The same two switches as STORED on the device by an earlier launch
346
+ * (contract v8 83), read from native when this module loads - both natives
347
+ * answer isUserOptedOut()/getTrackingConsent() from storage before their own
348
+ * initialize() (contract item 44). null until native has answered.
349
+ *
350
+ * Without them, `track('x'); optIn(); initialize()` for a person whose
351
+ * opt-out was stored last launch sent x: the call was held here, optIn went
352
+ * to native straight away, and x reached native only after init, when the
353
+ * opt-out was already lifted.
354
+ */
355
+ private _storedOptedOut;
356
+ private _storedDenied;
357
+ /** Settles once the stored decisions are known (or could not be read). */
358
+ private _storedKnown;
359
+ private _warnedDroppedWhileOptedOut;
360
+ /** Live onInAppMessage subscriptions. Rendering is handed to JS only while > 0. */
361
+ private _inAppListeners;
362
+ constructor();
363
+ /**
364
+ * Ask native, first thing, for the decisions an earlier launch stored. The
365
+ * first call this module makes, so native answers before any decision made
366
+ * in this process reaches it. Never rejects and never waits forever: an
367
+ * unreadable answer counts as "nothing stored", which leaves native's own
368
+ * judgement at replay - the behaviour before this read existed.
369
+ */
370
+ private _readStoredDecisions;
371
+ /**
372
+ * Whether collecting is refused before initialize(): a decision made in this
373
+ * process wins over the stored one (it is later). 'unknown' while the stored
374
+ * one is still being read and could make the answer yes.
375
+ */
376
+ private _refusedBeforeInit;
377
+ /**
378
+ * Native has answered: drop the `data` calls held while its answer was
379
+ * unknown that were made while refused, as they would have been dropped
380
+ * at call time had the answer been there (contract v7 73).
381
+ */
382
+ private _settleStored;
383
+ /**
384
+ * An initialize() failed, so native never applied its config: the `data`
385
+ * calls judged with it while it was in flight are judged again by this
386
+ * process's decisions and the stored ones alone (contract v9 91).
387
+ */
388
+ private _rejudgeWithoutInitConfig;
389
+ private _warnDroppedWhileRefused;
390
+ /**
391
+ * Wait for initialize(), but never forever.
392
+ *
393
+ * An unbounded await would be WORSE than the throw it replaces: a host app
394
+ * that forgets initialize() (or whose init failed) would hang on the first
395
+ * getter with no error to search for. On timeout the caller gets the same
396
+ * "not available" value it already gets when the native module is missing,
397
+ * plus a warning naming the actual cause.
398
+ *
399
+ * @returns true if the SDK became ready, false if it timed out.
400
+ */
401
+ private _awaitReady;
402
+ /**
403
+ * A privacy getter (isUserOptedOut, getTrackingConsent). Both native SDKs
404
+ * answer these from STORED state before their own initialize() (contract
405
+ * item 44), so before initialize() has been called - or after one that
406
+ * failed - native is asked straight away. They used to wait up to five
407
+ * seconds for an initialize() that might never come and then answer
408
+ * `false` / `null`, which reads as "not opted out" for a person whose
409
+ * opt-out is stored on the device.
410
+ *
411
+ * While an initialize() is in flight the answer waits for it, because a
412
+ * decision made in that window is queued behind it and native would not
413
+ * know it yet. Native is asked whatever the outcome (contract v6 65): when
414
+ * that init FAILS, the decisions held meanwhile have just been handed to
415
+ * native (_handHeldDecisionsToNative), so its stored answer is current. It
416
+ * used to answer the default `false` / `null` then without asking native.
417
+ */
418
+ private _queryDecision;
419
+ /**
420
+ * Route a fire-and-forget native call safely:
421
+ * - no-op when the native module isn't linked,
422
+ * - defer until initialize() resolves so we never hit native before init,
423
+ * - swallow (and warn) native errors so a bad call can't crash the host app.
424
+ */
425
+ private _dispatch;
426
+ /**
427
+ * Make room in a full pre-init hold (contract v5 51): the oldest call of the
428
+ * first kind present in EVICTION_ORDER goes. An unsubscribe, a push
429
+ * unregister or a privacy decision is dropped only when the hold holds
430
+ * nothing else. It used to drop the oldest call whatever it was, so an
431
+ * unsubscribe followed by a thousand track() calls lost the unsubscribe.
432
+ */
433
+ private _evictOneHeld;
434
+ /**
435
+ * A privacy decision - optOut, optIn, updateConsent, wipeData.
436
+ *
437
+ * Made BEFORE initialize() (contract item 25), it goes to the native module
438
+ * straight away instead of waiting in the queue above. Both native SDKs
439
+ * record a decision made before their own initialize() and apply it INSIDE
440
+ * it, ahead of the Session Start, the in-app sync and the push
441
+ * re-registration. Queued here, it reached native only after all three had
442
+ * run - so an app that called optOut() before initialize() still sent a
443
+ * Session Start for someone who had said no, and then a `tracking_opted_out`
444
+ * marker for a person who had never consented to anything.
445
+ *
446
+ * The native module processes calls in the order JS makes them, so the
447
+ * decision is recorded before the initialize() that follows it.
448
+ *
449
+ * @param discards the kinds of held call the decision drops instead of
450
+ * replaying after it: `data` for an opt-out or a denial, and also
451
+ * `identity` and `token` for a wipe. Nothing for optIn or a grant.
452
+ */
453
+ private _decide;
454
+ /**
455
+ * After a FAILED initialize(), hand the privacy decisions held while it was
456
+ * in flight to native now, in call order (contract v6 65). Nothing is in
457
+ * flight any more, so they take the pre-init path of _decide: native
458
+ * records each for its next initialize() and applies it ahead of that
459
+ * init's Session Start, in-app sync and push registration.
460
+ *
461
+ * Left held, they replayed only after the RETRY's initialize() and its side
462
+ * effects - a person who declined while the first init was failing got a
463
+ * Session Start - and after any decision made since the failure, which goes
464
+ * to native directly: an optIn() held from the failing window overrode a
465
+ * later optOut(), so the person ended opted in.
466
+ *
467
+ * Withdrawals go too, in place, while nothing but decisions and
468
+ * withdrawals precede them. One held behind a data, identity or control
469
+ * call stays held with it: handed ahead of an earlier subscribe for the
470
+ * same channel, the subscribe would replay later and supersede it; ahead of
471
+ * an identify, it would be filed under the wrong person. A wipe takes the
472
+ * withdrawals held before it along first, as _decide does, so native runs
473
+ * them under the identity being erased.
474
+ */
475
+ private _handHeldDecisionsToNative;
476
+ /** Hand the held withdrawals to native now, in order; keep the rest held. */
477
+ private _sendHeldWithdrawalsNow;
478
+ /** Drop the held calls of these kinds; keep the rest, in order. */
479
+ private _discardHeld;
480
+ private _invoke;
481
+ /**
482
+ * Replay the calls held before a SUCCESSFUL init. After a failed one they
483
+ * stay held for the next initialize() (see _dispatch).
484
+ */
485
+ private _drainPending;
486
+ /**
487
+ * Value-returning native call, gated on readiness, that never throws into
488
+ * the host app: a missing module, an init that never completed and a native
489
+ * rejection all answer `fallback`.
490
+ */
491
+ private _query;
492
+ /**
493
+ * Initialize the SDK. Must be called before any other method.
494
+ *
495
+ * @example
496
+ * ```ts
497
+ * import Joryio from '@joryio/react-native-sdk';
498
+ *
499
+ * // Each platform has its own key: iOS keys start with jry_sdk_ios_,
500
+ * // Android keys with jry_sdk_android_. The other platform's key is refused.
501
+ * const sdkKey = Platform.select({
502
+ * ios: 'jry_sdk_ios_abc123',
503
+ * android: 'jry_sdk_android_def456',
504
+ * })!;
505
+ * Joryio.initialize(sdkKey, 'api-eu1.joryio.com', {
506
+ * enableDebug: __DEV__,
507
+ * trackSessionStart: true,
508
+ * });
509
+ * ```
510
+ */
511
+ initialize(sdkKey: string, apiHost: string, config?: JoryioConfig): Promise<void>;
512
+ private _initialize;
513
+ /**
514
+ * Track a custom event.
515
+ *
516
+ * @example
517
+ * ```ts
518
+ * Joryio.track('Tutorial Completed', { step: 4 });
519
+ *
520
+ * // For e-commerce, prefer the typed surface - it applies the platform's
521
+ * // canonical event names and property keys for you. A hand-typed name that
522
+ * // is close but not exact is stored verbatim and matches no report, because
523
+ * // there is deliberately no alias layer:
524
+ * Joryio.ecommerce.addToCart({ productId: '123', name: 'Buds', price: 29.99, quantity: 1 });
525
+ * ```
526
+ */
527
+ track(eventName: string, properties?: Record<string, unknown>): void;
528
+ /**
529
+ * Track a screen view.
530
+ *
531
+ * @example
532
+ * ```ts
533
+ * Joryio.trackScreen('ProductDetail', { productId: '123' });
534
+ * ```
535
+ */
536
+ trackScreen(screenName: string, properties?: Record<string, unknown>): void;
537
+ /**
538
+ * Identify the current user by their user ID.
539
+ * Call this after login or when you know who the user is.
540
+ *
541
+ * @example
542
+ * ```ts
543
+ * Joryio.identify('user-123');
544
+ * ```
545
+ */
546
+ identify(userId: string): void;
547
+ /**
548
+ * Attach a labelled alias to the current person - an id from another system,
549
+ * such as `addAlias('crm_id', 'C-1042')`. Works for anonymous visitors as
550
+ * well as identified users: the alias is stored against the anonymous id and
551
+ * travels with it when the visitor is identified later.
552
+ *
553
+ * There is no unlabelled `alias(userId)`; to name the current person, call
554
+ * {@link identify}.
555
+ *
556
+ * @param aliasLabel what kind of id this is, e.g. `crm_id`
557
+ * @param aliasName the id itself
558
+ */
559
+ addAlias(aliasLabel: string, aliasName: string): void;
560
+ /**
561
+ * Reset the user identity. Call this on logout.
562
+ * Clears user ID, starts new anonymous session, clears in-app messages.
563
+ */
564
+ reset(): void;
565
+ /**
566
+ * Stop collecting and sending. Call when a user declines tracking.
567
+ *
568
+ * TRACKING consent, not marketing subscription - this does not unsubscribe
569
+ * anyone from email or SMS. Those live on the contact's subscription state
570
+ * and are changed through the API or the preference centre.
571
+ *
572
+ * Existed on web, Android, iOS and Unity; React Native had no consent surface
573
+ * at all, so an app built on it could not honour a "do not track" choice
574
+ * through the SDK. The native SDKs record `tracking_opted_out` on the profile
575
+ * as part of opting out, so that behaviour comes through this bridge
576
+ * unchanged - nothing about the wire is decided here.
577
+ *
578
+ * Stored on the device: it holds across restarts until {@link optIn}.
579
+ * Independent of tracking consent - `updateConsent('granted')` does not
580
+ * cancel it.
581
+ *
582
+ * Safe before initialize(), and applied FIRST: the SDK then starts opted out,
583
+ * with no Session Start, in-app sync or push registration in between. Calls
584
+ * that collect data made before it, or after it until {@link optIn}, are
585
+ * dropped: a later optIn() does not send them.
586
+ */
587
+ optOut(): void;
588
+ /**
589
+ * Resume collecting and sending after {@link optOut}. The only call that
590
+ * lifts an opt-out. It does not change tracking consent: with consent
591
+ * `'denied'` or `'pending'` nothing is sent until consent is granted too.
592
+ * Safe before initialize(), and applied before the SDK starts. Calls that
593
+ * collect data made before it while an opt-out was in force - one made in
594
+ * this launch or one stored by an earlier launch - are not sent by it.
595
+ */
596
+ optIn(): void;
597
+ /**
598
+ * Erase everything this device holds for the current person - identity,
599
+ * queued events, pending writes, in-app state, session, device id and push
600
+ * token - and unregister the token on the server. The opt-out is KEPT, so a
601
+ * person who opted out and asked for erasure stays opted out. Push stays
602
+ * unregistered until {@link registerPushToken} or a permission grant.
603
+ *
604
+ * Safe before initialize(), and applied before the SDK starts: nothing is
605
+ * sent under the erased identity first. Calls made before it that collect
606
+ * data are dropped, since the wipe would erase them anyway.
607
+ */
608
+ wipeData(): void;
609
+ /**
610
+ * Whether the user has opted out of tracking - the opt-out switch only
611
+ * ({@link optOut} / {@link optIn}). Consent is a separate switch; read it
612
+ * with {@link getTrackingConsent}.
613
+ *
614
+ * Safe before initialize(): the answer then comes from what is stored on the
615
+ * device, so an opt-out from an earlier launch reads as `true` straight
616
+ * away. While initialize() is running it waits for it, and then asks native
617
+ * whether that initialize() succeeded or failed. Answers `false` when the
618
+ * native module is not linked or the native read fails.
619
+ */
620
+ isUserOptedOut(): Promise<boolean>;
621
+ /**
622
+ * Change tracking consent - typically when the user answers your consent
623
+ * banner, for example after you initialized with `trackingConsent: 'pending'`.
624
+ *
625
+ * - `'granted'` releases everything held while consent was pending and
626
+ * starts normal collection. It lifts a stored `'denied'`, but never an
627
+ * opt-out: only {@link optIn} does that.
628
+ * - `'denied'` discards what was held and collects nothing more - calls
629
+ * made while denied are dropped, and a later `'granted'` does not send
630
+ * them. Stored, so it holds on later launches until `'granted'`.
631
+ * - `'pending'` holds data on the device and sends nothing, for this launch
632
+ * only. It never replaces a stored `'denied'`.
633
+ *
634
+ * It does not opt the user out and {@link isUserOptedOut} does not change.
635
+ * Safe before initialize(), and applied before the SDK starts.
636
+ */
637
+ updateConsent(consent: TrackingConsent): void;
638
+ /**
639
+ * The current tracking consent: `'granted'`, `'denied'` or `'pending'`.
640
+ * Separate from the opt-out, which {@link isUserOptedOut} reports.
641
+ *
642
+ * Safe before initialize(): the answer then comes from what is stored on the
643
+ * device (a stored `'denied'` reads as `'denied'`) and from any decision
644
+ * made earlier in this launch; with nothing decided and nothing stored it
645
+ * is `'granted'`, the state the SDK runs in, on both platforms. While
646
+ * initialize() is running it waits for it, and then asks native whether
647
+ * that initialize() succeeded or failed. Native's answer is passed through
648
+ * as it is. Answers `null` only when the native module is not linked or the
649
+ * native call fails.
650
+ */
651
+ getTrackingConsent(): Promise<TrackingConsent | null>;
652
+ /**
653
+ * Set the marketing subscription status for one channel.
654
+ *
655
+ * A DIFFERENT consent from {@link optOut}: that controls whether we may
656
+ * OBSERVE this person, this controls whether we may MESSAGE them. Neither
657
+ * implies the other - somebody can decline tracking and still want the
658
+ * newsletter.
659
+ *
660
+ * This is what an in-app preference centre writes. It existed on web only,
661
+ * so a mobile user had to find an email and click its unsubscribe link -
662
+ * unsubscribing being harder in the app than on the web, which is the wrong
663
+ * way round for the one action that must always be easy.
664
+ *
665
+ * @param channel one of email, sms, whatsapp, push, viber
666
+ * @param status one of optedIn, subscribed, unsubscribed
667
+ */
668
+ setSubscription(channel: SubscriptionChannel, status: SubscriptionStatus): void;
669
+ /** Join a subscription group (list) on a channel. */
670
+ addToSubscriptionGroup(groupId: string, channel: SubscriptionChannel): void;
671
+ /** Leave a subscription group (list) on a channel. */
672
+ removeFromSubscriptionGroup(groupId: string, channel: SubscriptionChannel): void;
673
+ /**
674
+ * Set multiple user attributes at once.
675
+ *
676
+ * @example
677
+ * ```ts
678
+ * Joryio.setAttributes({
679
+ * firstName: 'John',
680
+ * plan: 'premium',
681
+ * age: 28,
682
+ * });
683
+ * ```
684
+ */
685
+ setAttributes(attributes: UserAttributes): void;
686
+ /**
687
+ * Set a single user attribute. `null` unsets it.
688
+ */
689
+ setAttribute(key: string, value: string | number | boolean | null): void;
690
+ /**
691
+ * Increment a numeric user attribute.
692
+ *
693
+ * @example
694
+ * ```ts
695
+ * Joryio.incrementAttribute('loginCount', 1);
696
+ * ```
697
+ */
698
+ incrementAttribute(key: string, by?: number): void;
699
+ /**
700
+ * Remove a user attribute.
701
+ */
702
+ unsetAttribute(key: string): void;
703
+ /**
704
+ * Register a push notification token (FCM on Android, APNs on iOS).
705
+ * Typically called after receiving the token from Firebase or Apple.
706
+ *
707
+ * Resolves with the registration outcome - it never rejects, so a caller
708
+ * that ignores the promise gets exactly the previous fire-and-forget
709
+ * behaviour. Before 1.3.0 the bridge dropped the native result: a
710
+ * device-side DNS blip left the device unregistered for push with no
711
+ * signal and nothing to retry (Teamder, 2026-09-29). `isRetryable` says
712
+ * whether trying again is worth it; `isUnauthorized` says the SDK key is
713
+ * wrong and a retry will not help.
714
+ *
715
+ * On iOS register the APNs device token, never the FCM one: Joryio sends
716
+ * iOS push through APNs. A token that is not an APNs token (hex) is refused
717
+ * on iOS with `isRetryable: false`; registered, it would replace the
718
+ * device's working APNs token with one APNs rejects.
719
+ *
720
+ * @example
721
+ * ```ts
722
+ * // With @react-native-firebase/messaging
723
+ * const token = Platform.OS === 'ios'
724
+ * ? await messaging().getAPNSToken() // APNs device token (hex)
725
+ * : await messaging().getToken(); // FCM token
726
+ * if (token) {
727
+ * const result = await Joryio.registerPushToken(token);
728
+ * if (!result.success && result.isRetryable) {
729
+ * // transient (offline, DNS, 5xx): register again when the network is back
730
+ * }
731
+ * }
732
+ *
733
+ * // onTokenRefresh delivers the FCM token - on iOS too, at every app start.
734
+ * // Register it on Android only.
735
+ * messaging().onTokenRefresh((fcmToken) => {
736
+ * if (Platform.OS === 'android') Joryio.registerPushToken(fcmToken);
737
+ * });
738
+ * ```
739
+ */
740
+ registerPushToken(token: string): Promise<PushRegistrationResult>;
741
+ /**
742
+ * Report that a push ARRIVED on this device.
743
+ *
744
+ * Needed because most React Native apps run their OWN messaging service -
745
+ * @react-native-firebase/messaging, Expo notifications - so a push never
746
+ * passes through our native service and would report no delivery at all.
747
+ * The same apps already call `registerPushToken`; this is the matching half.
748
+ *
749
+ * ```ts
750
+ * messaging().onMessage(async (msg) => {
751
+ * if (msg.data?.trackingId) Joryio.reportPushDelivered(msg.data.trackingId);
752
+ * });
753
+ * messaging().setBackgroundMessageHandler(async (msg) => {
754
+ * if (msg.data?.trackingId) Joryio.reportPushDelivered(msg.data.trackingId);
755
+ * });
756
+ * ```
757
+ *
758
+ * @param trackingId the `trackingId` from the message's data payload.
759
+ */
760
+ reportPushDelivered(trackingId: string): void;
761
+ /**
762
+ * Unregister from push notifications.
763
+ */
764
+ unregisterPush(): void;
765
+ /**
766
+ * Where the user stands on notifications, without asking them anything.
767
+ *
768
+ * Check this BEFORE showing a primer: `notDetermined` is the ONLY state in
769
+ * which the system prompt can still appear. Push permission is one-shot on
770
+ * iOS and Android 13+ - a denial is recoverable only in system Settings - so a
771
+ * primer shown to an already-denied user spends their goodwill on a prompt
772
+ * that can never be displayed.
773
+ */
774
+ getPushPermissionStatus(): Promise<PushPermissionStatus>;
775
+ /**
776
+ * Show the system notification prompt, resolving the resulting status.
777
+ *
778
+ * The SDK never calls this for you. The prompt can only be shown once, and
779
+ * neither `initialize()` nor a dashboard toggle can know when your app has
780
+ * earned the right to ask - so the moment is yours. Ask after a booking, a
781
+ * purchase, or from a notification settings screen; not on first launch.
782
+ *
783
+ * ```ts
784
+ * if ((await Joryio.getPushPermissionStatus()) === 'notDetermined') {
785
+ * // show your own primer first, then:
786
+ * const status = await Joryio.requestPushPermission();
787
+ * }
788
+ * ```
789
+ *
790
+ * On Android this covers the API 33+ POST_NOTIFICATIONS runtime permission and
791
+ * registers the FCM token on a grant. Below API 33 nothing is shown and the
792
+ * current Settings state is reported.
793
+ *
794
+ * The same on both platforms when the user has opted out of tracking: the
795
+ * prompt is still shown (whether the app may NOTIFY is the user's choice in
796
+ * the OS, not a tracking decision) and the resulting status is resolved, but
797
+ * nothing is registered or reported to Joryio while opted out. iOS used to
798
+ * skip the prompt and answer `notDetermined`, so a primer asked forever.
799
+ *
800
+ * Always settles. If Android recreates the Activity while the prompt is up
801
+ * (a rotation, a theme change), the answer is lost with it; the promise then
802
+ * resolves with the status as it stands once the app is back in front.
803
+ */
804
+ requestPushPermission(): Promise<PushPermissionStatus>;
805
+ /**
806
+ * Check if push notifications are enabled.
807
+ */
808
+ isPushEnabled(): Promise<boolean>;
809
+ /**
810
+ * Track a push notification click (for analytics).
811
+ * Call this when the user taps a push notification.
812
+ */
813
+ trackPushClick(trackingId: string): void;
814
+ /**
815
+ * Listen for in-app messages that are ready to display.
816
+ * Returns an unsubscribe function.
817
+ *
818
+ * Subscribing OVERRIDES the SDK's own rendering: you draw the message, and
819
+ * therefore YOU MUST REPORT what happened via {@link trackInAppImpression}.
820
+ * The SDK cannot see a message it did not draw. Handing a message to you
821
+ * neither emits `in_app.displayed` nor spends the campaign's frequency
822
+ * budget: both happen when you report `'displayed'`, once, as the message
823
+ * appears - the same rule on iOS and Android. Report it exactly once per
824
+ * message you show; a message you never report counts toward no frequency
825
+ * cap and reads as delivered to nobody, which also breaks revenue
826
+ * attribution, which joins on that event.
827
+ *
828
+ * Declare what YOUR renderer can draw via `options.capabilities`. Campaigns
829
+ * are targeted on it, and an undeclared capability is read as "cannot" rather
830
+ * than "unknown" - so an app with `react-native-webview` should include
831
+ * `content.html`, and one without it should not. Omitting the option keeps the
832
+ * previous behaviour, where the value came from a native config flag
833
+ * describing the native SDK's own views, which never run here.
834
+ *
835
+ * @example
836
+ * ```ts
837
+ * const unsubscribe = Joryio.onInAppMessage((message) => {
838
+ * showMyOwnUI(message);
839
+ * Joryio.trackInAppImpression(message.id, 'displayed');
840
+ * // ... 'clicked' (with the button's id) when a button is tapped, and
841
+ * // 'dismissed' whenever the message leaves the screen - after a click too.
842
+ * });
843
+ *
844
+ * // Later:
845
+ * unsubscribe();
846
+ * ```
847
+ *
848
+ * A message you reported `'displayed'` holds the display slot until you
849
+ * report `'dismissed'`: until then no other in-app message is shown on iOS,
850
+ * and the same campaign is not shown again on Android. If `'dismissed'`
851
+ * never comes, the SDK frees the slot after 5 minutes. So report it whenever
852
+ * the message leaves the screen, including after a click.
853
+ *
854
+ * A callback that throws (or returns a promise that rejects) is caught and
855
+ * logged; it never crashes the app or stops other listeners.
856
+ *
857
+ * Unsubscribing the LAST listener hands rendering back to the native SDK, so
858
+ * messages that arrive afterwards are drawn natively instead of being handed
859
+ * to a listener that no longer exists. Before 1.3.0 they were silently lost
860
+ * on both platforms for the rest of the session.
861
+ */
862
+ onInAppMessage(callback: (message: InAppMessage) => unknown, options?: {
863
+ capabilities?: Array<'content.native' | 'content.html'>;
864
+ }): () => void;
865
+ /**
866
+ * Hand in-app messages to JavaScript and say what YOUR renderer can draw
867
+ * (`content.native`, `content.html`). `onInAppMessage` calls this for you;
868
+ * call it directly only when you listen for the `JoryioInAppMessage` event
869
+ * some other way, and pair it with {@link disableInAppMessages}.
870
+ *
871
+ * An empty or omitted list means "not declared" on both platforms - the SDK
872
+ * then describes its own renderer - never "this app can draw nothing".
873
+ */
874
+ enableInAppMessages(capabilities?: string[]): void;
875
+ /**
876
+ * Hand in-app rendering back to the native SDK after
877
+ * {@link enableInAppMessages}. Removing the last `onInAppMessage` listener
878
+ * does this for you.
879
+ */
880
+ disableInAppMessages(): void;
881
+ private _ecommerce?;
882
+ /**
883
+ * E-commerce events with the platform's canonical names applied for you:
884
+ * `Joryio.ecommerce.purchase(order)`.
885
+ *
886
+ * A real typed property, not something bolted on after construction. The
887
+ * first version assigned it with `(Joryio as any).ecommerce = ...`, which
888
+ * worked at runtime and was invisible to TypeScript - so every call site got
889
+ * "Property 'ecommerce' does not exist". The demo app found it immediately,
890
+ * which is what a reference integration is for.
891
+ */
892
+ get ecommerce(): JoryioEcommerce;
893
+ /**
894
+ * Report what happened to an in-app message YOUR app rendered.
895
+ *
896
+ * @param campaignId the message's `id`
897
+ * @param action `'displayed'` once when it appears (this is also what
898
+ * spends the campaign's frequency budget), `'clicked'`
899
+ * when a button is tapped, and `'dismissed'` whenever it
900
+ * leaves the screen - after a click too, because the
901
+ * message holds the display slot until then
902
+ * @param elementId for a click, the id of the button or link tapped - sent
903
+ * as `elementId`, the same name on every SDK, so reports can
904
+ * say which button. Omit it for other actions.
905
+ */
906
+ trackInAppImpression(campaignId: string, action: string, elementId?: string): void;
907
+ /**
908
+ * Submit a form shown inside an HTML in-app message that your app renders
909
+ * itself (a `<form data-jry-form>` in the campaign's html). The values are
910
+ * posted with the SDK's identity; the backend validates them against the
911
+ * form the campaign carries and writes the person. Resolves with the
912
+ * server's verdict - `{ ok, message?, redirect?, errors? }` - and never
913
+ * throws: a failure is `ok: false`.
914
+ */
915
+ submitInAppForm(campaignId: string, values: Record<string, string | string[]>): Promise<InAppFormResult>;
916
+ /**
917
+ * Set/replace the customer-minted JWT used for SDK Authentication.
918
+ * Attached as the `X-Joryio-Auth` header on every subsequent ingest request.
919
+ * Call after login, and again from your `onSdkAuthenticationError` handler to
920
+ * supply a freshly minted token.
921
+ *
922
+ * @example
923
+ * ```ts
924
+ * const jwt = await mintJoryioJwt(); // your backend mints it
925
+ * Joryio.setSdkAuthenticationToken(jwt);
926
+ * ```
927
+ */
928
+ setSdkAuthenticationToken(token: string): void;
929
+ /**
930
+ * Listen for SDK-authentication failures (the backend rejected the JWT with a
931
+ * 401 `sdk_authentication_error`). Mint a fresh JWT in the callback and call
932
+ * `setSdkAuthenticationToken()`; the refreshed token is carried on the next
933
+ * flush. Returns an unsubscribe function.
934
+ *
935
+ * @example
936
+ * ```ts
937
+ * const unsubscribe = Joryio.onSdkAuthenticationError(async ({ reason }) => {
938
+ * const jwt = await mintJoryioJwt(); // re-mint (esp. on 'expired')
939
+ * Joryio.setSdkAuthenticationToken(jwt);
940
+ * });
941
+ * ```
942
+ */
943
+ onSdkAuthenticationError(callback: (error: SdkAuthError) => unknown): () => void;
944
+ /**
945
+ * Flush the event queue immediately.
946
+ * Events are normally batched and sent periodically.
947
+ */
948
+ flush(): void;
949
+ /**
950
+ * Fetch in-app campaigns now and display any that are eligible.
951
+ *
952
+ * The SDK already syncs on session start and on foreground; use this for the
953
+ * moments in between - after resetDisplayedCampaigns(), or once the user has
954
+ * done something that should make them newly eligible.
955
+ *
956
+ * Both platforms. It was iOS-only, which was a missing BINDING rather than a
957
+ * missing capability - the Android facade has exposed `syncInAppCampaigns()`
958
+ * all along and the module simply never bound it, so this call was
959
+ * `undefined` on Android and did nothing at all.
960
+ *
961
+ * `?.()` is kept for a different reason than before: an app on an older
962
+ * native module than this JS package would otherwise throw
963
+ * "undefined is not a function".
964
+ */
965
+ syncInAppCampaigns(): void;
966
+ /**
967
+ * Forget which in-app campaigns have already been shown on this device.
968
+ *
969
+ * The SDK skips any campaign it has displayed before, and that set is
970
+ * persisted - so a message appears once per install and then never again.
971
+ * Useful when testing: without it you must delete and reinstall the app to
972
+ * see the same campaign twice.
973
+ *
974
+ * Both platforms, but they forget different things, and the difference is
975
+ * real rather than an oversight:
976
+ *
977
+ * iOS clears the persisted show-once-ever set AND the per-campaign
978
+ * impression history.
979
+ * Android has no show-once-ever set - a campaign with no frequency cap
980
+ * repeats there by design - so it clears the impression history
981
+ * and the cross-campaign gap, which is the state that actually
982
+ * suppresses a repeat on that platform.
983
+ *
984
+ * This was documented as "iOS ONLY - Android has nothing to reset", which was
985
+ * out of date: Joryio.kt has exposed resetDisplayedCampaigns() throughout and
986
+ * it does the second thing above. Debug-gated natively, so release builds are
987
+ * unaffected on both.
988
+ */
989
+ resetDisplayedCampaigns(): void;
990
+ /**
991
+ * What the SDK is EXPERIENCING: which server it talks to, and the last
992
+ * transport failure (null once a call succeeds again).
993
+ *
994
+ * "Initialized" and "the server accepts us" are different claims. Without
995
+ * this, a wrong SDK key looks identical to a healthy integration - the SDK
996
+ * reports itself up while every request is rejected 401.
997
+ *
998
+ * Implemented on BOTH platforms. It was iOS-only, which meant the one call you
999
+ * reach for when an integration misbehaves was absent on Android.
1000
+ *
1001
+ * It is also what the SDK is ACTUALLY doing, as opposed to the config your
1002
+ * app believes it set: `initialize()` ignores a second call, so changing
1003
+ * settings without restarting leaves the SDK on the old values while your UI
1004
+ * shows the new ones.
1005
+ */
1006
+ getDiagnostics(): Promise<{
1007
+ apiEndpoint: string | null;
1008
+ lastError?: {
1009
+ message: string;
1010
+ isUnauthorized: boolean;
1011
+ isRetryable: boolean;
1012
+ at: number;
1013
+ };
1014
+ }>;
1015
+ /**
1016
+ * Get the anonymous ID assigned to this device.
1017
+ */
1018
+ getAnonymousId(): Promise<string>;
1019
+ /**
1020
+ * Get the current user ID (null if not identified).
1021
+ */
1022
+ getUserId(): Promise<string | null>;
1023
+ /**
1024
+ * Get the current session ID.
1025
+ */
1026
+ getSessionId(): Promise<string>;
1027
+ }
1028
+ /** A product as the e-commerce tracker expects it. */
1029
+ export interface EcommerceProduct {
1030
+ productId: string;
1031
+ name: string;
1032
+ price: number;
1033
+ quantity?: number;
1034
+ category?: string;
1035
+ brand?: string;
1036
+ variant?: string;
1037
+ variantId?: string;
1038
+ sku?: string;
1039
+ imageUrl?: string;
1040
+ url?: string;
1041
+ }
1042
+ /** A cart line. `quantity` is required here; a line without one is not a line. */
1043
+ export interface EcommerceCartItem extends EcommerceProduct {
1044
+ quantity: number;
1045
+ }
1046
+ export interface EcommerceOrder {
1047
+ orderId: string;
1048
+ items: EcommerceCartItem[];
1049
+ value: number;
1050
+ currency?: string;
1051
+ shipping?: number;
1052
+ tax?: number;
1053
+ discount?: number;
1054
+ coupon?: string;
1055
+ }
1056
+ declare class JoryioEcommerce {
1057
+ private readonly sdk;
1058
+ constructor(sdk: JoryioSDK);
1059
+ private send;
1060
+ private product;
1061
+ /** Every line (a null entry is skipped, and not sent) a valid product. */
1062
+ private lines;
1063
+ private text;
1064
+ private amount;
1065
+ /** Absent is fine; present must be finite. */
1066
+ private optionalAmount;
1067
+ private cart;
1068
+ viewProduct(product: EcommerceProduct): void;
1069
+ viewCategory(categoryId: string, categoryName: string): void;
1070
+ search(query: string, resultCount?: number): void;
1071
+ addToCart(item: EcommerceCartItem, cartValue?: number): void;
1072
+ removeFromCart(product: EcommerceProduct, quantity?: number): void;
1073
+ cartViewed(items: EcommerceCartItem[], cartValue: number): void;
1074
+ updateCart(items: EcommerceCartItem[], cartValue: number): void;
1075
+ startCheckout(items: EcommerceCartItem[], cartValue: number): void;
1076
+ addPaymentInfo(paymentMethod: string): void;
1077
+ /**
1078
+ * An order. `orderId`, `items` (an array, may be empty) and a finite `value`
1079
+ * are required: the value is sent as `total`, the key revenue reports read,
1080
+ * and a purchase missing any of them is ignored with a warning rather than
1081
+ * recorded as a zero or an empty order.
1082
+ */
1083
+ purchase(order: EcommerceOrder): void;
1084
+ orderFulfilled(orderId: string, trackingNumber?: string, carrier?: string): void;
1085
+ orderDelivered(orderId: string): void;
1086
+ orderCancelled(orderId: string, reason?: string): void;
1087
+ orderRefunded(orderId: string, refundAmount?: number, reason?: string): void;
1088
+ addToWishlist(product: EcommerceProduct): void;
1089
+ removeFromWishlist(product: EcommerceProduct): void;
1090
+ shareProduct(product: EcommerceProduct, shareMethod: string): void;
1091
+ applyCoupon(couponCode: string, discountAmount?: number, discountType?: string): void;
1092
+ removeCoupon(couponCode: string): void;
1093
+ submitReview(productId: string, rating: number, reviewText?: string): void;
1094
+ }
1095
+ declare const Joryio: JoryioSDK;
1096
+ /** Also exported standalone, for `import {ecommerce} from ...`. */
1097
+ export declare const ecommerce: JoryioEcommerce;
1098
+ export default Joryio;