sitepong 0.1.9 → 0.1.10

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 CHANGED
@@ -596,6 +596,144 @@ Content inside `<SensitiveView>` is replaced with a black rectangle in recorded
596
596
  | `bufferDuration` | `10000` (10s) | `60000` (60s) | Rolling buffer size in ms |
597
597
  | `maxDuration` | `3600000` | `3600000` | Max recording duration in ms |
598
598
 
599
+ ### Push Notifications & Live Activities
600
+
601
+ Native APNs (iOS) and FCM (Android) push notifications, plus iOS Live Activity updates. SitePong sends directly to Apple/Google — no Expo Push service in the path.
602
+
603
+ #### Token lifecycle
604
+
605
+ There are two halves to push: the **client SDK** registers device tokens with the SitePong ingest server, and the **server-side Push API** sends notifications to those tokens.
606
+
607
+ ```
608
+ [App launches]
609
+
610
+ [Notifications.getDevicePushTokenAsync()] ← OS gives you a native token
611
+
612
+ [registerDeviceToken(token, opts)] ← SDK POSTs to /api/push/tokens
613
+ ↓ with X-API-Key: sp_live_xxx
614
+ [Token stored in SitePong]
615
+
616
+ [identify('user-123')] ← SDK auto re-registers token
617
+ ↓ with user_id (via lazy hook)
618
+ [Your backend → POST /api/push/send] ← server-only sp_push_xxx key
619
+
620
+ [SitePong → APNs / FCM → Device]
621
+ ```
622
+
623
+ #### 1. Install permission and get the native token
624
+
625
+ ```bash
626
+ npx expo install expo-notifications
627
+ ```
628
+
629
+ ```typescript
630
+ import * as Notifications from 'expo-notifications';
631
+ import * as Device from 'expo-device';
632
+ import { Platform } from 'react-native';
633
+
634
+ if (!Device.isDevice) return; // simulator can't receive push
635
+
636
+ const { status } = await Notifications.requestPermissionsAsync();
637
+ if (status !== 'granted') return;
638
+
639
+ // Returns the raw native token (APNs hex on iOS, FCM token on Android)
640
+ const { data: token } = await Notifications.getDevicePushTokenAsync();
641
+ ```
642
+
643
+ #### 2. Register the token with SitePong
644
+
645
+ ```typescript
646
+ import { registerDeviceToken } from '@sitepong/sdk/react-native';
647
+ import { Platform } from 'react-native';
648
+
649
+ registerDeviceToken(token, {
650
+ platform: Platform.OS as 'ios' | 'android',
651
+ environment: __DEV__ ? 'sandbox' : 'production',
652
+ userId: 'user-123', // optional — can be set later via identify()
653
+ });
654
+ ```
655
+
656
+ This makes a `POST /api/push/tokens` request to the ingest server with `X-API-Key: <your SDK key>`. The body includes the token, `token_type` (`apns`/`fcm`), `device_id`, `user_id`, `platform`, app version, device model, and OS version. The server upserts by `(token, environment)` so re-registration is idempotent.
657
+
658
+ #### 3. User identification (automatic)
659
+
660
+ When you call `identify(userId)`, the SDK automatically re-registers all cached push, Live Activity, and push-to-start tokens with the new `user_id`. **You do not need to call `registerDeviceToken()` again after login.**
661
+
662
+ ```typescript
663
+ import { identify } from '@sitepong/sdk/react-native';
664
+
665
+ // Token already registered anonymously at app launch
666
+ // Now user logs in:
667
+ identify('user-123');
668
+ // → SDK re-POSTs /api/push/tokens with user_id: "user-123"
669
+ ```
670
+
671
+ The hook is installed lazily the first time any `register*` function is called, so it works whether you use `SitePongRNProvider` or call `initRN()` directly.
672
+
673
+ #### 4. Send notifications from your backend
674
+
675
+ Generate a separate **Push API key** (prefix `sp_push_`) from the Notifications tab in the SitePong dashboard. This is **not** the SDK key — it's server-only and has permission to send pushes.
676
+
677
+ ```bash
678
+ curl -X POST https://api.sitepong.com/api/push/send \
679
+ -H "Authorization: Bearer sp_push_xxxxxxxxxxxxxxxx" \
680
+ -H "Content-Type: application/json" \
681
+ -d '{
682
+ "title": "Order Shipped",
683
+ "body": "Your order #1234 is on its way",
684
+ "data": { "orderId": "1234" },
685
+ "target": { "user_ids": ["user-123"] }
686
+ }'
687
+ ```
688
+
689
+ Targeting options (provide at least one):
690
+
691
+ - `user_ids: string[]` — sends to all active devices for these users
692
+ - `device_ids: string[]` — sends to specific SitePong device IDs
693
+ - `tokens: string[]` — sends directly to raw APNs/FCM tokens
694
+
695
+ #### Live Activities (iOS)
696
+
697
+ Register the per-instance push token from your native bridge:
698
+
699
+ ```typescript
700
+ import {
701
+ registerLiveActivityToken,
702
+ registerPushToStartToken,
703
+ endLiveActivity,
704
+ } from '@sitepong/sdk/react-native';
705
+
706
+ // When ActivityKit gives you a push token for a running activity
707
+ registerLiveActivityToken('DeliveryActivityAttributes', activity.id, pushToken, {
708
+ environment: 'production',
709
+ });
710
+
711
+ // Or register a push-to-start token (iOS 17.2+) so the activity can be launched remotely
712
+ registerPushToStartToken('DeliveryActivityAttributes', pushToStartToken, {
713
+ environment: 'production',
714
+ });
715
+
716
+ // When the activity ends, deactivate its token
717
+ endLiveActivity('DeliveryActivityAttributes', activity.id);
718
+ ```
719
+
720
+ #### Token invalidation
721
+
722
+ When APNs or FCM reports a token as invalid (app uninstalled, notifications disabled, token rotated), SitePong marks it inactive on the server side and stops sending. No client action needed. To force a refresh after an OS-level token change, call `registerDeviceToken()` again with the new token.
723
+
724
+ #### Endpoint reference
725
+
726
+ | Method | Path | Auth | SDK function |
727
+ |---|---|---|---|
728
+ | POST | `/api/push/tokens` | `X-API-Key: sp_live_*` | `registerDeviceToken()` |
729
+ | POST | `/api/push/live-activity-tokens` | `X-API-Key: sp_live_*` | `registerLiveActivityToken()` |
730
+ | POST | `/api/push/push-to-start-tokens` | `X-API-Key: sp_live_*` | `registerPushToStartToken()` |
731
+ | DELETE | `/api/push/live-activity-tokens` | `X-API-Key: sp_live_*` | `endLiveActivity()` |
732
+ | POST | `/api/push/send` | `Authorization: Bearer sp_push_*` | (server-side only) |
733
+ | POST | `/api/push/live-activity` | `Authorization: Bearer sp_push_*` | (server-side only) |
734
+
735
+ Default ingest endpoint: `https://ingest.sitepong.com`. Override via the `endpoint` option in `initRN()` or `SitePongRNProvider`.
736
+
599
737
  ### Device Intelligence (React Native)
600
738
 
601
739
  Persistent device identification that **survives app reinstalls**. Requires the `@sitepong/device-id` native module.
@@ -3160,7 +3160,20 @@ var RNPerformanceManager = class {
3160
3160
  };
3161
3161
 
3162
3162
  // src/react-native/push.ts
3163
+ var identifyHookInstalled = false;
3164
+ function ensureIdentifyHookInstalled() {
3165
+ if (identifyHookInstalled) return;
3166
+ identifyHookInstalled = true;
3167
+ try {
3168
+ const c = sitepong;
3169
+ if (typeof c.registerIdentifyHook === "function") {
3170
+ c.registerIdentifyHook(reRegisterTokensWithUserId);
3171
+ }
3172
+ } catch {
3173
+ }
3174
+ }
3163
3175
  var cachedDeviceContext = null;
3176
+ var registeredPushToken = null;
3164
3177
  var registeredLiveActivityTokens = /* @__PURE__ */ new Map();
3165
3178
  var registeredPushToStartTokens = /* @__PURE__ */ new Map();
3166
3179
  function getEndpoint() {
@@ -3241,9 +3254,10 @@ async function deleteFromIngest(path, body) {
3241
3254
  }
3242
3255
  }
3243
3256
  function registerDeviceToken(token, options) {
3257
+ ensureIdentifyHookInstalled();
3244
3258
  const device = getDeviceContext();
3245
3259
  const tokenType = options.platform === "ios" ? "apns" : "fcm";
3246
- ({ token, environment: options.environment });
3260
+ registeredPushToken = { token, environment: options.environment };
3247
3261
  postToIngest("/api/push/tokens", {
3248
3262
  native_device_token: token,
3249
3263
  token_type: tokenType,
@@ -3257,6 +3271,7 @@ function registerDeviceToken(token, options) {
3257
3271
  });
3258
3272
  }
3259
3273
  function registerLiveActivityToken(activityType, activityId, pushToken, options) {
3274
+ ensureIdentifyHookInstalled();
3260
3275
  const device = getDeviceContext();
3261
3276
  const userId = getUserId();
3262
3277
  const key = `${activityType}:${activityId}`;
@@ -3276,6 +3291,7 @@ function registerLiveActivityToken(activityType, activityId, pushToken, options)
3276
3291
  });
3277
3292
  }
3278
3293
  function registerPushToStartToken(activityType, pushToStartToken, options) {
3294
+ ensureIdentifyHookInstalled();
3279
3295
  const device = getDeviceContext();
3280
3296
  const userId = getUserId();
3281
3297
  registeredPushToStartTokens.set(activityType, {
@@ -3299,6 +3315,42 @@ function endLiveActivity(activityType, activityId) {
3299
3315
  activity_id: activityId
3300
3316
  });
3301
3317
  }
3318
+ function reRegisterTokensWithUserId(userId) {
3319
+ if (registeredPushToken) {
3320
+ const device = getDeviceContext();
3321
+ postToIngest("/api/push/tokens", {
3322
+ expo_push_token: registeredPushToken.token,
3323
+ environment: registeredPushToken.environment,
3324
+ device_id: device.deviceId,
3325
+ user_id: userId,
3326
+ platform: device.platform,
3327
+ app_version: device.appVersion,
3328
+ device_model: device.deviceModel,
3329
+ os_version: device.osVersion
3330
+ });
3331
+ }
3332
+ for (const entry of registeredLiveActivityTokens.values()) {
3333
+ const device = getDeviceContext();
3334
+ postToIngest("/api/push/live-activity-tokens", {
3335
+ activity_type: entry.activityType,
3336
+ activity_id: entry.activityId,
3337
+ push_token: entry.token,
3338
+ environment: entry.environment,
3339
+ device_id: device.deviceId,
3340
+ user_id: userId
3341
+ });
3342
+ }
3343
+ for (const entry of registeredPushToStartTokens.values()) {
3344
+ const device = getDeviceContext();
3345
+ postToIngest("/api/push/push-to-start-tokens", {
3346
+ activity_type: entry.activityType,
3347
+ push_to_start_token: entry.token,
3348
+ environment: entry.environment,
3349
+ device_id: device.deviceId,
3350
+ user_id: userId
3351
+ });
3352
+ }
3353
+ }
3302
3354
  var SitePongRNContext = react.createContext({
3303
3355
  isInitialized: false,
3304
3356
  performanceManager: null