@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/CHANGELOG.md +5 -0
- package/LICENSE +54 -0
- package/README.md +385 -2
- package/android/build.gradle +90 -0
- package/android/src/main/java/io/joryio/reactnative/JoryioModule.kt +1188 -0
- package/android/src/main/java/io/joryio/reactnative/JoryioPackage.kt +27 -0
- package/ios/JoryioModule.m +113 -0
- package/ios/JoryioModule.swift +979 -0
- package/joryio-react-native.podspec +36 -0
- package/lib/index.d.ts +1098 -0
- package/lib/index.js +1928 -0
- package/lib/index.js.map +1 -0
- package/package.json +62 -4
- package/react-native.config.js +25 -0
- package/src/index.ts +2355 -0
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 & 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;
|