@sensiblestats/widget-sdk 0.74.0 → 0.75.0
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/CHANGELOG.md +12 -0
- package/README.md +6 -2
- package/dist/index.cjs +127 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +204 -2
- package/dist/index.d.ts +204 -2
- package/dist/index.js +127 -19
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @sensiblestats/widget-sdk
|
|
2
2
|
|
|
3
|
+
## 0.75.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Personal greeting sections, a Favourites tab, and a combo_add event when a builder combo is added to the slip.
|
|
8
|
+
|
|
9
|
+
## 0.74.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Send the visitor ids (`operatorUserId`, `anonymousUserId`) in the `X-SS-Operator-User-Id` and `X-SS-Anonymous-User-Id` request headers on every gateway call instead of the query string and JSON body. Blank ids, and ids the gateway would reject (non-ASCII or over 128 characters), send no header; `sendSignal` then falls back to the anonymous id. Public function signatures are unchanged.
|
|
14
|
+
|
|
3
15
|
## 0.74.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -22,13 +22,17 @@ for await (const ev of client.chat({ message: 'odds for Arsenal?' })) {
|
|
|
22
22
|
await client.chat({ message: 'odds for Arsenal?' }, { onAnswer: a => render(a.text) })
|
|
23
23
|
|
|
24
24
|
// Multi-turn
|
|
25
|
-
const convo = client.conversation({ operatorUserId: '
|
|
25
|
+
const convo = client.conversation({ operatorUserId: '6f1c2a9e-4b7d-4e2a-9c3f-8a5d1e7b2c40' })
|
|
26
26
|
await convo.send({ message: 'and their recent form?' })
|
|
27
27
|
|
|
28
28
|
// Signals
|
|
29
|
-
await client.sendSignal({ signalType: 'MatchPageView', entityType: 'Match', entityName: 'Arsenal vs Chelsea', operatorUserId: '
|
|
29
|
+
await client.sendSignal({ signalType: 'MatchPageView', entityType: 'Match', entityName: 'Arsenal vs Chelsea', operatorUserId: '6f1c2a9e-4b7d-4e2a-9c3f-8a5d1e7b2c40' })
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Visitor ids (`operatorUserId`, `anonymousUserId`) are passed to the SDK as before. On the wire they travel in the `X-SS-Operator-User-Id` and `X-SS-Anonymous-User-Id` request headers on every gateway call, never in the URL or the JSON body; a blank id sends no header. Ids must be printable ASCII, at most 128 characters (surrounding spaces are trimmed); an id that does not qualify is not sent, as if it were blank.
|
|
33
|
+
|
|
34
|
+
`operatorUserId` is sent from the browser: use a random, non-guessable value you map to the user on your side (a UUID works), never an account number, email, username or sequential id.
|
|
35
|
+
|
|
32
36
|
Page grounding: `ChatInput.page` is a `PageContext` (`{ sport: 'football' | 'nba', kind?: 'section' | 'competition' | 'match', matchId?, competitionId? }`). Send it on the session-start greeting only; the server ignores it on any other request. `ChatGrounding.sport` names the sport of a tapped grounding. Greeting dashboard tiles may carry `meta` (a short line in the hero's top row where a price would go) and `imageCodes` (team abbreviations drawn as text marks when there are no images).
|
|
33
37
|
|
|
34
38
|
Errors are typed: `AuthError` (401), `ForbiddenError` (403), `RateLimitError` (429), `UpstreamError` (5xx), `NetworkError`, `ParseError`.
|
package/dist/index.cjs
CHANGED
|
@@ -110,6 +110,23 @@ function headersFor(cfg, json) {
|
|
|
110
110
|
if (cfg.client) h["X-SS-Client"] = cfg.client;
|
|
111
111
|
return h;
|
|
112
112
|
}
|
|
113
|
+
var OPERATOR_USER_ID_HEADER = "X-SS-Operator-User-Id";
|
|
114
|
+
var ANONYMOUS_USER_ID_HEADER = "X-SS-Anonymous-User-Id";
|
|
115
|
+
var SENDABLE_ID = /^[\x20-\x7E]{1,128}$/;
|
|
116
|
+
function isSendableId(id) {
|
|
117
|
+
const v = id?.trim();
|
|
118
|
+
return !!v && SENDABLE_ID.test(v);
|
|
119
|
+
}
|
|
120
|
+
function identityHeaders(operatorUserId, anonymousUserId) {
|
|
121
|
+
const h = {};
|
|
122
|
+
if (isSendableId(operatorUserId)) h[OPERATOR_USER_ID_HEADER] = operatorUserId.trim();
|
|
123
|
+
if (isSendableId(anonymousUserId)) h[ANONYMOUS_USER_ID_HEADER] = anonymousUserId.trim();
|
|
124
|
+
return h;
|
|
125
|
+
}
|
|
126
|
+
function splitIdentity(body) {
|
|
127
|
+
const { operatorUserId, anonymousUserId, ...rest } = body;
|
|
128
|
+
return { identity: identityHeaders(operatorUserId, anonymousUserId), rest };
|
|
129
|
+
}
|
|
113
130
|
function endpoint(cfg, path) {
|
|
114
131
|
return `${cfg.baseUrl.replace(/\/+$/, "")}/v1/${encodeURIComponent(cfg.operatorId)}/${path}`;
|
|
115
132
|
}
|
|
@@ -151,11 +168,11 @@ async function chatError(res) {
|
|
|
151
168
|
function isAbort(error) {
|
|
152
169
|
return error instanceof Error && error.name === "AbortError";
|
|
153
170
|
}
|
|
154
|
-
async function send(cfg, path, body, signal) {
|
|
171
|
+
async function send(cfg, path, body, signal, identity = {}) {
|
|
155
172
|
try {
|
|
156
173
|
return await cfg.fetch(endpoint(cfg, path), {
|
|
157
174
|
method: "POST",
|
|
158
|
-
headers: headersFor(cfg, true),
|
|
175
|
+
headers: { ...headersFor(cfg, true), ...identity },
|
|
159
176
|
body: JSON.stringify(body),
|
|
160
177
|
signal
|
|
161
178
|
});
|
|
@@ -165,24 +182,35 @@ async function send(cfg, path, body, signal) {
|
|
|
165
182
|
}
|
|
166
183
|
}
|
|
167
184
|
async function postChat(cfg, input, signal) {
|
|
168
|
-
const
|
|
185
|
+
const { userContext, ...rest } = input;
|
|
186
|
+
let body = rest;
|
|
187
|
+
let identity = {};
|
|
188
|
+
if (userContext) {
|
|
189
|
+
const split = splitIdentity(userContext);
|
|
190
|
+
identity = split.identity;
|
|
191
|
+
body = Object.keys(split.rest).length > 0 ? { ...rest, userContext: split.rest } : rest;
|
|
192
|
+
}
|
|
193
|
+
const res = await send(cfg, "chat", body, signal, identity);
|
|
169
194
|
if (!res.ok || res.body == null) throw await chatError(res);
|
|
170
195
|
return res;
|
|
171
196
|
}
|
|
172
197
|
async function postSignal(cfg, wireBody, signal) {
|
|
173
|
-
const
|
|
198
|
+
const { identity, rest } = splitIdentity(wireBody);
|
|
199
|
+
const res = await send(cfg, "signals", rest, signal, identity);
|
|
174
200
|
if (!res.ok) throw toWidgetError(res.status, res.headers.get("Retry-After"));
|
|
175
201
|
}
|
|
176
202
|
async function postEvents(cfg, batch) {
|
|
177
|
-
const
|
|
203
|
+
const { identity, rest } = splitIdentity(batch);
|
|
204
|
+
const res = await send(cfg, "events", rest, void 0, identity);
|
|
178
205
|
if (!res.ok) throw toWidgetError(res.status, res.headers.get("Retry-After"));
|
|
179
206
|
}
|
|
180
207
|
function postEventsKeepalive(cfg, batch) {
|
|
181
208
|
try {
|
|
209
|
+
const { identity, rest } = splitIdentity(batch);
|
|
182
210
|
void cfg.fetch(endpoint(cfg, "events"), {
|
|
183
211
|
method: "POST",
|
|
184
|
-
headers: headersFor(cfg, true),
|
|
185
|
-
body: JSON.stringify(
|
|
212
|
+
headers: { ...headersFor(cfg, true), ...identity },
|
|
213
|
+
body: JSON.stringify(rest),
|
|
186
214
|
keepalive: true
|
|
187
215
|
}).catch(() => {
|
|
188
216
|
});
|
|
@@ -232,8 +260,8 @@ async function postPlayerProps(cfg, matchId, sport) {
|
|
|
232
260
|
async function postSchedule(cfg, localDate, utcOffsetMinutes, operatorUserId, anonymousUserId) {
|
|
233
261
|
const res = await cfg.fetch(endpoint(cfg, "live-monitor/schedule"), {
|
|
234
262
|
method: "POST",
|
|
235
|
-
headers: headersFor(cfg, true),
|
|
236
|
-
body: JSON.stringify({ localDate, utcOffsetMinutes, sports: ["basketball", "americanfootball"]
|
|
263
|
+
headers: { ...headersFor(cfg, true), ...identityHeaders(operatorUserId, anonymousUserId) },
|
|
264
|
+
body: JSON.stringify({ localDate, utcOffsetMinutes, sports: ["basketball", "americanfootball"] })
|
|
237
265
|
});
|
|
238
266
|
if (res.status === 404) return null;
|
|
239
267
|
if (!res.ok) throw new Error(`live-monitor/schedule ${res.status}`);
|
|
@@ -242,8 +270,8 @@ async function postSchedule(cfg, localDate, utcOffsetMinutes, operatorUserId, an
|
|
|
242
270
|
async function postLeagueStar(cfg, competitionId, starred, operatorUserId, anonymousUserId) {
|
|
243
271
|
const res = await cfg.fetch(endpoint(cfg, `leagues/${competitionId}/star`), {
|
|
244
272
|
method: "POST",
|
|
245
|
-
headers: headersFor(cfg, true),
|
|
246
|
-
body: JSON.stringify({ starred
|
|
273
|
+
headers: { ...headersFor(cfg, true), ...identityHeaders(operatorUserId, anonymousUserId) },
|
|
274
|
+
body: JSON.stringify({ starred })
|
|
247
275
|
});
|
|
248
276
|
if (res.status === 404) return null;
|
|
249
277
|
if (!res.ok) throw new Error(`leagues/star ${res.status}`);
|
|
@@ -268,7 +296,7 @@ function q(parts) {
|
|
|
268
296
|
return kept.length ? `?${kept.join("&")}` : "";
|
|
269
297
|
}
|
|
270
298
|
async function getGames(cfg, operatorUserId, gameDay) {
|
|
271
|
-
const res = await cfg.fetch(endpoint(cfg, "games" + q({
|
|
299
|
+
const res = await cfg.fetch(endpoint(cfg, "games" + q({ gameDay })), { method: "GET", headers: { ...headersFor(cfg, false), ...identityHeaders(operatorUserId) } });
|
|
272
300
|
if (res.status === 404) return null;
|
|
273
301
|
if (!res.ok) throw new Error(`games ${res.status}`);
|
|
274
302
|
return await res.json();
|
|
@@ -280,23 +308,24 @@ async function getGameConfig(cfg, gameId, fixtureId, gameDay, locale) {
|
|
|
280
308
|
return await res.json();
|
|
281
309
|
}
|
|
282
310
|
async function getGameResult(cfg, gameId, operatorUserId, fixtureId, gameDay, locale) {
|
|
283
|
-
const res = await cfg.fetch(endpoint(cfg, `games/${encodeURIComponent(gameId)}/result` + q({
|
|
311
|
+
const res = await cfg.fetch(endpoint(cfg, `games/${encodeURIComponent(gameId)}/result` + q({ fixtureId, gameDay, locale })), { method: "GET", headers: { ...headersFor(cfg, false), ...identityHeaders(operatorUserId) } });
|
|
284
312
|
if (res.status === 404) return null;
|
|
285
313
|
if (!res.ok) throw new Error(`game-result ${res.status}`);
|
|
286
314
|
return await res.json();
|
|
287
315
|
}
|
|
288
316
|
async function postPlay(cfg, gameId, body, locale) {
|
|
317
|
+
const { identity, rest } = splitIdentity(body);
|
|
289
318
|
const res = await cfg.fetch(endpoint(cfg, `games/${encodeURIComponent(gameId)}/plays` + q({ locale })), {
|
|
290
319
|
method: "POST",
|
|
291
|
-
headers: headersFor(cfg, true),
|
|
292
|
-
body: JSON.stringify(
|
|
320
|
+
headers: { ...headersFor(cfg, true), ...identity },
|
|
321
|
+
body: JSON.stringify(rest)
|
|
293
322
|
});
|
|
294
323
|
if (res.status === 404) return null;
|
|
295
324
|
if (!res.ok) throw new Error(`play ${res.status}`);
|
|
296
325
|
return await res.json();
|
|
297
326
|
}
|
|
298
327
|
async function getLeaderboard(cfg, operatorUserId, week) {
|
|
299
|
-
const res = await cfg.fetch(endpoint(cfg, "games/leaderboard" + q({
|
|
328
|
+
const res = await cfg.fetch(endpoint(cfg, "games/leaderboard" + q({ week })), { method: "GET", headers: { ...headersFor(cfg, false), ...identityHeaders(operatorUserId) } });
|
|
300
329
|
if (res.status === 404) return null;
|
|
301
330
|
if (!res.ok) throw new Error(`leaderboard ${res.status}`);
|
|
302
331
|
return await res.json();
|
|
@@ -338,11 +367,68 @@ async function getClubRecords(cfg, limit) {
|
|
|
338
367
|
return await res.json();
|
|
339
368
|
}
|
|
340
369
|
async function getPricePresets(cfg, locale, operatorUserId, options) {
|
|
341
|
-
const res = await cfg.fetch(endpoint(cfg, "price-presets" + q({ locale,
|
|
370
|
+
const res = await cfg.fetch(endpoint(cfg, "price-presets" + q({ locale, pool: options?.pool })), { method: "GET", headers: { ...headersFor(cfg, false), ...identityHeaders(operatorUserId) } });
|
|
342
371
|
if (res.status === 404) return null;
|
|
343
372
|
if (!res.ok) throw new Error(`price-presets ${res.status}`);
|
|
344
373
|
return await res.json();
|
|
345
374
|
}
|
|
375
|
+
async function getBuilderGrid(cfg, locale, req) {
|
|
376
|
+
const query = q({
|
|
377
|
+
locale,
|
|
378
|
+
sport: req.sport,
|
|
379
|
+
legs: req.legs,
|
|
380
|
+
total: req.total,
|
|
381
|
+
marketKey: req.marketKey ?? void 0,
|
|
382
|
+
matchIds: req.widen ? void 0 : req.matchIds.join(","),
|
|
383
|
+
widen: req.widen ? "true" : void 0
|
|
384
|
+
});
|
|
385
|
+
const res = await cfg.fetch(endpoint(cfg, "price-presets" + query), {
|
|
386
|
+
method: "GET",
|
|
387
|
+
headers: { ...headersFor(cfg, false), ...identityHeaders(req.operatorUserId, req.anonymousUserId) }
|
|
388
|
+
});
|
|
389
|
+
if (res.status === 404) return null;
|
|
390
|
+
if (!res.ok) throw new Error(`price-presets ${res.status}`);
|
|
391
|
+
return await res.json();
|
|
392
|
+
}
|
|
393
|
+
function favouritePath(kind, id) {
|
|
394
|
+
return `favourites/${encodeURIComponent(kind)}/${encodeURIComponent(id)}`;
|
|
395
|
+
}
|
|
396
|
+
function favouritesFetch(cfg, method, path, operatorUserId, anonymousUserId) {
|
|
397
|
+
return cfg.fetch(endpoint(cfg, path), { method, headers: { ...headersFor(cfg, false), ...identityHeaders(operatorUserId, anonymousUserId) } });
|
|
398
|
+
}
|
|
399
|
+
async function getFavourites(cfg, sport, operatorUserId, anonymousUserId) {
|
|
400
|
+
const res = await favouritesFetch(cfg, "GET", "favourites" + q({ sport }), operatorUserId, anonymousUserId);
|
|
401
|
+
if (res.status === 400) {
|
|
402
|
+
const body = await res.json().catch(() => null);
|
|
403
|
+
return body?.code === "NoAcceptedIdentity" ? "noAcceptedIdentity" : null;
|
|
404
|
+
}
|
|
405
|
+
if (!res.ok) return null;
|
|
406
|
+
return await res.json();
|
|
407
|
+
}
|
|
408
|
+
var MIN_FAVOURITES_QUERY = 2;
|
|
409
|
+
async function searchFavourites(cfg, sport, query, kind, operatorUserId, anonymousUserId) {
|
|
410
|
+
const text = query.trim();
|
|
411
|
+
if (text.length < MIN_FAVOURITES_QUERY) return [];
|
|
412
|
+
const res = await favouritesFetch(cfg, "GET", "favourites/search" + q({ sport, q: text, kind }), operatorUserId, anonymousUserId);
|
|
413
|
+
if (!res.ok) return null;
|
|
414
|
+
return (await res.json()).results;
|
|
415
|
+
}
|
|
416
|
+
async function putFavourite(cfg, kind, id, operatorUserId, anonymousUserId) {
|
|
417
|
+
const res = await favouritesFetch(cfg, "PUT", favouritePath(kind, id), operatorUserId, anonymousUserId);
|
|
418
|
+
if (res.ok) return await res.json();
|
|
419
|
+
if (res.status !== 409) return null;
|
|
420
|
+
const body = await res.json().catch(() => null);
|
|
421
|
+
return body?.status === "atCap" ? { status: "atCap", count: body.count, cap: body.cap } : null;
|
|
422
|
+
}
|
|
423
|
+
async function deleteFavourite(cfg, kind, id, operatorUserId, anonymousUserId) {
|
|
424
|
+
return (await favouritesFetch(cfg, "DELETE", favouritePath(kind, id), operatorUserId, anonymousUserId)).ok;
|
|
425
|
+
}
|
|
426
|
+
async function clearFavourites(cfg, sport, operatorUserId, anonymousUserId) {
|
|
427
|
+
return (await favouritesFetch(cfg, "DELETE", "favourites" + q({ sport }), operatorUserId, anonymousUserId)).ok;
|
|
428
|
+
}
|
|
429
|
+
async function hideFavourite(cfg, kind, id, operatorUserId, anonymousUserId) {
|
|
430
|
+
return (await favouritesFetch(cfg, "POST", `${favouritePath(kind, id)}/hide`, operatorUserId, anonymousUserId)).ok;
|
|
431
|
+
}
|
|
346
432
|
|
|
347
433
|
// src/ndjson.ts
|
|
348
434
|
function parseLine(line) {
|
|
@@ -538,7 +624,7 @@ var Conversation = class {
|
|
|
538
624
|
}
|
|
539
625
|
sendSignal(input) {
|
|
540
626
|
const operatorUserId = input.operatorUserId ?? this.context?.operatorUserId;
|
|
541
|
-
const anonymousUserId = operatorUserId ? void 0 : input.anonymousUserId ?? this.context?.anonymousUserId;
|
|
627
|
+
const anonymousUserId = isSendableId(operatorUserId) ? void 0 : input.anonymousUserId ?? this.context?.anonymousUserId;
|
|
542
628
|
if (!operatorUserId && !anonymousUserId) {
|
|
543
629
|
throw new Error("operatorUserId or anonymousUserId is required (set it on conversation() or pass it to sendSignal)");
|
|
544
630
|
}
|
|
@@ -562,12 +648,33 @@ var Conversation = class {
|
|
|
562
648
|
getPricePresets(locale, options) {
|
|
563
649
|
return getPricePresets(this.cfg, locale, this.context?.operatorUserId, options);
|
|
564
650
|
}
|
|
651
|
+
getBuilderGrid(locale, req) {
|
|
652
|
+
return getBuilderGrid(this.cfg, locale, req);
|
|
653
|
+
}
|
|
565
654
|
getSchedule(localDate, utcOffsetMinutes, operatorUserId, anonymousUserId) {
|
|
566
655
|
return postSchedule(this.cfg, localDate, utcOffsetMinutes, operatorUserId, anonymousUserId);
|
|
567
656
|
}
|
|
568
657
|
starLeague(competitionId, starred, operatorUserId, anonymousUserId) {
|
|
569
658
|
return postLeagueStar(this.cfg, competitionId, starred, operatorUserId, anonymousUserId);
|
|
570
659
|
}
|
|
660
|
+
getFavourites(sport, operatorUserId, anonymousUserId) {
|
|
661
|
+
return getFavourites(this.cfg, sport, operatorUserId, anonymousUserId);
|
|
662
|
+
}
|
|
663
|
+
searchFavourites(sport, query, kind, operatorUserId, anonymousUserId) {
|
|
664
|
+
return searchFavourites(this.cfg, sport, query, kind, operatorUserId, anonymousUserId);
|
|
665
|
+
}
|
|
666
|
+
putFavourite(kind, id, operatorUserId, anonymousUserId) {
|
|
667
|
+
return putFavourite(this.cfg, kind, id, operatorUserId, anonymousUserId);
|
|
668
|
+
}
|
|
669
|
+
deleteFavourite(kind, id, operatorUserId, anonymousUserId) {
|
|
670
|
+
return deleteFavourite(this.cfg, kind, id, operatorUserId, anonymousUserId);
|
|
671
|
+
}
|
|
672
|
+
clearFavourites(sport, operatorUserId, anonymousUserId) {
|
|
673
|
+
return clearFavourites(this.cfg, sport, operatorUserId, anonymousUserId);
|
|
674
|
+
}
|
|
675
|
+
hideFavourite(kind, id, operatorUserId, anonymousUserId) {
|
|
676
|
+
return hideFavourite(this.cfg, kind, id, operatorUserId, anonymousUserId);
|
|
677
|
+
}
|
|
571
678
|
getStates(matchIds, nbaGameIds, nflGameIds) {
|
|
572
679
|
return postStates(this.cfg, matchIds, nbaGameIds, nflGameIds);
|
|
573
680
|
}
|
|
@@ -617,7 +724,8 @@ var IMMEDIATE = /* @__PURE__ */ new Set([
|
|
|
617
724
|
"slip_click",
|
|
618
725
|
"slip_result",
|
|
619
726
|
"slip_remove",
|
|
620
|
-
"bet_placed"
|
|
727
|
+
"bet_placed",
|
|
728
|
+
"combo_add"
|
|
621
729
|
]);
|
|
622
730
|
var DEFERRED = /* @__PURE__ */ new Set(["match_view"]);
|
|
623
731
|
var DEFAULT_BATCH_INTERVAL_MS = 1e4;
|