@playcademy/sdk 0.14.1-beta.2 → 0.15.1-beta.1

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
@@ -171,6 +171,7 @@ declare function parseOAuthState(state: string): {
171
171
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
172
172
  interface RetryPolicy {
173
173
  retryableMethods?: readonly Method[];
174
+ retryDelaysMs?: readonly number[];
174
175
  }
175
176
 
176
177
  /**
@@ -1149,6 +1150,12 @@ interface StartActivityOptions {
1149
1150
  * Must be a UUID (the backend validates it as such) and unique per
1150
1151
  * logical run. If omitted, the SDK generates a new UUID on each call,
1151
1152
  * which means every session is treated as its own run.
1153
+ *
1154
+ * The platform records at most one completion per activity per run:
1155
+ * after `endActivity` succeeds for an activity under a given `runId`,
1156
+ * any further `endActivity` for that same activity and `runId` is
1157
+ * skipped as a duplicate (`blockedByDedupeGuard`, zero XP awarded).
1158
+ * Start a new run (new `runId`) for a genuine retake.
1152
1159
  */
1153
1160
  runId?: string;
1154
1161
  }
package/dist/index.js CHANGED
@@ -1248,6 +1248,10 @@ var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
1248
1248
  var DEFAULT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
1249
1249
  var RELAY_INTERVAL_MS = 1000;
1250
1250
  var HEARTBEAT_RETRY_POLICY = { retryableMethods: ["POST"] };
1251
+ var END_ACTIVITY_RETRY_POLICY = {
1252
+ retryableMethods: ["POST"],
1253
+ retryDelaysMs: [500, 1500, 5000, 1e4, 20000]
1254
+ };
1251
1255
  var USER_ACTIVITY_EVENTS = ["keydown", "pointerdown", "pointermove", "wheel"];
1252
1256
  var USER_ACTIVITY_LISTENER_OPTIONS = { capture: true };
1253
1257
  function normalizeDelayMs(value, defaultValue, allowZero = true) {
@@ -1799,7 +1803,7 @@ function createTimebackActivityTracker(client) {
1799
1803
  extensions: data.extensions
1800
1804
  };
1801
1805
  try {
1802
- const response = await client["requestGameBackend"](TIMEBACK_ROUTES.END_ACTIVITY, "POST", request);
1806
+ const response = await client["requestGameBackend"](TIMEBACK_ROUTES.END_ACTIVITY, "POST", request, undefined, { retryPolicy: END_ACTIVITY_RETRY_POLICY });
1803
1807
  if (currentActivity === activity) {
1804
1808
  currentActivity = null;
1805
1809
  }
@@ -2256,8 +2260,9 @@ function isRetryableStatus(status) {
2256
2260
  }
2257
2261
  async function fetchWithRetry(url, init2) {
2258
2262
  const startedAt = Date.now();
2259
- for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
2260
- const retryDelayMs = RETRY_DELAYS_MS[attempt];
2263
+ const retryDelaysMs = init2.retryPolicy?.retryDelaysMs ?? RETRY_DELAYS_MS;
2264
+ for (let attempt = 0;attempt <= retryDelaysMs.length; attempt++) {
2265
+ const retryDelayMs = retryDelaysMs[attempt];
2261
2266
  const retryableMethods = init2.retryPolicy?.retryableMethods ?? ["GET"];
2262
2267
  const canRetry = retryDelayMs !== undefined && retryableMethods.includes(init2.method);
2263
2268
  try {
@@ -2277,7 +2282,7 @@ async function fetchWithRetry(url, init2) {
2277
2282
  }
2278
2283
  return {
2279
2284
  error: new PlaycademyError("Request failed after exhausting retries"),
2280
- retryCount: RETRY_DELAYS_MS.length,
2285
+ retryCount: retryDelaysMs.length,
2281
2286
  durationMs: Date.now() - startedAt
2282
2287
  };
2283
2288
  }
@@ -2386,7 +2391,7 @@ async function request({
2386
2391
  return rawText && rawText.length > 0 ? rawText : undefined;
2387
2392
  }
2388
2393
  // src/version.ts
2389
- var SDK_VERSION = "0.14.1-beta.2";
2394
+ var SDK_VERSION = "0.15.1-beta.1";
2390
2395
 
2391
2396
  // src/clients/base.ts
2392
2397
  class PlaycademyBaseClient {
@@ -14,6 +14,7 @@ import { AUTH_PROVIDER_IDS } from '@playcademy/constants';
14
14
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
15
15
  interface RetryPolicy {
16
16
  retryableMethods?: readonly Method[];
17
+ retryDelaysMs?: readonly number[];
17
18
  }
18
19
 
19
20
  /**
@@ -1917,6 +1918,12 @@ interface StartActivityOptions {
1917
1918
  * Must be a UUID (the backend validates it as such) and unique per
1918
1919
  * logical run. If omitted, the SDK generates a new UUID on each call,
1919
1920
  * which means every session is treated as its own run.
1921
+ *
1922
+ * The platform records at most one completion per activity per run:
1923
+ * after `endActivity` succeeds for an activity under a given `runId`,
1924
+ * any further `endActivity` for that same activity and `runId` is
1925
+ * skipped as a duplicate (`blockedByDedupeGuard`, zero XP awarded).
1926
+ * Start a new run (new `runId`) for a genuine retake.
1920
1927
  */
1921
1928
  runId?: string;
1922
1929
  }
package/dist/internal.js CHANGED
@@ -1248,6 +1248,10 @@ var DEFAULT_HEARTBEAT_INTERVAL_MS = 15000;
1248
1248
  var DEFAULT_INACTIVITY_TIMEOUT_MS = 10 * 60 * 1000;
1249
1249
  var RELAY_INTERVAL_MS = 1000;
1250
1250
  var HEARTBEAT_RETRY_POLICY = { retryableMethods: ["POST"] };
1251
+ var END_ACTIVITY_RETRY_POLICY = {
1252
+ retryableMethods: ["POST"],
1253
+ retryDelaysMs: [500, 1500, 5000, 1e4, 20000]
1254
+ };
1251
1255
  var USER_ACTIVITY_EVENTS = ["keydown", "pointerdown", "pointermove", "wheel"];
1252
1256
  var USER_ACTIVITY_LISTENER_OPTIONS = { capture: true };
1253
1257
  function normalizeDelayMs(value, defaultValue, allowZero = true) {
@@ -1799,7 +1803,7 @@ function createTimebackActivityTracker(client) {
1799
1803
  extensions: data.extensions
1800
1804
  };
1801
1805
  try {
1802
- const response = await client["requestGameBackend"](TIMEBACK_ROUTES.END_ACTIVITY, "POST", request);
1806
+ const response = await client["requestGameBackend"](TIMEBACK_ROUTES.END_ACTIVITY, "POST", request, undefined, { retryPolicy: END_ACTIVITY_RETRY_POLICY });
1803
1807
  if (currentActivity === activity) {
1804
1808
  currentActivity = null;
1805
1809
  }
@@ -2959,8 +2963,9 @@ function isRetryableStatus(status) {
2959
2963
  }
2960
2964
  async function fetchWithRetry(url, init2) {
2961
2965
  const startedAt = Date.now();
2962
- for (let attempt = 0;attempt <= RETRY_DELAYS_MS.length; attempt++) {
2963
- const retryDelayMs = RETRY_DELAYS_MS[attempt];
2966
+ const retryDelaysMs = init2.retryPolicy?.retryDelaysMs ?? RETRY_DELAYS_MS;
2967
+ for (let attempt = 0;attempt <= retryDelaysMs.length; attempt++) {
2968
+ const retryDelayMs = retryDelaysMs[attempt];
2964
2969
  const retryableMethods = init2.retryPolicy?.retryableMethods ?? ["GET"];
2965
2970
  const canRetry = retryDelayMs !== undefined && retryableMethods.includes(init2.method);
2966
2971
  try {
@@ -2980,7 +2985,7 @@ async function fetchWithRetry(url, init2) {
2980
2985
  }
2981
2986
  return {
2982
2987
  error: new PlaycademyError("Request failed after exhausting retries"),
2983
- retryCount: RETRY_DELAYS_MS.length,
2988
+ retryCount: retryDelaysMs.length,
2984
2989
  durationMs: Date.now() - startedAt
2985
2990
  };
2986
2991
  }
@@ -3089,7 +3094,7 @@ async function request({
3089
3094
  return rawText && rawText.length > 0 ? rawText : undefined;
3090
3095
  }
3091
3096
  // src/version.ts
3092
- var SDK_VERSION = "0.14.1-beta.2";
3097
+ var SDK_VERSION = "0.15.1-beta.1";
3093
3098
 
3094
3099
  // src/clients/base.ts
3095
3100
  class PlaycademyBaseClient {
@@ -49,6 +49,7 @@ function createTimebackNamespace(client) {
49
49
  return client["request"]("/api/timeback/end-activity", "POST", {
50
50
  gameId: client.gameId,
51
51
  studentId,
52
+ ...payload.runId ? { runId: payload.runId } : {},
52
53
  activityData: enrichedActivityData,
53
54
  scoreData: payload.scoreData,
54
55
  timingData: payload.timingData,
@@ -233,7 +234,7 @@ function extractApiErrorInfo(error) {
233
234
  }
234
235
 
235
236
  // src/version.ts
236
- var SDK_VERSION = "0.14.1-beta.2";
237
+ var SDK_VERSION = "0.15.1-beta.1";
237
238
 
238
239
  // src/server/request.ts
239
240
  async function makeApiRequest(opts) {
package/dist/server.js CHANGED
@@ -238,6 +238,7 @@ function createTimebackNamespace(client) {
238
238
  return client["request"]("/api/timeback/end-activity", "POST", {
239
239
  gameId: client.gameId,
240
240
  studentId,
241
+ ...payload.runId ? { runId: payload.runId } : {},
241
242
  activityData: enrichedActivityData,
242
243
  scoreData: payload.scoreData,
243
244
  timingData: payload.timingData,
@@ -422,7 +423,7 @@ function extractApiErrorInfo(error) {
422
423
  }
423
424
 
424
425
  // src/version.ts
425
- var SDK_VERSION = "0.14.1-beta.2";
426
+ var SDK_VERSION = "0.15.1-beta.1";
426
427
 
427
428
  // src/server/request.ts
428
429
  async function makeApiRequest(opts) {
package/dist/types.d.ts CHANGED
@@ -28,6 +28,7 @@ declare function parseOAuthState(state: string): {
28
28
  type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
29
29
  interface RetryPolicy {
30
30
  retryableMethods?: readonly Method[];
31
+ retryDelaysMs?: readonly number[];
31
32
  }
32
33
 
33
34
  /**
@@ -1548,6 +1549,12 @@ interface StartActivityOptions {
1548
1549
  * Must be a UUID (the backend validates it as such) and unique per
1549
1550
  * logical run. If omitted, the SDK generates a new UUID on each call,
1550
1551
  * which means every session is treated as its own run.
1552
+ *
1553
+ * The platform records at most one completion per activity per run:
1554
+ * after `endActivity` succeeds for an activity under a given `runId`,
1555
+ * any further `endActivity` for that same activity and `runId` is
1556
+ * skipped as a duplicate (`blockedByDedupeGuard`, zero XP awarded).
1557
+ * Start a new run (new `runId`) for a genuine retake.
1551
1558
  */
1552
1559
  runId?: string;
1553
1560
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sdk",
3
- "version": "0.14.1-beta.2",
3
+ "version": "0.15.1-beta.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {