@sendoracloud/sdk-react-native 0.15.0 → 0.16.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/README.md +126 -43
- package/dist/index.cjs +447 -33
- package/dist/index.d.cts +176 -56
- package/dist/index.d.ts +176 -56
- package/dist/index.js +443 -32
- package/examples/links-router.tsx +79 -0
- package/examples/links-share.tsx +99 -0
- package/package.json +3 -2
package/dist/index.d.cts
CHANGED
|
@@ -305,35 +305,62 @@ declare class Auth {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
/**
|
|
308
|
-
* Sendora Deep Links surface for React Native.
|
|
308
|
+
* Sendora Deep Links surface for React Native (s58.50 — Pulse DX rewrite).
|
|
309
309
|
*
|
|
310
|
-
*
|
|
311
|
-
* 1. `create(...)`
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
310
|
+
* Surface:
|
|
311
|
+
* 1. `create<T>(...)` — mint a Sendora short link from inside the app.
|
|
312
|
+
* 2. `prewarm<T>(...)` — background-mint + cache so share row taps
|
|
313
|
+
* skip the HTTPS round-trip ActivityIndicator.
|
|
314
|
+
* 3. `handleUniversalLink(url)` — resolve a delivered universal / app link
|
|
315
|
+
* and fire `onLinkOpened`.
|
|
316
|
+
* 4. `matchDeferred({ ... })` — cold-launch deferred match.
|
|
317
|
+
* 5. `attachLinkingApi({ ... })` — one-call replacement for the
|
|
318
|
+
* `Linking.getInitialURL` + `addEventListener`
|
|
319
|
+
* + extract-pre-check + fallthrough boilerplate.
|
|
320
|
+
* 6. `computeDeviceFingerprint()` — canonical recipe (matches iOS + Android),
|
|
321
|
+
* no client-side boilerplate required.
|
|
322
|
+
* 7. `revoke(shortcode)` — soft-delete (private-content unsend).
|
|
323
|
+
* 8. `getStats(shortcode)` — totals + breakdowns; no dashboard scraping.
|
|
324
|
+
* 9. `onLinkOpened<T>(cb)` — generic-typed handler.
|
|
325
|
+
* 10. `LinkError` — typed errors with codes
|
|
326
|
+
* (`instanceof` + `err.code` rather than
|
|
327
|
+
* `err.message.includes(...)`).
|
|
321
328
|
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
329
|
+
* Bundle-id gate: SDK auto-supplies `iosBundleId` / `androidPackageName`
|
|
330
|
+
* from `init()` config — host app never has to thread them through per call.
|
|
331
|
+
* Sendora-side `apps` table is the source of truth — a leaked public key
|
|
332
|
+
* + wrong bundle = 422 from the backend mapped to
|
|
333
|
+
* `LinkError(code: "BUNDLE_MISMATCH")` here.
|
|
325
334
|
*/
|
|
326
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Error code taxonomy. Mirrors `apps/backend/src/modules/links/*` outcomes.
|
|
337
|
+
* Callers branch on `err.code` (string compare) or `err instanceof LinkError`.
|
|
338
|
+
*/
|
|
339
|
+
type LinkErrorCode = "BUNDLE_MISMATCH" | "DATA_TOO_LARGE" | "EXPIRED" | "NETWORK" | "RATE_LIMITED" | "NOT_FOUND" | "UNAUTHORIZED" | "INVALID_INPUT" | "PLAN_LIMIT" | "FALLBACK_REQUIRED" | "SERVER" | "UNKNOWN";
|
|
340
|
+
declare class LinkError extends Error {
|
|
341
|
+
readonly code: LinkErrorCode;
|
|
342
|
+
readonly statusCode: number;
|
|
343
|
+
readonly details?: unknown;
|
|
344
|
+
constructor(code: LinkErrorCode, message: string, statusCode?: number, details?: unknown);
|
|
345
|
+
}
|
|
346
|
+
/** Default linkData shape — opaque record. Override with a discriminated
|
|
347
|
+
* union or product-specific interface in the host app for full type-safety. */
|
|
348
|
+
type LinkData = Record<string, unknown>;
|
|
349
|
+
interface LinkCreateInput<T extends LinkData = LinkData> {
|
|
327
350
|
/** Customer-facing title. Shown in dashboard list views. */
|
|
328
351
|
title: string;
|
|
329
352
|
/** In-app deep-link path the host app routes to (e.g. `/articles/123`). */
|
|
330
353
|
iosDeepLinkPath?: string;
|
|
331
354
|
/** In-app deep-link path the host app routes to (e.g. `/articles/123`). */
|
|
332
355
|
androidDeepLinkPath?: string;
|
|
333
|
-
/**
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Web fallback URL. **Optional as of 0.16.0** — backend defaults from
|
|
358
|
+
* the project's apps registry (web origin > iOS App Store URL > Android
|
|
359
|
+
* Play Store URL). Pass explicitly for per-link override.
|
|
360
|
+
*/
|
|
361
|
+
fallbackUrl?: string;
|
|
362
|
+
/** Custom typed JSON delivered to onLinkOpened. Max 2KB serialized. */
|
|
363
|
+
linkData?: T;
|
|
337
364
|
/** Optional OG preview overrides for richer share cards. */
|
|
338
365
|
ogTitle?: string;
|
|
339
366
|
ogDescription?: string;
|
|
@@ -344,72 +371,130 @@ interface LinkCreateInput {
|
|
|
344
371
|
medium?: string;
|
|
345
372
|
channel?: string;
|
|
346
373
|
tags?: string[];
|
|
347
|
-
/** ISO timestamp when the link stops resolving. */
|
|
374
|
+
/** ISO timestamp when the link stops resolving. Must be future + within 5y. */
|
|
348
375
|
expiresAt?: string;
|
|
349
|
-
/** Bundle ID / package name for the bundle-id gate. */
|
|
376
|
+
/** Bundle ID / package name for the bundle-id gate. Defaults from init. */
|
|
350
377
|
iosBundleId?: string;
|
|
351
378
|
androidPackageName?: string;
|
|
352
379
|
}
|
|
353
|
-
interface LinkCreateResult {
|
|
380
|
+
interface LinkCreateResult<T extends LinkData = LinkData> {
|
|
354
381
|
id: string;
|
|
355
382
|
shortcode: string;
|
|
356
|
-
/** Fully qualified share URL —
|
|
383
|
+
/** Fully qualified share URL — pass to RN `Share.share({ message: ... })`. */
|
|
357
384
|
url: string;
|
|
358
385
|
iosDeepLinkPath: string | null;
|
|
359
386
|
androidDeepLinkPath: string | null;
|
|
360
387
|
fallbackUrl: string;
|
|
361
|
-
linkData:
|
|
388
|
+
linkData: T;
|
|
362
389
|
expiresAt: string | null;
|
|
363
390
|
}
|
|
364
|
-
interface LinkOpenedEvent {
|
|
391
|
+
interface LinkOpenedEvent<T extends LinkData = LinkData> {
|
|
365
392
|
shortcode: string;
|
|
366
|
-
/** Custom JSON the link was created with.
|
|
367
|
-
linkData:
|
|
368
|
-
/** Server's iOS deep-link path. Useful when host app's URL parser needs a hint. */
|
|
393
|
+
/** Custom typed JSON the link was created with. */
|
|
394
|
+
linkData: T;
|
|
369
395
|
iosDeepLinkPath: string | null;
|
|
370
|
-
/** Server's Android deep-link path. */
|
|
371
396
|
androidDeepLinkPath: string | null;
|
|
372
|
-
/**
|
|
373
|
-
* `true` when this event came from the deferred deep link path —
|
|
374
|
-
* i.e. user installed the app from the App / Play Store after
|
|
375
|
-
* clicking the link, and the SDK matched their fingerprint /
|
|
376
|
-
* install-referrer to the prior click.
|
|
377
|
-
*/
|
|
397
|
+
/** `true` when this event came from the deferred deep link path. */
|
|
378
398
|
isDeferred: boolean;
|
|
379
399
|
}
|
|
380
|
-
type LinkOpenedHandler = (event: LinkOpenedEvent) => void;
|
|
400
|
+
type LinkOpenedHandler<T extends LinkData = LinkData> = (event: LinkOpenedEvent<T>) => void;
|
|
381
401
|
interface LinkMatchInput {
|
|
382
|
-
/** Hex-encoded SHA-256
|
|
402
|
+
/** Hex-encoded SHA-256 — pass output of `computeDeviceFingerprint()`. */
|
|
383
403
|
fingerprintHash?: string;
|
|
384
|
-
/** Raw Play Install Referrer
|
|
404
|
+
/** Raw Play Install Referrer. Sendora probes
|
|
405
|
+
* `react-native-play-install-referrer` automatically when this is omitted. */
|
|
385
406
|
installReferrer?: string;
|
|
386
407
|
iosBundleId?: string;
|
|
387
408
|
androidPackageName?: string;
|
|
388
409
|
}
|
|
410
|
+
interface LinkStats {
|
|
411
|
+
totalClicks: number;
|
|
412
|
+
uniqueClicks: number;
|
|
413
|
+
deferredMatches: number;
|
|
414
|
+
byDevice: Array<{
|
|
415
|
+
deviceType: string | null;
|
|
416
|
+
count: number;
|
|
417
|
+
}>;
|
|
418
|
+
byCountry: Array<{
|
|
419
|
+
country: string | null;
|
|
420
|
+
count: number;
|
|
421
|
+
}>;
|
|
422
|
+
byOs: Array<{
|
|
423
|
+
os: string | null;
|
|
424
|
+
count: number;
|
|
425
|
+
}>;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Extract a Sendora shortcode from a URL.
|
|
429
|
+
*
|
|
430
|
+
* - When `linkDomain` is supplied, only matches URLs whose host equals it
|
|
431
|
+
* (or is a subdomain) — prevents firing `handleUniversalLink` on unrelated
|
|
432
|
+
* inbound URLs from other deep-link providers.
|
|
433
|
+
* - When omitted, host-agnostic (matches any tail segment that looks like a
|
|
434
|
+
* shortcode — back-compat with 0.15.x).
|
|
435
|
+
* - Accepts both `https://<host>/<shortcode>` and `https://<host>/link/<shortcode>`.
|
|
436
|
+
*/
|
|
437
|
+
declare function extractShortcodeFromUrl(url: string, linkDomain?: string): string | null;
|
|
389
438
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
439
|
+
* Canonical device-fingerprint recipe. Identical byte-for-byte across the
|
|
440
|
+
* RN / iOS / Android SDKs so a click logged from one platform and an
|
|
441
|
+
* install completing on another won't accidentally match.
|
|
442
|
+
*
|
|
443
|
+
* Input: `${platform}|${screenW}x${screenH}|${timezone}|${locale}`
|
|
444
|
+
* Output: lowercase hex SHA-256.
|
|
395
445
|
*/
|
|
396
|
-
declare function
|
|
446
|
+
declare function computeDeviceFingerprint(): Promise<string>;
|
|
447
|
+
/** Probe optional `react-native-play-install-referrer` peer. Returns the
|
|
448
|
+
* raw referrer string (which Play passes Sendora's `?sd_sc=<shortcode>`
|
|
449
|
+
* inside) when the peer is installed, or `null` when not. Safe to call
|
|
450
|
+
* on iOS — returns null. */
|
|
451
|
+
declare function getPlayInstallReferrer(): Promise<string | null>;
|
|
452
|
+
interface AttachLinkingApiOptions {
|
|
453
|
+
/** Called with URLs that aren't Sendora-shaped so the host app can hand
|
|
454
|
+
* them to its existing router (other deep-link providers, OAuth callbacks,
|
|
455
|
+
* etc). */
|
|
456
|
+
onLegacyUrl?: (url: string) => void;
|
|
457
|
+
}
|
|
397
458
|
interface LinksDeps {
|
|
398
459
|
apiUrl: string;
|
|
399
460
|
publicKey: string;
|
|
400
461
|
debug: boolean;
|
|
401
462
|
iosBundleId?: string;
|
|
402
463
|
androidPackageName?: string;
|
|
464
|
+
/** Custom share domain (e.g. `pulse.link`). Used by `attachLinkingApi` to
|
|
465
|
+
* filter Sendora vs legacy URLs. When unset, falls back to the default
|
|
466
|
+
* Sendora host pattern (host-agnostic — any URL whose tail looks like a
|
|
467
|
+
* shortcode is treated as Sendora). */
|
|
468
|
+
linkDomain?: string;
|
|
403
469
|
}
|
|
404
470
|
declare class Links {
|
|
405
471
|
private deps;
|
|
406
472
|
private listeners;
|
|
473
|
+
private prewarmCache;
|
|
474
|
+
private linkingSub;
|
|
407
475
|
constructor(deps: LinksDeps);
|
|
408
|
-
/** Register a callback for warm + deferred deep-link opens. Returns
|
|
409
|
-
|
|
476
|
+
/** Register a callback for warm + deferred deep-link opens. Returns
|
|
477
|
+
* unsubscribe fn. Generic `T` lets callers type `linkData` per-app. */
|
|
478
|
+
onLinkOpened<T extends LinkData = LinkData>(handler: LinkOpenedHandler<T>): () => void;
|
|
410
479
|
private emit;
|
|
411
|
-
/**
|
|
412
|
-
|
|
480
|
+
/** Stable cache key. Customer can override per call with `opts.key`. */
|
|
481
|
+
private cacheKey;
|
|
482
|
+
private evictExpired;
|
|
483
|
+
/**
|
|
484
|
+
* Background-mint a link + cache the promise so a subsequent `create()`
|
|
485
|
+
* with the same input (or matching `key`) returns instantly.
|
|
486
|
+
*
|
|
487
|
+
* Fire-and-forget. Errors are swallowed; the matching `create()` call
|
|
488
|
+
* will surface them on demand instead.
|
|
489
|
+
*/
|
|
490
|
+
prewarm<T extends LinkData = LinkData>(input: LinkCreateInput<T>, opts?: {
|
|
491
|
+
key?: string;
|
|
492
|
+
}): void;
|
|
493
|
+
/** Mint a new short link. Uses prewarm cache when the input matches. */
|
|
494
|
+
create<T extends LinkData = LinkData>(input: LinkCreateInput<T>, opts?: {
|
|
495
|
+
key?: string;
|
|
496
|
+
}): Promise<LinkCreateResult<T>>;
|
|
497
|
+
private doCreate;
|
|
413
498
|
/**
|
|
414
499
|
* Warm-path: app received a universal/app-link delivery. Resolves the
|
|
415
500
|
* shortcode against the backend and fires `onLinkOpened`. No-op (and
|
|
@@ -417,14 +502,41 @@ declare class Links {
|
|
|
417
502
|
*/
|
|
418
503
|
handleUniversalLink(url: string): Promise<boolean>;
|
|
419
504
|
/**
|
|
420
|
-
* Cold-launch deferred deep link match.
|
|
421
|
-
*
|
|
422
|
-
*
|
|
505
|
+
* Cold-launch deferred deep link match. When neither `fingerprintHash` nor
|
|
506
|
+
* `installReferrer` is supplied, the SDK auto-discovers:
|
|
507
|
+
* - Android: probes `react-native-play-install-referrer` (preferred — 100%
|
|
508
|
+
* accurate when present).
|
|
509
|
+
* - Otherwise: computes the canonical device fingerprint hash.
|
|
510
|
+
*
|
|
511
|
+
* Fires `onLinkOpened` with `isDeferred: true` on success.
|
|
512
|
+
*/
|
|
513
|
+
matchDeferred(input?: LinkMatchInput): Promise<LinkOpenedEvent<LinkData> | null>;
|
|
514
|
+
/** Soft-delete a link. Idempotent — repeated calls succeed. */
|
|
515
|
+
revoke(shortcode: string): Promise<{
|
|
516
|
+
shortcode: string;
|
|
517
|
+
revoked: true;
|
|
518
|
+
}>;
|
|
519
|
+
/** Click totals + breakdowns for a link the SDK owns. */
|
|
520
|
+
getStats(shortcode: string): Promise<LinkStats>;
|
|
521
|
+
/**
|
|
522
|
+
* One-call replacement for the standard cold + warm URL boilerplate:
|
|
523
|
+
*
|
|
524
|
+
* const detach = await sendora.links.attachLinkingApi({
|
|
525
|
+
* onLegacyUrl: (url) => myRouter.handle(url),
|
|
526
|
+
* });
|
|
527
|
+
* // …later in unmount:
|
|
528
|
+
* detach();
|
|
423
529
|
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
530
|
+
* Internally:
|
|
531
|
+
* - Reads `Linking.getInitialURL()` once.
|
|
532
|
+
* - Subscribes to `Linking.addEventListener('url', …)`.
|
|
533
|
+
* - For each URL: checks `extractShortcodeFromUrl(url, linkDomain)`.
|
|
534
|
+
* Sendora-shaped → calls `handleUniversalLink`.
|
|
535
|
+
* Otherwise → forwards to `onLegacyUrl` (if supplied).
|
|
536
|
+
*
|
|
537
|
+
* Idempotent: re-attaching while a subscription is active replaces it.
|
|
426
538
|
*/
|
|
427
|
-
|
|
539
|
+
attachLinkingApi(opts?: AttachLinkingApiOptions): Promise<() => void>;
|
|
428
540
|
}
|
|
429
541
|
|
|
430
542
|
interface SendoraConfig {
|
|
@@ -492,6 +604,14 @@ interface SendoraConfig {
|
|
|
492
604
|
* `iosBundleId` above.
|
|
493
605
|
*/
|
|
494
606
|
androidPackageName?: string;
|
|
607
|
+
/**
|
|
608
|
+
* Optional share-link host (e.g. `pulse.link`). When set, the deep
|
|
609
|
+
* links module's URL extractor only treats Sendora-routed URLs whose
|
|
610
|
+
* host matches this domain (or a subdomain). Leave unset on apps that
|
|
611
|
+
* use the default Sendora-hosted share domain (`go.sendoracloud.com`).
|
|
612
|
+
* Added 0.16.0.
|
|
613
|
+
*/
|
|
614
|
+
linkDomain?: string;
|
|
495
615
|
}
|
|
496
616
|
type TraitValue = string | number | boolean | null | undefined;
|
|
497
617
|
interface IdentifyTraits {
|
|
@@ -612,4 +732,4 @@ declare class SendoraSDK {
|
|
|
612
732
|
}
|
|
613
733
|
declare const SendoraCloud: SendoraSDK;
|
|
614
734
|
|
|
615
|
-
export { Auth, AuthError, type AuthTokens, type AuthUser, EmailAlreadyTakenError, type IdentifyTraits, type LinkCreateInput, type LinkCreateResult, type LinkMatchInput, type LinkOpenedEvent, type LinkOpenedHandler, Links, type PushTokenReceipt, type PushTokenRegistration, SendoraCloud, type SendoraConfig, type TrackProperties, SendoraCloud as default, extractShortcodeFromUrl };
|
|
735
|
+
export { type AttachLinkingApiOptions, Auth, AuthError, type AuthTokens, type AuthUser, EmailAlreadyTakenError, type IdentifyTraits, type LinkCreateInput, type LinkCreateResult, type LinkData, LinkError, type LinkErrorCode, type LinkMatchInput, type LinkOpenedEvent, type LinkOpenedHandler, type LinkStats, Links, type PushTokenReceipt, type PushTokenRegistration, SendoraCloud, type SendoraConfig, type TrackProperties, computeDeviceFingerprint, SendoraCloud as default, extractShortcodeFromUrl, getPlayInstallReferrer };
|
package/dist/index.d.ts
CHANGED
|
@@ -305,35 +305,62 @@ declare class Auth {
|
|
|
305
305
|
}
|
|
306
306
|
|
|
307
307
|
/**
|
|
308
|
-
* Sendora Deep Links surface for React Native.
|
|
308
|
+
* Sendora Deep Links surface for React Native (s58.50 — Pulse DX rewrite).
|
|
309
309
|
*
|
|
310
|
-
*
|
|
311
|
-
* 1. `create(...)`
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
*
|
|
310
|
+
* Surface:
|
|
311
|
+
* 1. `create<T>(...)` — mint a Sendora short link from inside the app.
|
|
312
|
+
* 2. `prewarm<T>(...)` — background-mint + cache so share row taps
|
|
313
|
+
* skip the HTTPS round-trip ActivityIndicator.
|
|
314
|
+
* 3. `handleUniversalLink(url)` — resolve a delivered universal / app link
|
|
315
|
+
* and fire `onLinkOpened`.
|
|
316
|
+
* 4. `matchDeferred({ ... })` — cold-launch deferred match.
|
|
317
|
+
* 5. `attachLinkingApi({ ... })` — one-call replacement for the
|
|
318
|
+
* `Linking.getInitialURL` + `addEventListener`
|
|
319
|
+
* + extract-pre-check + fallthrough boilerplate.
|
|
320
|
+
* 6. `computeDeviceFingerprint()` — canonical recipe (matches iOS + Android),
|
|
321
|
+
* no client-side boilerplate required.
|
|
322
|
+
* 7. `revoke(shortcode)` — soft-delete (private-content unsend).
|
|
323
|
+
* 8. `getStats(shortcode)` — totals + breakdowns; no dashboard scraping.
|
|
324
|
+
* 9. `onLinkOpened<T>(cb)` — generic-typed handler.
|
|
325
|
+
* 10. `LinkError` — typed errors with codes
|
|
326
|
+
* (`instanceof` + `err.code` rather than
|
|
327
|
+
* `err.message.includes(...)`).
|
|
321
328
|
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
329
|
+
* Bundle-id gate: SDK auto-supplies `iosBundleId` / `androidPackageName`
|
|
330
|
+
* from `init()` config — host app never has to thread them through per call.
|
|
331
|
+
* Sendora-side `apps` table is the source of truth — a leaked public key
|
|
332
|
+
* + wrong bundle = 422 from the backend mapped to
|
|
333
|
+
* `LinkError(code: "BUNDLE_MISMATCH")` here.
|
|
325
334
|
*/
|
|
326
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Error code taxonomy. Mirrors `apps/backend/src/modules/links/*` outcomes.
|
|
337
|
+
* Callers branch on `err.code` (string compare) or `err instanceof LinkError`.
|
|
338
|
+
*/
|
|
339
|
+
type LinkErrorCode = "BUNDLE_MISMATCH" | "DATA_TOO_LARGE" | "EXPIRED" | "NETWORK" | "RATE_LIMITED" | "NOT_FOUND" | "UNAUTHORIZED" | "INVALID_INPUT" | "PLAN_LIMIT" | "FALLBACK_REQUIRED" | "SERVER" | "UNKNOWN";
|
|
340
|
+
declare class LinkError extends Error {
|
|
341
|
+
readonly code: LinkErrorCode;
|
|
342
|
+
readonly statusCode: number;
|
|
343
|
+
readonly details?: unknown;
|
|
344
|
+
constructor(code: LinkErrorCode, message: string, statusCode?: number, details?: unknown);
|
|
345
|
+
}
|
|
346
|
+
/** Default linkData shape — opaque record. Override with a discriminated
|
|
347
|
+
* union or product-specific interface in the host app for full type-safety. */
|
|
348
|
+
type LinkData = Record<string, unknown>;
|
|
349
|
+
interface LinkCreateInput<T extends LinkData = LinkData> {
|
|
327
350
|
/** Customer-facing title. Shown in dashboard list views. */
|
|
328
351
|
title: string;
|
|
329
352
|
/** In-app deep-link path the host app routes to (e.g. `/articles/123`). */
|
|
330
353
|
iosDeepLinkPath?: string;
|
|
331
354
|
/** In-app deep-link path the host app routes to (e.g. `/articles/123`). */
|
|
332
355
|
androidDeepLinkPath?: string;
|
|
333
|
-
/**
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
356
|
+
/**
|
|
357
|
+
* Web fallback URL. **Optional as of 0.16.0** — backend defaults from
|
|
358
|
+
* the project's apps registry (web origin > iOS App Store URL > Android
|
|
359
|
+
* Play Store URL). Pass explicitly for per-link override.
|
|
360
|
+
*/
|
|
361
|
+
fallbackUrl?: string;
|
|
362
|
+
/** Custom typed JSON delivered to onLinkOpened. Max 2KB serialized. */
|
|
363
|
+
linkData?: T;
|
|
337
364
|
/** Optional OG preview overrides for richer share cards. */
|
|
338
365
|
ogTitle?: string;
|
|
339
366
|
ogDescription?: string;
|
|
@@ -344,72 +371,130 @@ interface LinkCreateInput {
|
|
|
344
371
|
medium?: string;
|
|
345
372
|
channel?: string;
|
|
346
373
|
tags?: string[];
|
|
347
|
-
/** ISO timestamp when the link stops resolving. */
|
|
374
|
+
/** ISO timestamp when the link stops resolving. Must be future + within 5y. */
|
|
348
375
|
expiresAt?: string;
|
|
349
|
-
/** Bundle ID / package name for the bundle-id gate. */
|
|
376
|
+
/** Bundle ID / package name for the bundle-id gate. Defaults from init. */
|
|
350
377
|
iosBundleId?: string;
|
|
351
378
|
androidPackageName?: string;
|
|
352
379
|
}
|
|
353
|
-
interface LinkCreateResult {
|
|
380
|
+
interface LinkCreateResult<T extends LinkData = LinkData> {
|
|
354
381
|
id: string;
|
|
355
382
|
shortcode: string;
|
|
356
|
-
/** Fully qualified share URL —
|
|
383
|
+
/** Fully qualified share URL — pass to RN `Share.share({ message: ... })`. */
|
|
357
384
|
url: string;
|
|
358
385
|
iosDeepLinkPath: string | null;
|
|
359
386
|
androidDeepLinkPath: string | null;
|
|
360
387
|
fallbackUrl: string;
|
|
361
|
-
linkData:
|
|
388
|
+
linkData: T;
|
|
362
389
|
expiresAt: string | null;
|
|
363
390
|
}
|
|
364
|
-
interface LinkOpenedEvent {
|
|
391
|
+
interface LinkOpenedEvent<T extends LinkData = LinkData> {
|
|
365
392
|
shortcode: string;
|
|
366
|
-
/** Custom JSON the link was created with.
|
|
367
|
-
linkData:
|
|
368
|
-
/** Server's iOS deep-link path. Useful when host app's URL parser needs a hint. */
|
|
393
|
+
/** Custom typed JSON the link was created with. */
|
|
394
|
+
linkData: T;
|
|
369
395
|
iosDeepLinkPath: string | null;
|
|
370
|
-
/** Server's Android deep-link path. */
|
|
371
396
|
androidDeepLinkPath: string | null;
|
|
372
|
-
/**
|
|
373
|
-
* `true` when this event came from the deferred deep link path —
|
|
374
|
-
* i.e. user installed the app from the App / Play Store after
|
|
375
|
-
* clicking the link, and the SDK matched their fingerprint /
|
|
376
|
-
* install-referrer to the prior click.
|
|
377
|
-
*/
|
|
397
|
+
/** `true` when this event came from the deferred deep link path. */
|
|
378
398
|
isDeferred: boolean;
|
|
379
399
|
}
|
|
380
|
-
type LinkOpenedHandler = (event: LinkOpenedEvent) => void;
|
|
400
|
+
type LinkOpenedHandler<T extends LinkData = LinkData> = (event: LinkOpenedEvent<T>) => void;
|
|
381
401
|
interface LinkMatchInput {
|
|
382
|
-
/** Hex-encoded SHA-256
|
|
402
|
+
/** Hex-encoded SHA-256 — pass output of `computeDeviceFingerprint()`. */
|
|
383
403
|
fingerprintHash?: string;
|
|
384
|
-
/** Raw Play Install Referrer
|
|
404
|
+
/** Raw Play Install Referrer. Sendora probes
|
|
405
|
+
* `react-native-play-install-referrer` automatically when this is omitted. */
|
|
385
406
|
installReferrer?: string;
|
|
386
407
|
iosBundleId?: string;
|
|
387
408
|
androidPackageName?: string;
|
|
388
409
|
}
|
|
410
|
+
interface LinkStats {
|
|
411
|
+
totalClicks: number;
|
|
412
|
+
uniqueClicks: number;
|
|
413
|
+
deferredMatches: number;
|
|
414
|
+
byDevice: Array<{
|
|
415
|
+
deviceType: string | null;
|
|
416
|
+
count: number;
|
|
417
|
+
}>;
|
|
418
|
+
byCountry: Array<{
|
|
419
|
+
country: string | null;
|
|
420
|
+
count: number;
|
|
421
|
+
}>;
|
|
422
|
+
byOs: Array<{
|
|
423
|
+
os: string | null;
|
|
424
|
+
count: number;
|
|
425
|
+
}>;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Extract a Sendora shortcode from a URL.
|
|
429
|
+
*
|
|
430
|
+
* - When `linkDomain` is supplied, only matches URLs whose host equals it
|
|
431
|
+
* (or is a subdomain) — prevents firing `handleUniversalLink` on unrelated
|
|
432
|
+
* inbound URLs from other deep-link providers.
|
|
433
|
+
* - When omitted, host-agnostic (matches any tail segment that looks like a
|
|
434
|
+
* shortcode — back-compat with 0.15.x).
|
|
435
|
+
* - Accepts both `https://<host>/<shortcode>` and `https://<host>/link/<shortcode>`.
|
|
436
|
+
*/
|
|
437
|
+
declare function extractShortcodeFromUrl(url: string, linkDomain?: string): string | null;
|
|
389
438
|
/**
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
394
|
-
*
|
|
439
|
+
* Canonical device-fingerprint recipe. Identical byte-for-byte across the
|
|
440
|
+
* RN / iOS / Android SDKs so a click logged from one platform and an
|
|
441
|
+
* install completing on another won't accidentally match.
|
|
442
|
+
*
|
|
443
|
+
* Input: `${platform}|${screenW}x${screenH}|${timezone}|${locale}`
|
|
444
|
+
* Output: lowercase hex SHA-256.
|
|
395
445
|
*/
|
|
396
|
-
declare function
|
|
446
|
+
declare function computeDeviceFingerprint(): Promise<string>;
|
|
447
|
+
/** Probe optional `react-native-play-install-referrer` peer. Returns the
|
|
448
|
+
* raw referrer string (which Play passes Sendora's `?sd_sc=<shortcode>`
|
|
449
|
+
* inside) when the peer is installed, or `null` when not. Safe to call
|
|
450
|
+
* on iOS — returns null. */
|
|
451
|
+
declare function getPlayInstallReferrer(): Promise<string | null>;
|
|
452
|
+
interface AttachLinkingApiOptions {
|
|
453
|
+
/** Called with URLs that aren't Sendora-shaped so the host app can hand
|
|
454
|
+
* them to its existing router (other deep-link providers, OAuth callbacks,
|
|
455
|
+
* etc). */
|
|
456
|
+
onLegacyUrl?: (url: string) => void;
|
|
457
|
+
}
|
|
397
458
|
interface LinksDeps {
|
|
398
459
|
apiUrl: string;
|
|
399
460
|
publicKey: string;
|
|
400
461
|
debug: boolean;
|
|
401
462
|
iosBundleId?: string;
|
|
402
463
|
androidPackageName?: string;
|
|
464
|
+
/** Custom share domain (e.g. `pulse.link`). Used by `attachLinkingApi` to
|
|
465
|
+
* filter Sendora vs legacy URLs. When unset, falls back to the default
|
|
466
|
+
* Sendora host pattern (host-agnostic — any URL whose tail looks like a
|
|
467
|
+
* shortcode is treated as Sendora). */
|
|
468
|
+
linkDomain?: string;
|
|
403
469
|
}
|
|
404
470
|
declare class Links {
|
|
405
471
|
private deps;
|
|
406
472
|
private listeners;
|
|
473
|
+
private prewarmCache;
|
|
474
|
+
private linkingSub;
|
|
407
475
|
constructor(deps: LinksDeps);
|
|
408
|
-
/** Register a callback for warm + deferred deep-link opens. Returns
|
|
409
|
-
|
|
476
|
+
/** Register a callback for warm + deferred deep-link opens. Returns
|
|
477
|
+
* unsubscribe fn. Generic `T` lets callers type `linkData` per-app. */
|
|
478
|
+
onLinkOpened<T extends LinkData = LinkData>(handler: LinkOpenedHandler<T>): () => void;
|
|
410
479
|
private emit;
|
|
411
|
-
/**
|
|
412
|
-
|
|
480
|
+
/** Stable cache key. Customer can override per call with `opts.key`. */
|
|
481
|
+
private cacheKey;
|
|
482
|
+
private evictExpired;
|
|
483
|
+
/**
|
|
484
|
+
* Background-mint a link + cache the promise so a subsequent `create()`
|
|
485
|
+
* with the same input (or matching `key`) returns instantly.
|
|
486
|
+
*
|
|
487
|
+
* Fire-and-forget. Errors are swallowed; the matching `create()` call
|
|
488
|
+
* will surface them on demand instead.
|
|
489
|
+
*/
|
|
490
|
+
prewarm<T extends LinkData = LinkData>(input: LinkCreateInput<T>, opts?: {
|
|
491
|
+
key?: string;
|
|
492
|
+
}): void;
|
|
493
|
+
/** Mint a new short link. Uses prewarm cache when the input matches. */
|
|
494
|
+
create<T extends LinkData = LinkData>(input: LinkCreateInput<T>, opts?: {
|
|
495
|
+
key?: string;
|
|
496
|
+
}): Promise<LinkCreateResult<T>>;
|
|
497
|
+
private doCreate;
|
|
413
498
|
/**
|
|
414
499
|
* Warm-path: app received a universal/app-link delivery. Resolves the
|
|
415
500
|
* shortcode against the backend and fires `onLinkOpened`. No-op (and
|
|
@@ -417,14 +502,41 @@ declare class Links {
|
|
|
417
502
|
*/
|
|
418
503
|
handleUniversalLink(url: string): Promise<boolean>;
|
|
419
504
|
/**
|
|
420
|
-
* Cold-launch deferred deep link match.
|
|
421
|
-
*
|
|
422
|
-
*
|
|
505
|
+
* Cold-launch deferred deep link match. When neither `fingerprintHash` nor
|
|
506
|
+
* `installReferrer` is supplied, the SDK auto-discovers:
|
|
507
|
+
* - Android: probes `react-native-play-install-referrer` (preferred — 100%
|
|
508
|
+
* accurate when present).
|
|
509
|
+
* - Otherwise: computes the canonical device fingerprint hash.
|
|
510
|
+
*
|
|
511
|
+
* Fires `onLinkOpened` with `isDeferred: true` on success.
|
|
512
|
+
*/
|
|
513
|
+
matchDeferred(input?: LinkMatchInput): Promise<LinkOpenedEvent<LinkData> | null>;
|
|
514
|
+
/** Soft-delete a link. Idempotent — repeated calls succeed. */
|
|
515
|
+
revoke(shortcode: string): Promise<{
|
|
516
|
+
shortcode: string;
|
|
517
|
+
revoked: true;
|
|
518
|
+
}>;
|
|
519
|
+
/** Click totals + breakdowns for a link the SDK owns. */
|
|
520
|
+
getStats(shortcode: string): Promise<LinkStats>;
|
|
521
|
+
/**
|
|
522
|
+
* One-call replacement for the standard cold + warm URL boilerplate:
|
|
523
|
+
*
|
|
524
|
+
* const detach = await sendora.links.attachLinkingApi({
|
|
525
|
+
* onLegacyUrl: (url) => myRouter.handle(url),
|
|
526
|
+
* });
|
|
527
|
+
* // …later in unmount:
|
|
528
|
+
* detach();
|
|
423
529
|
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
530
|
+
* Internally:
|
|
531
|
+
* - Reads `Linking.getInitialURL()` once.
|
|
532
|
+
* - Subscribes to `Linking.addEventListener('url', …)`.
|
|
533
|
+
* - For each URL: checks `extractShortcodeFromUrl(url, linkDomain)`.
|
|
534
|
+
* Sendora-shaped → calls `handleUniversalLink`.
|
|
535
|
+
* Otherwise → forwards to `onLegacyUrl` (if supplied).
|
|
536
|
+
*
|
|
537
|
+
* Idempotent: re-attaching while a subscription is active replaces it.
|
|
426
538
|
*/
|
|
427
|
-
|
|
539
|
+
attachLinkingApi(opts?: AttachLinkingApiOptions): Promise<() => void>;
|
|
428
540
|
}
|
|
429
541
|
|
|
430
542
|
interface SendoraConfig {
|
|
@@ -492,6 +604,14 @@ interface SendoraConfig {
|
|
|
492
604
|
* `iosBundleId` above.
|
|
493
605
|
*/
|
|
494
606
|
androidPackageName?: string;
|
|
607
|
+
/**
|
|
608
|
+
* Optional share-link host (e.g. `pulse.link`). When set, the deep
|
|
609
|
+
* links module's URL extractor only treats Sendora-routed URLs whose
|
|
610
|
+
* host matches this domain (or a subdomain). Leave unset on apps that
|
|
611
|
+
* use the default Sendora-hosted share domain (`go.sendoracloud.com`).
|
|
612
|
+
* Added 0.16.0.
|
|
613
|
+
*/
|
|
614
|
+
linkDomain?: string;
|
|
495
615
|
}
|
|
496
616
|
type TraitValue = string | number | boolean | null | undefined;
|
|
497
617
|
interface IdentifyTraits {
|
|
@@ -612,4 +732,4 @@ declare class SendoraSDK {
|
|
|
612
732
|
}
|
|
613
733
|
declare const SendoraCloud: SendoraSDK;
|
|
614
734
|
|
|
615
|
-
export { Auth, AuthError, type AuthTokens, type AuthUser, EmailAlreadyTakenError, type IdentifyTraits, type LinkCreateInput, type LinkCreateResult, type LinkMatchInput, type LinkOpenedEvent, type LinkOpenedHandler, Links, type PushTokenReceipt, type PushTokenRegistration, SendoraCloud, type SendoraConfig, type TrackProperties, SendoraCloud as default, extractShortcodeFromUrl };
|
|
735
|
+
export { type AttachLinkingApiOptions, Auth, AuthError, type AuthTokens, type AuthUser, EmailAlreadyTakenError, type IdentifyTraits, type LinkCreateInput, type LinkCreateResult, type LinkData, LinkError, type LinkErrorCode, type LinkMatchInput, type LinkOpenedEvent, type LinkOpenedHandler, type LinkStats, Links, type PushTokenReceipt, type PushTokenRegistration, SendoraCloud, type SendoraConfig, type TrackProperties, computeDeviceFingerprint, SendoraCloud as default, extractShortcodeFromUrl, getPlayInstallReferrer };
|