sitepong 0.1.12 → 0.1.13

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
@@ -602,7 +602,7 @@ Native APNs (iOS) and FCM (Android) push notifications, plus iOS Live Activity u
602
602
 
603
603
  #### Token lifecycle
604
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.
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 and binds tokens to authenticated users.
606
606
 
607
607
  ```
608
608
  [App launches]
@@ -611,15 +611,19 @@ There are two halves to push: the **client SDK** registers device tokens with th
611
611
 
612
612
  [registerDeviceToken(token, opts)] ← SDK POSTs to /api/push/tokens
613
613
  ↓ with X-API-Key: sp_live_xxx
614
- [Token stored in SitePong]
614
+ [Token stored anonymously in SitePong]
615
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
616
+ [User logs in with your backend]
617
+
618
+ [Your backend → POST /api/push/tokens/associate] ← sp_push_xxx + { token, user_id }
619
+
620
+ [Your backend → POST /api/push/send] ← sp_push_xxx + target user_ids
619
621
 
620
622
  [SitePong → APNs / FCM → Device]
621
623
  ```
622
624
 
625
+ > **Why two steps?** Publishable SDK keys (`sp_live_xxx`) are embedded in your app binary and can be extracted by anyone who decompiles the APK/IPA. If the client controlled `user_id`, a leaked SDK key would let an attacker register their own device under your users' IDs and silently receive their push notifications (password resets, OTP codes, order details). User attribution therefore only happens server-to-server with the privileged `sp_push_xxx` key — which stays on your backend.
626
+
623
627
  #### 1. Install permission and get the native token
624
628
 
625
629
  ```bash
@@ -649,26 +653,25 @@ import { Platform } from 'react-native';
649
653
  registerDeviceToken(token, {
650
654
  platform: Platform.OS as 'ios' | 'android',
651
655
  environment: __DEV__ ? 'sandbox' : 'production',
652
- userId: 'user-123', // optional — can be set later via identify()
653
656
  });
654
657
  ```
655
658
 
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.
659
+ 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`, `platform`, app version, device model, and OS version — no `user_id`. The server upserts by `(project_id, token)` so re-registration is idempotent.
657
660
 
658
- #### 3. User identification (automatic)
661
+ #### 3. Associate the token with a user (server-side)
659
662
 
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';
663
+ Once your own backend has authenticated the user, have it POST to SitePong's associate endpoint with your `sp_push_xxx` key. Your app should forward the push token to your backend as part of the login flow.
664
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"
665
+ ```bash
666
+ curl -X POST https://api.sitepong.com/api/push/tokens/associate \
667
+ -H "Authorization: Bearer sp_push_xxxxxxxxxxxxxxxx" \
668
+ -H "Content-Type: application/json" \
669
+ -d '{ "token": "<expo_or_native_token>", "user_id": "user-123" }'
669
670
  ```
670
671
 
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
+ On sign-out, POST the same endpoint with `"user_id": null` to clear the association. Because this call is server-authenticated, an attacker who has scraped the SDK key out of your app cannot impersonate a user.
673
+
674
+ > The older pattern of passing `userId` to `registerDeviceToken()` or `pushUserId` to `SitePongRNProvider` still type-checks for backwards compatibility, but the values are silently ignored on both the client and the server. Migrate to the associate endpoint.
672
675
 
673
676
  #### 4. Send notifications from your backend
674
677
 
@@ -794,6 +797,7 @@ When APNs or FCM reports a token as invalid (app uninstalled, notifications disa
794
797
  | POST | `/api/push/live-activity-tokens` | `X-API-Key: sp_live_*` | `registerLiveActivityToken()` |
795
798
  | POST | `/api/push/push-to-start-tokens` | `X-API-Key: sp_live_*` | `registerPushToStartToken()` |
796
799
  | DELETE | `/api/push/live-activity-tokens` | `X-API-Key: sp_live_*` | `endLiveActivity()` |
800
+ | POST | `/api/push/tokens/associate` | `Authorization: Bearer sp_push_*` | (server-side only) |
797
801
  | POST | `/api/push/send` | `Authorization: Bearer sp_push_*` | (server-side only) |
798
802
  | POST | `/api/push/live-activity` | `Authorization: Bearer sp_push_*` | (server-side only) |
799
803
 
@@ -3163,22 +3163,7 @@ var RNPerformanceManager = class {
3163
3163
  };
3164
3164
 
3165
3165
  // src/react-native/push.ts
3166
- var identifyHookInstalled = false;
3167
- function ensureIdentifyHookInstalled() {
3168
- if (identifyHookInstalled) return;
3169
- identifyHookInstalled = true;
3170
- try {
3171
- const c = sitepong;
3172
- if (typeof c.registerIdentifyHook === "function") {
3173
- c.registerIdentifyHook(reRegisterTokensWithUserId);
3174
- }
3175
- } catch {
3176
- }
3177
- }
3178
3166
  var cachedDeviceContext = null;
3179
- var registeredPushToken = null;
3180
- var registeredLiveActivityTokens = /* @__PURE__ */ new Map();
3181
- var registeredPushToStartTokens = /* @__PURE__ */ new Map();
3182
3167
  function getEndpoint() {
3183
3168
  const cfg = sitepong.config;
3184
3169
  return cfg?.endpoint || "https://ingest.sitepong.com";
@@ -3205,14 +3190,6 @@ function getDeviceContext() {
3205
3190
  }
3206
3191
  return cachedDeviceContext;
3207
3192
  }
3208
- function getUserId() {
3209
- try {
3210
- const analyticsManager = sitepong.analyticsManager;
3211
- return analyticsManager?.getUserId?.() || void 0;
3212
- } catch {
3213
- return void 0;
3214
- }
3215
- }
3216
3193
  async function postToIngest(path, body) {
3217
3194
  const endpoint = getEndpoint();
3218
3195
  const apiKey = getApiKey();
@@ -3256,17 +3233,26 @@ async function deleteFromIngest(path, body) {
3256
3233
  console.warn("[SitePong] Push token deactivation failed:", err);
3257
3234
  }
3258
3235
  }
3236
+ function registerPushToken(token, options) {
3237
+ const device = getDeviceContext();
3238
+ postToIngest("/api/push/tokens", {
3239
+ expo_push_token: token,
3240
+ environment: options.environment,
3241
+ device_id: device.deviceId,
3242
+ platform: device.platform,
3243
+ app_version: device.appVersion,
3244
+ device_model: device.deviceModel,
3245
+ os_version: device.osVersion
3246
+ });
3247
+ }
3259
3248
  function registerDeviceToken(token, options) {
3260
- ensureIdentifyHookInstalled();
3261
3249
  const device = getDeviceContext();
3262
3250
  const tokenType = options.platform === "ios" ? "apns" : "fcm";
3263
- registeredPushToken = { token, environment: options.environment };
3264
3251
  postToIngest("/api/push/tokens", {
3265
3252
  native_device_token: token,
3266
3253
  token_type: tokenType,
3267
3254
  environment: options.environment,
3268
3255
  device_id: device.deviceId,
3269
- user_id: options.userId || void 0,
3270
3256
  platform: options.platform,
3271
3257
  app_version: device.appVersion,
3272
3258
  device_model: device.deviceModel,
@@ -3274,86 +3260,30 @@ function registerDeviceToken(token, options) {
3274
3260
  });
3275
3261
  }
3276
3262
  function registerLiveActivityToken(activityType, activityId, pushToken, options) {
3277
- ensureIdentifyHookInstalled();
3278
3263
  const device = getDeviceContext();
3279
- const userId = getUserId();
3280
- const key = `${activityType}:${activityId}`;
3281
- registeredLiveActivityTokens.set(key, {
3282
- activityType,
3283
- activityId,
3284
- token: pushToken,
3285
- environment: options.environment
3286
- });
3287
3264
  postToIngest("/api/push/live-activity-tokens", {
3288
3265
  activity_type: activityType,
3289
3266
  activity_id: activityId,
3290
3267
  push_token: pushToken,
3291
3268
  environment: options.environment,
3292
- device_id: device.deviceId,
3293
- user_id: userId
3269
+ device_id: device.deviceId
3294
3270
  });
3295
3271
  }
3296
3272
  function registerPushToStartToken(activityType, pushToStartToken, options) {
3297
- ensureIdentifyHookInstalled();
3298
3273
  const device = getDeviceContext();
3299
- const userId = getUserId();
3300
- registeredPushToStartTokens.set(activityType, {
3301
- activityType,
3302
- token: pushToStartToken,
3303
- environment: options.environment
3304
- });
3305
3274
  postToIngest("/api/push/push-to-start-tokens", {
3306
3275
  activity_type: activityType,
3307
3276
  push_to_start_token: pushToStartToken,
3308
3277
  environment: options.environment,
3309
- device_id: device.deviceId,
3310
- user_id: userId
3278
+ device_id: device.deviceId
3311
3279
  });
3312
3280
  }
3313
3281
  function endLiveActivity(activityType, activityId) {
3314
- const key = `${activityType}:${activityId}`;
3315
- registeredLiveActivityTokens.delete(key);
3316
3282
  deleteFromIngest("/api/push/live-activity-tokens", {
3317
3283
  activity_type: activityType,
3318
3284
  activity_id: activityId
3319
3285
  });
3320
3286
  }
3321
- function reRegisterTokensWithUserId(userId) {
3322
- if (registeredPushToken) {
3323
- const device = getDeviceContext();
3324
- postToIngest("/api/push/tokens", {
3325
- expo_push_token: registeredPushToken.token,
3326
- environment: registeredPushToken.environment,
3327
- device_id: device.deviceId,
3328
- user_id: userId,
3329
- platform: device.platform,
3330
- app_version: device.appVersion,
3331
- device_model: device.deviceModel,
3332
- os_version: device.osVersion
3333
- });
3334
- }
3335
- for (const entry of registeredLiveActivityTokens.values()) {
3336
- const device = getDeviceContext();
3337
- postToIngest("/api/push/live-activity-tokens", {
3338
- activity_type: entry.activityType,
3339
- activity_id: entry.activityId,
3340
- push_token: entry.token,
3341
- environment: entry.environment,
3342
- device_id: device.deviceId,
3343
- user_id: userId
3344
- });
3345
- }
3346
- for (const entry of registeredPushToStartTokens.values()) {
3347
- const device = getDeviceContext();
3348
- postToIngest("/api/push/push-to-start-tokens", {
3349
- activity_type: entry.activityType,
3350
- push_to_start_token: entry.token,
3351
- environment: entry.environment,
3352
- device_id: device.deviceId,
3353
- user_id: userId
3354
- });
3355
- }
3356
- }
3357
3287
  var SitePongRNContext = react.createContext({
3358
3288
  isInitialized: false,
3359
3289
  performanceManager: null
@@ -3426,7 +3356,7 @@ function SitePongRNProvider({
3426
3356
  teardowns.push(() => stopScreenRecording());
3427
3357
  }
3428
3358
  if (devicePushToken && devicePlatform) {
3429
- registerDeviceToken(devicePushToken, { platform: devicePlatform, environment: pushEnvironment, userId: pushUserId });
3359
+ registerDeviceToken(devicePushToken, { platform: devicePlatform, environment: pushEnvironment});
3430
3360
  }
3431
3361
  performanceManagerRef.current = new RNPerformanceManager();
3432
3362
  performanceManagerRef.current.trackColdStart();
@@ -3646,6 +3576,7 @@ exports.refreshFlags = refreshFlags;
3646
3576
  exports.registerDeviceToken = registerDeviceToken;
3647
3577
  exports.registerLiveActivityToken = registerLiveActivityToken;
3648
3578
  exports.registerPushToStartToken = registerPushToStartToken;
3579
+ exports.registerPushToken = registerPushToken;
3649
3580
  exports.resetAnalytics = resetAnalytics;
3650
3581
  exports.setAnonymousId = setAnonymousId;
3651
3582
  exports.setContext = setContext;