@playcademy/sdk 0.4.1-beta.8 → 0.4.1-beta.9

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/dist/index.d.ts CHANGED
@@ -380,6 +380,9 @@ declare function parseOAuthState(state: string): {
380
380
 
381
381
  /** Permitted HTTP verbs */
382
382
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
383
+ interface RetryPolicy {
384
+ retryableMethods?: readonly Method[];
385
+ }
383
386
 
384
387
  /**
385
388
  * TimeBack Enums & Literal Types
@@ -946,11 +949,15 @@ declare abstract class PlaycademyBaseClient {
946
949
  body?: unknown;
947
950
  headers?: Record<string, string>;
948
951
  raw?: boolean;
952
+ retryPolicy?: RetryPolicy;
949
953
  }): Promise<T>;
950
954
  /**
951
955
  * Makes an authenticated HTTP request to the game's backend Worker.
952
956
  */
953
- protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, raw?: boolean): Promise<T>;
957
+ protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
958
+ raw?: boolean;
959
+ retryPolicy?: RetryPolicy;
960
+ }): Promise<T>;
954
961
  /**
955
962
  * Ensures a gameId is available, throwing an error if not.
956
963
  */
package/dist/index.js CHANGED
@@ -1037,7 +1037,7 @@ function createBackendNamespace(client) {
1037
1037
  return client["requestGameBackend"](normalizePath(path), method, body, headers);
1038
1038
  },
1039
1039
  async download(path, method = "GET", body, headers) {
1040
- return client["requestGameBackend"](normalizePath(path), method, body, headers, true);
1040
+ return client["requestGameBackend"](normalizePath(path), method, body, headers, { raw: true });
1041
1041
  },
1042
1042
  url(pathOrStrings, ...values) {
1043
1043
  if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
@@ -1391,6 +1391,7 @@ function isValidUUID(value) {
1391
1391
  // src/core/activity-tracker.ts
1392
1392
  var DEFAULT_HIDDEN_TIMEOUT_MS = 10 * 60 * 1000;
1393
1393
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
1394
+ var HEARTBEAT_RETRY_POLICY = { retryableMethods: ["POST"] };
1394
1395
  function getCurrentPausedTotal(activity, now = Date.now()) {
1395
1396
  if (activity.pauseStartTime === null) {
1396
1397
  return activity.pausedTime;
@@ -1417,9 +1418,11 @@ function markPersistedTiming(activity, timing) {
1417
1418
  activity.totalPersistedPausedMs += timing.pausedMs;
1418
1419
  }
1419
1420
  function queueHeartbeatFlush(activity, timing, flush) {
1420
- markPersistedTiming(activity, timing);
1421
1421
  async function doFlush() {
1422
- await flush();
1422
+ const wasPersisted = await flush();
1423
+ if (wasPersisted) {
1424
+ markPersistedTiming(activity, timing);
1425
+ }
1423
1426
  }
1424
1427
  if (activity.flushInFlight) {
1425
1428
  const previousFlush = activity.flushInFlight.catch(() => {
@@ -1537,8 +1540,11 @@ function createTimebackActivityTracker(client) {
1537
1540
  const body = buildHeartbeatBody(trackedActivity, timing, isFinal);
1538
1541
  await queueHeartbeatFlush(trackedActivity, timing, async () => {
1539
1542
  try {
1540
- await client["requestGameBackend"](TIMEBACK_ROUTES.HEARTBEAT, "POST", body);
1541
- } catch {}
1543
+ await client["requestGameBackend"](TIMEBACK_ROUTES.HEARTBEAT, "POST", body, undefined, { retryPolicy: HEARTBEAT_RETRY_POLICY });
1544
+ return true;
1545
+ } catch {
1546
+ return false;
1547
+ }
1542
1548
  });
1543
1549
  }
1544
1550
  function handlePageHide() {
@@ -1566,9 +1572,10 @@ function createTimebackActivityTracker(client) {
1566
1572
  keepalive: true
1567
1573
  });
1568
1574
  if (response.ok) {
1569
- return;
1575
+ return true;
1570
1576
  }
1571
1577
  } catch {}
1578
+ return false;
1572
1579
  });
1573
1580
  }
1574
1581
  function cleanupListeners() {
@@ -2219,7 +2226,8 @@ async function fetchWithRetry(url, init2) {
2219
2226
  const startedAt = Date.now();
2220
2227
  for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
2221
2228
  const retryDelayMs = RETRY_DELAYS_MS[attempt];
2222
- const canRetry = init2.method === "GET" && retryDelayMs !== undefined;
2229
+ const retryableMethods = init2.retryPolicy?.retryableMethods ?? ["GET"];
2230
+ const canRetry = retryDelayMs !== undefined && retryableMethods.includes(init2.method);
2223
2231
  try {
2224
2232
  const response = await fetch(url, init2);
2225
2233
  if (canRetry && isRetryableStatus(response.status)) {
@@ -2303,7 +2311,8 @@ async function request({
2303
2311
  method = "GET",
2304
2312
  body,
2305
2313
  extraHeaders = {},
2306
- raw = false
2314
+ raw = false,
2315
+ retryPolicy
2307
2316
  }) {
2308
2317
  const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
2309
2318
  const headers = { ...extraHeaders };
@@ -2312,7 +2321,8 @@ async function request({
2312
2321
  method,
2313
2322
  headers,
2314
2323
  body: payload,
2315
- credentials: "omit"
2324
+ credentials: "omit",
2325
+ retryPolicy
2316
2326
  }));
2317
2327
  if (raw) {
2318
2328
  return res;
@@ -2447,7 +2457,8 @@ class PlaycademyBaseClient {
2447
2457
  body: options?.body,
2448
2458
  baseUrl: this.baseUrl,
2449
2459
  extraHeaders: effectiveHeaders,
2450
- raw: options?.raw
2460
+ raw: options?.raw,
2461
+ retryPolicy: options?.retryPolicy
2451
2462
  });
2452
2463
  this.connectionManager?.reportRequestSuccess();
2453
2464
  return result;
@@ -2456,7 +2467,7 @@ class PlaycademyBaseClient {
2456
2467
  throw error;
2457
2468
  }
2458
2469
  }
2459
- async requestGameBackend(path, method, body, headers, raw) {
2470
+ async requestGameBackend(path, method, body, headers, options) {
2460
2471
  const effectiveHeaders = {
2461
2472
  ...headers,
2462
2473
  ...this.authStrategy.getHeaders()
@@ -2468,7 +2479,8 @@ class PlaycademyBaseClient {
2468
2479
  body,
2469
2480
  baseUrl: this.getGameBackendUrl(),
2470
2481
  extraHeaders: effectiveHeaders,
2471
- raw
2482
+ raw: options?.raw,
2483
+ retryPolicy: options?.retryPolicy
2472
2484
  });
2473
2485
  this.connectionManager?.reportRequestSuccess();
2474
2486
  return result;
@@ -7,6 +7,9 @@ import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
7
7
 
8
8
  /** Permitted HTTP verbs */
9
9
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
10
+ interface RetryPolicy {
11
+ retryableMethods?: readonly Method[];
12
+ }
10
13
 
11
14
  /**
12
15
  * TimeBack Enums & Literal Types
@@ -5137,11 +5140,15 @@ declare abstract class PlaycademyBaseClient {
5137
5140
  body?: unknown;
5138
5141
  headers?: Record<string, string>;
5139
5142
  raw?: boolean;
5143
+ retryPolicy?: RetryPolicy;
5140
5144
  }): Promise<T>;
5141
5145
  /**
5142
5146
  * Makes an authenticated HTTP request to the game's backend Worker.
5143
5147
  */
5144
- protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, raw?: boolean): Promise<T>;
5148
+ protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
5149
+ raw?: boolean;
5150
+ retryPolicy?: RetryPolicy;
5151
+ }): Promise<T>;
5145
5152
  /**
5146
5153
  * Ensures a gameId is available, throwing an error if not.
5147
5154
  */
package/dist/internal.js CHANGED
@@ -1037,7 +1037,7 @@ function createBackendNamespace(client) {
1037
1037
  return client["requestGameBackend"](normalizePath(path), method, body, headers);
1038
1038
  },
1039
1039
  async download(path, method = "GET", body, headers) {
1040
- return client["requestGameBackend"](normalizePath(path), method, body, headers, true);
1040
+ return client["requestGameBackend"](normalizePath(path), method, body, headers, { raw: true });
1041
1041
  },
1042
1042
  url(pathOrStrings, ...values) {
1043
1043
  if (Array.isArray(pathOrStrings) && "raw" in pathOrStrings) {
@@ -1391,6 +1391,7 @@ function isValidUUID(value) {
1391
1391
  // src/core/activity-tracker.ts
1392
1392
  var DEFAULT_HIDDEN_TIMEOUT_MS = 10 * 60 * 1000;
1393
1393
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
1394
+ var HEARTBEAT_RETRY_POLICY = { retryableMethods: ["POST"] };
1394
1395
  function getCurrentPausedTotal(activity, now = Date.now()) {
1395
1396
  if (activity.pauseStartTime === null) {
1396
1397
  return activity.pausedTime;
@@ -1417,9 +1418,11 @@ function markPersistedTiming(activity, timing) {
1417
1418
  activity.totalPersistedPausedMs += timing.pausedMs;
1418
1419
  }
1419
1420
  function queueHeartbeatFlush(activity, timing, flush) {
1420
- markPersistedTiming(activity, timing);
1421
1421
  async function doFlush() {
1422
- await flush();
1422
+ const wasPersisted = await flush();
1423
+ if (wasPersisted) {
1424
+ markPersistedTiming(activity, timing);
1425
+ }
1423
1426
  }
1424
1427
  if (activity.flushInFlight) {
1425
1428
  const previousFlush = activity.flushInFlight.catch(() => {
@@ -1537,8 +1540,11 @@ function createTimebackActivityTracker(client) {
1537
1540
  const body = buildHeartbeatBody(trackedActivity, timing, isFinal);
1538
1541
  await queueHeartbeatFlush(trackedActivity, timing, async () => {
1539
1542
  try {
1540
- await client["requestGameBackend"](TIMEBACK_ROUTES.HEARTBEAT, "POST", body);
1541
- } catch {}
1543
+ await client["requestGameBackend"](TIMEBACK_ROUTES.HEARTBEAT, "POST", body, undefined, { retryPolicy: HEARTBEAT_RETRY_POLICY });
1544
+ return true;
1545
+ } catch {
1546
+ return false;
1547
+ }
1542
1548
  });
1543
1549
  }
1544
1550
  function handlePageHide() {
@@ -1566,9 +1572,10 @@ function createTimebackActivityTracker(client) {
1566
1572
  keepalive: true
1567
1573
  });
1568
1574
  if (response.ok) {
1569
- return;
1575
+ return true;
1570
1576
  }
1571
1577
  } catch {}
1578
+ return false;
1572
1579
  });
1573
1580
  }
1574
1581
  function cleanupListeners() {
@@ -3179,7 +3186,8 @@ async function fetchWithRetry(url, init2) {
3179
3186
  const startedAt = Date.now();
3180
3187
  for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
3181
3188
  const retryDelayMs = RETRY_DELAYS_MS[attempt];
3182
- const canRetry = init2.method === "GET" && retryDelayMs !== undefined;
3189
+ const retryableMethods = init2.retryPolicy?.retryableMethods ?? ["GET"];
3190
+ const canRetry = retryDelayMs !== undefined && retryableMethods.includes(init2.method);
3183
3191
  try {
3184
3192
  const response = await fetch(url, init2);
3185
3193
  if (canRetry && isRetryableStatus(response.status)) {
@@ -3263,7 +3271,8 @@ async function request({
3263
3271
  method = "GET",
3264
3272
  body,
3265
3273
  extraHeaders = {},
3266
- raw = false
3274
+ raw = false,
3275
+ retryPolicy
3267
3276
  }) {
3268
3277
  const url = baseUrl.replace(/\/$/, "") + (path.startsWith("/") ? path : `/${path}`);
3269
3278
  const headers = { ...extraHeaders };
@@ -3272,7 +3281,8 @@ async function request({
3272
3281
  method,
3273
3282
  headers,
3274
3283
  body: payload,
3275
- credentials: "omit"
3284
+ credentials: "omit",
3285
+ retryPolicy
3276
3286
  }));
3277
3287
  if (raw) {
3278
3288
  return res;
@@ -3407,7 +3417,8 @@ class PlaycademyBaseClient {
3407
3417
  body: options?.body,
3408
3418
  baseUrl: this.baseUrl,
3409
3419
  extraHeaders: effectiveHeaders,
3410
- raw: options?.raw
3420
+ raw: options?.raw,
3421
+ retryPolicy: options?.retryPolicy
3411
3422
  });
3412
3423
  this.connectionManager?.reportRequestSuccess();
3413
3424
  return result;
@@ -3416,7 +3427,7 @@ class PlaycademyBaseClient {
3416
3427
  throw error;
3417
3428
  }
3418
3429
  }
3419
- async requestGameBackend(path, method, body, headers, raw) {
3430
+ async requestGameBackend(path, method, body, headers, options) {
3420
3431
  const effectiveHeaders = {
3421
3432
  ...headers,
3422
3433
  ...this.authStrategy.getHeaders()
@@ -3428,7 +3439,8 @@ class PlaycademyBaseClient {
3428
3439
  body,
3429
3440
  baseUrl: this.getGameBackendUrl(),
3430
3441
  extraHeaders: effectiveHeaders,
3431
- raw
3442
+ raw: options?.raw,
3443
+ retryPolicy: options?.retryPolicy
3432
3444
  });
3433
3445
  this.connectionManager?.reportRequestSuccess();
3434
3446
  return result;
package/dist/types.d.ts CHANGED
@@ -21,6 +21,9 @@ declare function parseOAuthState(state: string): {
21
21
 
22
22
  /** Permitted HTTP verbs */
23
23
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
24
+ interface RetryPolicy {
25
+ retryableMethods?: readonly Method[];
26
+ }
24
27
 
25
28
  /**
26
29
  * TimeBack Enums & Literal Types
@@ -4680,11 +4683,15 @@ declare abstract class PlaycademyBaseClient {
4680
4683
  body?: unknown;
4681
4684
  headers?: Record<string, string>;
4682
4685
  raw?: boolean;
4686
+ retryPolicy?: RetryPolicy;
4683
4687
  }): Promise<T>;
4684
4688
  /**
4685
4689
  * Makes an authenticated HTTP request to the game's backend Worker.
4686
4690
  */
4687
- protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, raw?: boolean): Promise<T>;
4691
+ protected requestGameBackend<T>(path: string, method: Method, body?: unknown, headers?: Record<string, string>, options?: {
4692
+ raw?: boolean;
4693
+ retryPolicy?: RetryPolicy;
4694
+ }): Promise<T>;
4688
4695
  /**
4689
4696
  * Ensures a gameId is available, throwing an error if not.
4690
4697
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.4.1-beta.8",
3
+ "version": "0.4.1-beta.9",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {