skillprint-js-sdk 1.1.0-beta.8 → 1.1.0-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/README.md +3 -3
- package/dist/index.cjs +61 -13
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +25 -2
- package/dist/index.d.ts +25 -2
- package/dist/index.global.js +61 -13
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +61 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -201,7 +201,7 @@ manager.logEvent('ROTATE_CLOCKWISE');
|
|
|
201
201
|
manager.logEvent(GameEvent.LEVEL_COMPLETE, { level: 3, score: 1200 });
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
-
Events are queued and
|
|
204
|
+
Events are queued, and each screenshot upload carries the ones logged up to its last screenshot. Later events wait for the upload with the screenshots after them, and `stopGameSession()` sends the rest with the closing upload. The SDK decides which events go with which upload; the server keeps each upload's events with its screenshots and does no time arithmetic. If no screenshots come for a long while, events only go on their own once more than one upload's worth (1000) has piled up. `logEvent` never throws, and with no active session it only logs a warning.
|
|
205
205
|
|
|
206
206
|
### Universal and game-specific events
|
|
207
207
|
|
|
@@ -232,7 +232,7 @@ Each event can carry data (`{ level: 3, score: 1200 }`). `event` and `timestamp`
|
|
|
232
232
|
|
|
233
233
|
Each screenshot upload can carry, beside the images:
|
|
234
234
|
- `offset_ms<n>`: when the n-th screenshot was captured, in ms from session start
|
|
235
|
-
- `events`: a JSON array of the events
|
|
235
|
+
- `events`: a JSON array of the events logged up to the upload's last screenshot (at most 1000), e.g. `[{"event": "ROTATE_CLOCKWISE", "timestamp": 1250}]`, where `timestamp` is ms from session start
|
|
236
236
|
|
|
237
237
|
### Pages that upload their own frames
|
|
238
238
|
|
|
@@ -250,7 +250,7 @@ timeline.record('ROTATE_CLOCKWISE'); // when the player acts
|
|
|
250
250
|
|
|
251
251
|
await client.postScreenshots(sessionId, [frame], false, {
|
|
252
252
|
offsetsMs: [offset],
|
|
253
|
-
events: timeline.
|
|
253
|
+
events: timeline.eventsForUpload(offset) // null for the closing upload
|
|
254
254
|
});
|
|
255
255
|
```
|
|
256
256
|
|
package/dist/index.cjs
CHANGED
|
@@ -516,7 +516,7 @@ var SkillprintAPIClient = class {
|
|
|
516
516
|
formData.append(`offset_ms${i}`, String(Math.round(offset)));
|
|
517
517
|
}
|
|
518
518
|
});
|
|
519
|
-
if (events
|
|
519
|
+
if (options.events !== void 0) {
|
|
520
520
|
formData.append("events", JSON.stringify(events));
|
|
521
521
|
}
|
|
522
522
|
try {
|
|
@@ -851,6 +851,7 @@ var SessionTimeline = class {
|
|
|
851
851
|
constructor(now = defaultNow, maxQueued = 5e3) {
|
|
852
852
|
this.startedAt = null;
|
|
853
853
|
this.queue = [];
|
|
854
|
+
this.recordedAny = false;
|
|
854
855
|
this.now = now;
|
|
855
856
|
this.maxQueued = maxQueued;
|
|
856
857
|
}
|
|
@@ -858,11 +859,13 @@ var SessionTimeline = class {
|
|
|
858
859
|
start() {
|
|
859
860
|
this.startedAt = this.now();
|
|
860
861
|
this.queue = [];
|
|
862
|
+
this.recordedAny = false;
|
|
861
863
|
}
|
|
862
864
|
/** Stops the clock and empties the queue. */
|
|
863
865
|
reset() {
|
|
864
866
|
this.startedAt = null;
|
|
865
867
|
this.queue = [];
|
|
868
|
+
this.recordedAny = false;
|
|
866
869
|
}
|
|
867
870
|
get isStarted() {
|
|
868
871
|
return this.startedAt !== null;
|
|
@@ -889,15 +892,47 @@ var SessionTimeline = class {
|
|
|
889
892
|
if (this.queue.length >= this.maxQueued) return null;
|
|
890
893
|
const entry = { ...data, event, timestamp: this.offsetMs(at) };
|
|
891
894
|
this.queue.push(entry);
|
|
895
|
+
this.recordedAny = true;
|
|
892
896
|
return entry;
|
|
893
897
|
}
|
|
894
898
|
get pendingEvents() {
|
|
895
899
|
return this.queue.length;
|
|
896
900
|
}
|
|
897
|
-
/**
|
|
901
|
+
/** True once this session has recorded any event. */
|
|
902
|
+
get hasRecorded() {
|
|
903
|
+
return this.recordedAny;
|
|
904
|
+
}
|
|
905
|
+
/** Removes and returns the earliest queued events, at most `max`, in session-time order. */
|
|
898
906
|
takeEvents(max = MAX_EVENTS_PER_UPLOAD) {
|
|
907
|
+
this.sortQueue();
|
|
899
908
|
return this.queue.splice(0, max);
|
|
900
909
|
}
|
|
910
|
+
/**
|
|
911
|
+
* Removes and returns the queued events logged up to `offsetMs`
|
|
912
|
+
* (inclusive), at most `max`, in session-time order. Later ones stay
|
|
913
|
+
* queued for the upload that carries the screenshots after them.
|
|
914
|
+
*/
|
|
915
|
+
takeEventsThrough(offsetMs, max = MAX_EVENTS_PER_UPLOAD) {
|
|
916
|
+
this.sortQueue();
|
|
917
|
+
let n = 0;
|
|
918
|
+
while (n < this.queue.length && n < max && this.queue[n].timestamp <= offsetMs) n++;
|
|
919
|
+
return this.queue.splice(0, n);
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* The `events` for an upload whose last screenshot was taken at
|
|
923
|
+
* `lastFrameOffsetMs`: the events logged up to then, or every queued
|
|
924
|
+
* event for the closing upload (pass null). An empty list once the
|
|
925
|
+
* session has recorded any event, so the server can tell a quiet stretch
|
|
926
|
+
* from a game that doesn't log events; undefined before that.
|
|
927
|
+
*/
|
|
928
|
+
eventsForUpload(lastFrameOffsetMs) {
|
|
929
|
+
if (!this.recordedAny) return void 0;
|
|
930
|
+
return lastFrameOffsetMs === null ? this.takeEvents() : this.takeEventsThrough(lastFrameOffsetMs);
|
|
931
|
+
}
|
|
932
|
+
// Events stamped with `at` can arrive out of order; uploads send them in order.
|
|
933
|
+
sortQueue() {
|
|
934
|
+
this.queue.sort((a, b) => a.timestamp - b.timestamp);
|
|
935
|
+
}
|
|
901
936
|
/** Puts events back at the front of the queue, e.g. after a failed upload. */
|
|
902
937
|
requeue(events) {
|
|
903
938
|
const room = Math.max(0, this.maxQueued - this.queue.length);
|
|
@@ -1200,6 +1235,7 @@ var _SkillprintManager = class _SkillprintManager {
|
|
|
1200
1235
|
}
|
|
1201
1236
|
const sessionId = this.currentSessionId;
|
|
1202
1237
|
const remaining = this.screenshotQueue;
|
|
1238
|
+
const recorded = this.timeline.hasRecorded;
|
|
1203
1239
|
const events = this.timeline.takeEvents(Number.MAX_SAFE_INTEGER);
|
|
1204
1240
|
const inFlightPost = this.inFlightPost;
|
|
1205
1241
|
const apiClient = this.apiClient;
|
|
@@ -1222,22 +1258,34 @@ var _SkillprintManager = class _SkillprintManager {
|
|
|
1222
1258
|
if (inFlightPost) await inFlightPost;
|
|
1223
1259
|
const batchSize = this.postBatchSize();
|
|
1224
1260
|
try {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1261
|
+
const eventsUpTo = (lastFrameOffsetMs) => {
|
|
1262
|
+
if (!recorded) return void 0;
|
|
1263
|
+
const n = lastFrameOffsetMs === null ? events.length : events.findIndex((e) => e.timestamp > lastFrameOffsetMs);
|
|
1264
|
+
return events.splice(0, Math.min(n === -1 ? events.length : n, MAX_EVENTS_PER_UPLOAD));
|
|
1265
|
+
};
|
|
1228
1266
|
if (remaining.length === 0) {
|
|
1229
|
-
|
|
1267
|
+
while (events.length > MAX_EVENTS_PER_UPLOAD) {
|
|
1268
|
+
await apiClient.postScreenshots(sessionId, [], false, { events: events.splice(0, MAX_EVENTS_PER_UPLOAD) });
|
|
1269
|
+
}
|
|
1270
|
+
await apiClient.postScreenshots(sessionId, [], true, { events: eventsUpTo(null) });
|
|
1230
1271
|
} else {
|
|
1231
1272
|
for (let i = 0; i < remaining.length; i += batchSize) {
|
|
1232
1273
|
const batch = remaining.slice(i, i + batchSize);
|
|
1233
1274
|
const isLastChunk = i + batchSize >= remaining.length;
|
|
1275
|
+
if (isLastChunk) {
|
|
1276
|
+
while (events.length > MAX_EVENTS_PER_UPLOAD) {
|
|
1277
|
+
await apiClient.postScreenshots(sessionId, [], false, { events: events.splice(0, MAX_EVENTS_PER_UPLOAD) });
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
const lastOffset = Math.max(...batch.map((s) => s.offsetMs));
|
|
1234
1281
|
await apiClient.postScreenshots(sessionId, batch.map((s) => s.blob), isLastChunk, {
|
|
1235
1282
|
offsetsMs: batch.map((s) => s.offsetMs),
|
|
1236
|
-
events: isLastChunk ?
|
|
1283
|
+
events: eventsUpTo(isLastChunk ? null : lastOffset)
|
|
1237
1284
|
});
|
|
1238
1285
|
}
|
|
1239
1286
|
}
|
|
1240
|
-
|
|
1287
|
+
const flushed = remaining.length;
|
|
1288
|
+
this.log(`Skillprint session stopped. Flushed ${flushed} queued screenshots.`);
|
|
1241
1289
|
} catch (error) {
|
|
1242
1290
|
const message = error instanceof Error ? error.message : String(error);
|
|
1243
1291
|
this.log(`Failed to flush screenshots on session stop: ${message}`, "error" /* ERROR */);
|
|
@@ -1287,23 +1335,23 @@ var _SkillprintManager = class _SkillprintManager {
|
|
|
1287
1335
|
startScreenshotPostLoop() {
|
|
1288
1336
|
this.screenshotPostTimer = setInterval(async () => {
|
|
1289
1337
|
if (!this.isSessionActive || !this.apiClient || !this.currentSessionId) return;
|
|
1290
|
-
if (this.screenshotQueue.length === 0 && this.timeline.pendingEvents
|
|
1338
|
+
if (this.screenshotQueue.length === 0 && this.timeline.pendingEvents <= MAX_EVENTS_PER_UPLOAD) return;
|
|
1291
1339
|
const batch = this.screenshotQueue.splice(0, Math.min(this.screenshotQueue.length, this.postBatchSize()));
|
|
1292
|
-
const events = this.timeline.takeEvents();
|
|
1340
|
+
const events = batch.length > 0 ? this.timeline.eventsForUpload(Math.max(...batch.map((s) => s.offsetMs))) : this.timeline.takeEvents();
|
|
1293
1341
|
const apiClient = this.apiClient;
|
|
1294
1342
|
const sessionId = this.currentSessionId;
|
|
1295
|
-
this.log(`Posting ${batch.length} screenshots and ${events
|
|
1343
|
+
this.log(`Posting ${batch.length} screenshots and ${events?.length ?? 0} events...`);
|
|
1296
1344
|
const post = (async () => {
|
|
1297
1345
|
try {
|
|
1298
1346
|
const result = await apiClient.postScreenshots(sessionId, batch.map((s) => s.blob), false, {
|
|
1299
1347
|
offsetsMs: batch.map((s) => s.offsetMs),
|
|
1300
1348
|
events
|
|
1301
1349
|
});
|
|
1302
|
-
this.log(`Successfully posted ${batch.length} screenshots and ${events
|
|
1350
|
+
this.log(`Successfully posted ${batch.length} screenshots and ${events?.length ?? 0} events. Response: ${result.data}`);
|
|
1303
1351
|
} catch (error) {
|
|
1304
1352
|
const message = error instanceof Error ? error.message : String(error);
|
|
1305
1353
|
this.log(`Failed to post screenshots: ${message}`, "error" /* ERROR */);
|
|
1306
|
-
this.requeueEvents(events, error, sessionId);
|
|
1354
|
+
this.requeueEvents(events ?? [], error, sessionId);
|
|
1307
1355
|
}
|
|
1308
1356
|
})();
|
|
1309
1357
|
this.inFlightPost = post;
|