@jest-com/types 1.260826.3
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 +44 -0
- package/index.d.ts +1247 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# @jest-com/types
|
|
2
|
+
|
|
3
|
+
TypeScript types for the [Jest](https://docs.jest.com) HTML5 game SDK.
|
|
4
|
+
|
|
5
|
+
The SDK runtime is loaded from the CDN, not from npm. This package only
|
|
6
|
+
describes the global `JestSDK` object that script creates.
|
|
7
|
+
|
|
8
|
+
```html
|
|
9
|
+
<script defer src="https://cdn.jest.com/sdk/latest/jestsdk.js"></script>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install --save-dev @jest-com/types
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
Import any type from the package, anywhere in your project:
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import type { Jest, PlayerProfile, PurchaseData } from "@jest-com/types";
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
That is the whole setup. `JestSDK` and `window.JestSDK` are typed globals in
|
|
25
|
+
every file, not just the one that imports:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
await JestSDK.init();
|
|
29
|
+
|
|
30
|
+
const player = JestSDK.getPlayer();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
If your game only touches the global and never annotates a named type, there is
|
|
34
|
+
nothing for it to import. Add a `.d.ts` file instead, for example
|
|
35
|
+
`src/jestsdk.d.ts`:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
/// <reference types="@jest-com/types" />
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
TypeScript auto-loads types only from `node_modules/@types/*`, so one or the
|
|
42
|
+
other is needed.
|
|
43
|
+
|
|
44
|
+
Full docs: https://docs.jest.com/sdk/html5
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,1247 @@
|
|
|
1
|
+
export type AvatarSize = 64 | 128 | 256 | 512 | 1000;
|
|
2
|
+
export type PlayerProfile = {
|
|
3
|
+
username: string;
|
|
4
|
+
avatarUrl: string | null;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Data structure for a purchase, documented for game use.
|
|
8
|
+
*
|
|
9
|
+
* Because intended consumer is the game, we use decimal credits.
|
|
10
|
+
*/
|
|
11
|
+
export type PurchaseData = {
|
|
12
|
+
purchaseToken: string;
|
|
13
|
+
productSku: string;
|
|
14
|
+
credits: number;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
completedAt: number | null;
|
|
17
|
+
/** @deprecated Always 0. Kept for SDK backwards compatibility. */
|
|
18
|
+
estimatedRevenue: number;
|
|
19
|
+
price: number;
|
|
20
|
+
currency: string;
|
|
21
|
+
/**
|
|
22
|
+
* `true` when no money changed hands: a sandbox user made the purchase (it
|
|
23
|
+
* is then priced at 0), or it came from the Developer Console simulator
|
|
24
|
+
* (which keeps the configured price). Absent on real purchases. Grant the
|
|
25
|
+
* item as usual when testing, but keep these out of anything counting real
|
|
26
|
+
* money.
|
|
27
|
+
*/
|
|
28
|
+
sandbox?: boolean;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Data structure for a subscription offer + the wallet's current
|
|
32
|
+
* entitlement for it. Mirrored in the `SubscriptionsSigned` JWS so
|
|
33
|
+
* games can server-verify the same payload they received over the SDK.
|
|
34
|
+
*/
|
|
35
|
+
export type SubscriptionData = {
|
|
36
|
+
sku: string;
|
|
37
|
+
displayName: string;
|
|
38
|
+
displayDescription: string | null;
|
|
39
|
+
price: number;
|
|
40
|
+
currency: string;
|
|
41
|
+
billingPeriod: "monthly" | "weekly" | "yearly";
|
|
42
|
+
status: "active" | "inactive";
|
|
43
|
+
/**
|
|
44
|
+
* Whether the wallet is eligible for this subscription's free trial: it has a
|
|
45
|
+
* trial configured and the wallet has never subscribed to it before.
|
|
46
|
+
*/
|
|
47
|
+
trialEligible: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* The retention discount this wallet can claim once via
|
|
50
|
+
* `claimRetentionOffer`: a discounted price for the next billing periods of
|
|
51
|
+
* the subscription they already hold, after which the standard `price`
|
|
52
|
+
* returns automatically. Non-null only while the wallet is entitled, the
|
|
53
|
+
* offer is configured, it was never claimed before, and no free trial or
|
|
54
|
+
* introductory offer window is still running.
|
|
55
|
+
*/
|
|
56
|
+
retentionOffer: {
|
|
57
|
+
/** Discounted price in the currency specified in `currency`, in decimal. */
|
|
58
|
+
price: number;
|
|
59
|
+
/**
|
|
60
|
+
* Number of billing periods (see `billingPeriod`) the discounted price
|
|
61
|
+
* applies, starting at the next renewal.
|
|
62
|
+
*/
|
|
63
|
+
durationPeriods: number;
|
|
64
|
+
} | null;
|
|
65
|
+
/**
|
|
66
|
+
* The introductory offer available to this wallet: a discounted price for
|
|
67
|
+
* the first billing periods of the subscription, after which the standard
|
|
68
|
+
* `price` applies automatically. Non-null only when an intro offer is configured
|
|
69
|
+
* and the wallet has never subscribed to this product before.
|
|
70
|
+
*/
|
|
71
|
+
introOffer: {
|
|
72
|
+
/** Discounted price in the currency specified in `currency`, in decimal. */
|
|
73
|
+
price: number;
|
|
74
|
+
/**
|
|
75
|
+
* Number of billing periods (see `billingPeriod`) the discounted price
|
|
76
|
+
* applies, measured from signup.
|
|
77
|
+
*/
|
|
78
|
+
durationPeriods: number;
|
|
79
|
+
} | null;
|
|
80
|
+
/**
|
|
81
|
+
* `true` when no money can change hands: the player is a sandbox user (any
|
|
82
|
+
* subscription they start bills 0), or this came from the Developer Console
|
|
83
|
+
* simulator. Absent for real players. `price` still shows the configured
|
|
84
|
+
* amount, but a sandbox user never gets `introOffer` or `retentionOffer` —
|
|
85
|
+
* a checkout already forced to 0 carries no discount.
|
|
86
|
+
*/
|
|
87
|
+
sandbox?: boolean;
|
|
88
|
+
/** @deprecated Always 0. Kept for SDK backwards compatibility. */
|
|
89
|
+
estimatedRevenue: number;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export type RedirectToTeamGameErrorCode = "game_not_found" | "different_team" | "internal_error";
|
|
93
|
+
export type NotificationPriority = "low" | "medium" | "high" | "critical";
|
|
94
|
+
export type ReferralNotificationVariant = {
|
|
95
|
+
title?: string | null;
|
|
96
|
+
body: string;
|
|
97
|
+
ctaText: string;
|
|
98
|
+
imageReference?: string | null;
|
|
99
|
+
};
|
|
100
|
+
export type ReferralNotificationTemplate = {
|
|
101
|
+
minConversionCount: number;
|
|
102
|
+
variants: ReferralNotificationVariant[];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export type LifecycleListener = () => void | PromiseLike<void>;
|
|
106
|
+
export interface LifecycleModule {
|
|
107
|
+
/** Runs when the game document changes from visible to hidden. */
|
|
108
|
+
onHide(listener: LifecycleListener): () => void;
|
|
109
|
+
/** Runs when the game document changes from hidden to visible. */
|
|
110
|
+
onShow(listener: LifecycleListener): () => void;
|
|
111
|
+
/** Runs when the platform begins an exit flow for the game. */
|
|
112
|
+
onExitRequested(listener: LifecycleListener): () => void;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Player data — a simple key-value store for per-player state,
|
|
116
|
+
* persisted across sessions and devices.
|
|
117
|
+
*
|
|
118
|
+
* Use this when your game does not have its own backend. Data is
|
|
119
|
+
* stored alongside the player record on the Jest platform.
|
|
120
|
+
*
|
|
121
|
+
* Constraints:
|
|
122
|
+
* - Values must be JSON-serializable.
|
|
123
|
+
* - Limited to 1 MB per game per player; further writes fail
|
|
124
|
+
* until the stored data size is reduced.
|
|
125
|
+
* - Written directly from the client — do NOT store sensitive
|
|
126
|
+
* information or data requiring strong security guarantees.
|
|
127
|
+
* - Writes are sent as they are made; `flush()` waits for the platform
|
|
128
|
+
* to acknowledge them.
|
|
129
|
+
*
|
|
130
|
+
* Docs: https://docs.jest.com/sdk/html5/player#player-data
|
|
131
|
+
*/
|
|
132
|
+
export interface PlayerDataModule {
|
|
133
|
+
/**
|
|
134
|
+
* Returns the value for a key, or `undefined` if not set.
|
|
135
|
+
*
|
|
136
|
+
* @throws {Error} If the SDK is not initialized.
|
|
137
|
+
*/
|
|
138
|
+
get(key: string): unknown;
|
|
139
|
+
/**
|
|
140
|
+
* Returns a snapshot of all player data.
|
|
141
|
+
*
|
|
142
|
+
* Modifying the returned object does NOT update stored data;
|
|
143
|
+
* use `set()` to write back.
|
|
144
|
+
*
|
|
145
|
+
* @throws {Error} If the SDK is not initialized.
|
|
146
|
+
*/
|
|
147
|
+
getAll(): Record<string, unknown>;
|
|
148
|
+
/**
|
|
149
|
+
* Sets a single key-value pair.
|
|
150
|
+
*
|
|
151
|
+
* @throws {Error} If the SDK is not initialized.
|
|
152
|
+
*/
|
|
153
|
+
set(key: string, value: unknown): void;
|
|
154
|
+
/**
|
|
155
|
+
* Shallow-merges multiple key-value pairs into player data.
|
|
156
|
+
*
|
|
157
|
+
* Existing keys not in `partial` are preserved. To remove a key,
|
|
158
|
+
* use `delete()` (or set its value to `undefined`).
|
|
159
|
+
*
|
|
160
|
+
* @throws {Error} If the SDK is not initialized.
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* JestSDK.data.set({ score: 100, level: 2 });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
set(partial: Record<string, unknown>): void;
|
|
167
|
+
/**
|
|
168
|
+
* Deletes a key from player data.
|
|
169
|
+
*
|
|
170
|
+
* @throws {Error} If the SDK is not initialized.
|
|
171
|
+
*/
|
|
172
|
+
delete(key: string): void;
|
|
173
|
+
/**
|
|
174
|
+
* Waits for the platform to acknowledge the writes made so far.
|
|
175
|
+
*
|
|
176
|
+
* `set` and `delete` send their update as soon as they are called,
|
|
177
|
+
* unless an earlier update is still unacknowledged, in which case the
|
|
178
|
+
* queued changes coalesce into the next message. This call resolves
|
|
179
|
+
* immediately when nothing is outstanding.
|
|
180
|
+
*
|
|
181
|
+
* The acknowledgement reports that the platform handled the update, not
|
|
182
|
+
* that it stored it: an update the platform rejects because another
|
|
183
|
+
* session advanced the player's state is acknowledged as well.
|
|
184
|
+
*
|
|
185
|
+
* @throws {Error} If the SDK is not initialized.
|
|
186
|
+
*/
|
|
187
|
+
flush(): Promise<void>;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Options for {@link NotificationsModule.scheduleNotification}.
|
|
191
|
+
*
|
|
192
|
+
* Provide exactly one of `scheduledAt` (exact time) or `scheduledInDays`
|
|
193
|
+
* (fuzzy timing).
|
|
194
|
+
*/
|
|
195
|
+
export type ScheduleNotificationOptions = {
|
|
196
|
+
/**
|
|
197
|
+
* Stable identifier for this notification. Used to replace or
|
|
198
|
+
* unschedule it later. Scheduling with an existing identifier
|
|
199
|
+
* automatically replaces the previous notification.
|
|
200
|
+
*/
|
|
201
|
+
identifier?: string;
|
|
202
|
+
/**
|
|
203
|
+
* Main body text of the notification.
|
|
204
|
+
*/
|
|
205
|
+
body: string;
|
|
206
|
+
/**
|
|
207
|
+
* Optional title; rendered above the body where supported.
|
|
208
|
+
*/
|
|
209
|
+
title?: string;
|
|
210
|
+
/**
|
|
211
|
+
* Call-to-action button label. Must be 1–25 characters.
|
|
212
|
+
*/
|
|
213
|
+
ctaText: string;
|
|
214
|
+
/**
|
|
215
|
+
* Higher-priority notifications are weighted more heavily when
|
|
216
|
+
* the platform selects which notification to deliver per day.
|
|
217
|
+
* Defaults to `"low"`.
|
|
218
|
+
*/
|
|
219
|
+
priority: NotificationPriority;
|
|
220
|
+
/**
|
|
221
|
+
* Reference to a pre-approved image from the Developer Console's
|
|
222
|
+
* Assets Library. Falls back to the game's Hero image if missing
|
|
223
|
+
* or unapproved.
|
|
224
|
+
*/
|
|
225
|
+
assetReference?: string;
|
|
226
|
+
/**
|
|
227
|
+
* @deprecated Use {@link assetReference} instead.
|
|
228
|
+
*/
|
|
229
|
+
imageReference?: string;
|
|
230
|
+
/**
|
|
231
|
+
* Optional metadata embedded into the notification's link.
|
|
232
|
+
* Available via `JestSDK.getEntryPayload()` when the player taps
|
|
233
|
+
* the notification.
|
|
234
|
+
*/
|
|
235
|
+
entryPayload?: Record<string, unknown>;
|
|
236
|
+
} & ({
|
|
237
|
+
/**
|
|
238
|
+
* Exact scheduled delivery time. Must be within the next 7 days.
|
|
239
|
+
* Use this for fixed events or deadlines.
|
|
240
|
+
*/
|
|
241
|
+
scheduledAt: Date;
|
|
242
|
+
scheduledInDays?: never;
|
|
243
|
+
} | {
|
|
244
|
+
scheduledAt?: never;
|
|
245
|
+
/**
|
|
246
|
+
* Days from now to deliver the notification (1–7, inclusive).
|
|
247
|
+
* The platform picks an optimal delivery time within that window
|
|
248
|
+
* for each player.
|
|
249
|
+
*/
|
|
250
|
+
scheduledInDays: number;
|
|
251
|
+
});
|
|
252
|
+
/**
|
|
253
|
+
* A single reply option on an interactive notification step. The `key` is
|
|
254
|
+
* returned in the gathered picks; a key matching a message's `key` branches the
|
|
255
|
+
* flow there, otherwise it advances to the next message.
|
|
256
|
+
*/
|
|
257
|
+
export type InteractiveNotificationOption = {
|
|
258
|
+
key: string;
|
|
259
|
+
/** Reply-chip label (1–25 characters). */
|
|
260
|
+
label: string;
|
|
261
|
+
};
|
|
262
|
+
/** A non-terminal message: presents 1–6 reply chips. */
|
|
263
|
+
export type InteractiveNotificationStepMessage = {
|
|
264
|
+
/** Stable id; also a branch target for options keyed to it. */
|
|
265
|
+
key: string;
|
|
266
|
+
body: string;
|
|
267
|
+
options: InteractiveNotificationOption[];
|
|
268
|
+
};
|
|
269
|
+
/**
|
|
270
|
+
* A terminal message: ends its branch and is sent as the "continue in browser"
|
|
271
|
+
* link. The last message in a flow must be terminal.
|
|
272
|
+
*/
|
|
273
|
+
export type InteractiveNotificationTerminalMessage = {
|
|
274
|
+
key: string;
|
|
275
|
+
body: string;
|
|
276
|
+
/** CTA label for the link. */
|
|
277
|
+
ctaText: string;
|
|
278
|
+
};
|
|
279
|
+
export type InteractiveNotificationMessage = InteractiveNotificationStepMessage | InteractiveNotificationTerminalMessage;
|
|
280
|
+
/**
|
|
281
|
+
* Options for {@link ExperimentalNotificationsModule.scheduleInteractiveNotification}.
|
|
282
|
+
*
|
|
283
|
+
* Provide exactly one of `scheduledAt` (exact time) or `scheduledInDays`
|
|
284
|
+
* (fuzzy timing).
|
|
285
|
+
*/
|
|
286
|
+
export type ScheduleInteractiveNotificationOptions = {
|
|
287
|
+
/** Stable identifier used to replace or unschedule this notification. */
|
|
288
|
+
identifier?: string;
|
|
289
|
+
/**
|
|
290
|
+
* Ordered messages sent one after another as the player taps. The last
|
|
291
|
+
* message must be terminal (no options); terminal messages carry the
|
|
292
|
+
* "continue in browser" link.
|
|
293
|
+
*/
|
|
294
|
+
messages: InteractiveNotificationMessage[];
|
|
295
|
+
/** Delivery weight. Defaults to `"medium"`. */
|
|
296
|
+
priority?: NotificationPriority;
|
|
297
|
+
/** Pre-approved image reference for the first message. */
|
|
298
|
+
assetReference?: string;
|
|
299
|
+
/**
|
|
300
|
+
* Metadata embedded into the game link. The gathered picks are added
|
|
301
|
+
* under `interactivePicks` and delivered to the game on open.
|
|
302
|
+
*/
|
|
303
|
+
entryPayload?: Record<string, unknown>;
|
|
304
|
+
} & ({
|
|
305
|
+
scheduledAt: Date;
|
|
306
|
+
scheduledInDays?: never;
|
|
307
|
+
} | {
|
|
308
|
+
scheduledAt?: never;
|
|
309
|
+
scheduledInDays: number;
|
|
310
|
+
});
|
|
311
|
+
/**
|
|
312
|
+
* Notifications — schedule SMS / RCS / Library re-engagement
|
|
313
|
+
* messages for the current player.
|
|
314
|
+
*
|
|
315
|
+
* Only registered players receive notifications. Check via
|
|
316
|
+
* `getPlayer().registered` before scheduling — calls for guest
|
|
317
|
+
* players are valid but won't be delivered.
|
|
318
|
+
*
|
|
319
|
+
* Delivery: scheduled notifications appear in the platform Library tab.
|
|
320
|
+
* Once per day the platform also selects at most one notification per
|
|
321
|
+
* user across all games to deliver as SMS/RCS, weighted by `priority`.
|
|
322
|
+
* Delivery time is platform-determined per user, respecting compliance
|
|
323
|
+
* (quiet hours, opt-outs).
|
|
324
|
+
*
|
|
325
|
+
* Jest handles consent, opt-outs, and messaging cost subsidization.
|
|
326
|
+
*
|
|
327
|
+
* Docs: https://docs.jest.com/sdk/html5/notifications
|
|
328
|
+
*/
|
|
329
|
+
export interface NotificationsModule {
|
|
330
|
+
/**
|
|
331
|
+
* Schedules a notification for the current player.
|
|
332
|
+
*
|
|
333
|
+
* Provide either `scheduledAt` (exact time) or `scheduledInDays`
|
|
334
|
+
* (fuzzy timing — the platform picks an optimal delivery time per
|
|
335
|
+
* user), but not both.
|
|
336
|
+
*
|
|
337
|
+
* Scheduling with an existing `identifier` automatically replaces
|
|
338
|
+
* the previous notification — no need to unschedule first.
|
|
339
|
+
*
|
|
340
|
+
* Constraints (out-of-range values throw `INVALID_ARGUMENTS`):
|
|
341
|
+
* - `scheduledAt` must be within the next 7 days
|
|
342
|
+
* - `scheduledInDays` must be an integer 1–7 (inclusive)
|
|
343
|
+
* - `ctaText` must be 1–25 characters
|
|
344
|
+
*
|
|
345
|
+
* Use exact scheduling for fixed events/deadlines. Use fuzzy
|
|
346
|
+
* scheduling for time-of-day-flexible re-engagement (lets the
|
|
347
|
+
* platform optimize delivery).
|
|
348
|
+
*
|
|
349
|
+
* See {@link ScheduleNotificationOptions} for field-level docs.
|
|
350
|
+
*
|
|
351
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
352
|
+
* @example
|
|
353
|
+
* ```typescript
|
|
354
|
+
* // Fuzzy: platform picks optimal time within day 2
|
|
355
|
+
* JestSDK.notifications.scheduleNotification({
|
|
356
|
+
* identifier: "retention_d2",
|
|
357
|
+
* scheduledInDays: 2,
|
|
358
|
+
* body: "Your crops are ready to harvest",
|
|
359
|
+
* ctaText: "Play",
|
|
360
|
+
* priority: "medium",
|
|
361
|
+
* entryPayload: { source: "retention_d2" },
|
|
362
|
+
* });
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
scheduleNotification(options: ScheduleNotificationOptions): void;
|
|
366
|
+
/**
|
|
367
|
+
* Cancels a previously scheduled notification by identifier.
|
|
368
|
+
*
|
|
369
|
+
* Safe to call with an unknown identifier (no-op).
|
|
370
|
+
*
|
|
371
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
372
|
+
*/
|
|
373
|
+
unscheduleNotification(options: {
|
|
374
|
+
identifier: string;
|
|
375
|
+
}): void;
|
|
376
|
+
/** Experimental, unstable notification APIs. */
|
|
377
|
+
experimental: ExperimentalNotificationsModule;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Experimental notification APIs under `JestSDK.notifications.experimental`.
|
|
381
|
+
*
|
|
382
|
+
* These are unstable and may change without a major version bump.
|
|
383
|
+
*/
|
|
384
|
+
export interface ExperimentalNotificationsModule {
|
|
385
|
+
/**
|
|
386
|
+
* Schedules an interactive (multi-message) notification: a sequence of
|
|
387
|
+
* messages with reply chips that advance in the messaging thread as the
|
|
388
|
+
* player taps. The gathered picks are delivered to the game via
|
|
389
|
+
* `entryPayload.interactivePicks` when the player opens the game.
|
|
390
|
+
*
|
|
391
|
+
* Provide either `scheduledAt` or `scheduledInDays` (not both). Tapping a
|
|
392
|
+
* chip whose `key` matches a message's `key` branches to that message,
|
|
393
|
+
* otherwise the flow advances to the next message. A message with no options
|
|
394
|
+
* is terminal — it ends the branch and carries the "continue in browser"
|
|
395
|
+
* link (`ctaText`); the last message must be terminal.
|
|
396
|
+
*
|
|
397
|
+
* See {@link ScheduleInteractiveNotificationOptions} for field-level docs.
|
|
398
|
+
*
|
|
399
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
400
|
+
* @example
|
|
401
|
+
* ```typescript
|
|
402
|
+
* JestSDK.notifications.experimental.scheduleInteractiveNotification({
|
|
403
|
+
* identifier: "tot_daily",
|
|
404
|
+
* scheduledInDays: 1,
|
|
405
|
+
* messages: [
|
|
406
|
+
* {
|
|
407
|
+
* key: "q1",
|
|
408
|
+
* body: "Is it Synthetic or Natural?",
|
|
409
|
+
* options: [
|
|
410
|
+
* { key: "synthetic", label: "Synthetic" },
|
|
411
|
+
* { key: "natural", label: "Natural" },
|
|
412
|
+
* { key: "done", label: "Play in browser" },
|
|
413
|
+
* ],
|
|
414
|
+
* },
|
|
415
|
+
* { key: "done", body: "Keep guessing in the browser", ctaText: "Play" },
|
|
416
|
+
* ],
|
|
417
|
+
* });
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
scheduleInteractiveNotification(options: ScheduleInteractiveNotificationOptions): void;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Referrals — invite friends via shareable links and track which
|
|
424
|
+
* conversions came from which campaign.
|
|
425
|
+
*
|
|
426
|
+
* Conversions are grouped by `reference`, a stable campaign key
|
|
427
|
+
* you choose (e.g. `"unlock_party_mode_v1"`, `"share_score"`).
|
|
428
|
+
* Only invited players who complete registration count.
|
|
429
|
+
*
|
|
430
|
+
* Both methods work for guest and registered players.
|
|
431
|
+
*
|
|
432
|
+
* For high-stakes rewards (entitlements, currency), verify
|
|
433
|
+
* `referralsSigned` server-side instead of trusting the client.
|
|
434
|
+
*
|
|
435
|
+
* Docs: https://docs.jest.com/sdk/html5/referrals
|
|
436
|
+
*/
|
|
437
|
+
export interface ReferralsModule {
|
|
438
|
+
/**
|
|
439
|
+
* Returns all referral conversions for the current player,
|
|
440
|
+
* grouped by `reference`.
|
|
441
|
+
*
|
|
442
|
+
* @returns
|
|
443
|
+
* - `referrals` — map of reference → array of `{ playerId, joinedAt }`
|
|
444
|
+
* (where `joinedAt` is an ISO 8601 timestamp).
|
|
445
|
+
* - `referralsSigned` — HS256 JWS for server-side verification,
|
|
446
|
+
* signed with the game's shared secret. Verified payload shape:
|
|
447
|
+
* `{ referrals, aud: gameId, sub: referrerPlayerId }`.
|
|
448
|
+
*/
|
|
449
|
+
listReferrals: () => Promise<{
|
|
450
|
+
referrals: {
|
|
451
|
+
[reference: string]: {
|
|
452
|
+
playerId: string;
|
|
453
|
+
joinedAt: string;
|
|
454
|
+
}[];
|
|
455
|
+
};
|
|
456
|
+
referralsSigned: string;
|
|
457
|
+
}>;
|
|
458
|
+
/**
|
|
459
|
+
* Opens the platform share dialog with a referral link.
|
|
460
|
+
*
|
|
461
|
+
* Only opens the dialog — does NOT guarantee the player completes
|
|
462
|
+
* the share. The promise resolves once the dialog closes.
|
|
463
|
+
*
|
|
464
|
+
* @param opts.reference - Stable campaign key for grouping conversions
|
|
465
|
+
* (e.g. `"unlock_party_mode_v1"`).
|
|
466
|
+
* @param opts.entryPayload - Metadata embedded into the shared link,
|
|
467
|
+
* delivered to the invited player via `getEntryPayload()`.
|
|
468
|
+
* Useful for attribution, custom invite context, etc.
|
|
469
|
+
* @param opts.shareTitle - Title shown in the share sheet (platform-dependent).
|
|
470
|
+
* @param opts.shareText - Body text shown in the share sheet.
|
|
471
|
+
* @param opts.onboardingSlug - Optional game slug to route invited
|
|
472
|
+
* players through an onboarding game first.
|
|
473
|
+
* @param opts.notificationTemplates - Optional templates used to notify
|
|
474
|
+
* the referrer when invited players convert. Each template applies
|
|
475
|
+
* above its `minConversionCount` threshold; the server picks the
|
|
476
|
+
* template with the highest matching threshold and a variant from
|
|
477
|
+
* within it.
|
|
478
|
+
* @param opts.shareImage - Optional **base64 data URL** of an image (e.g.
|
|
479
|
+
* from `canvas.toDataURL("image/png")`) to use as the OG image on the
|
|
480
|
+
* referral's landing page — so messaging-app previews, social cards,
|
|
481
|
+
* etc. show the personalized image instead of the game's static share
|
|
482
|
+
* image. The platform decodes, hashes, deduplicates, hosts the bytes
|
|
483
|
+
* on its CDN, and stores the resulting URL on the referral. Accepted
|
|
484
|
+
* MIME: `image/png`, `image/jpeg`, `image/webp`. The data URL must be
|
|
485
|
+
* at most 2 MB.
|
|
486
|
+
* @returns `canceled: true` if the player dismissed the dialog.
|
|
487
|
+
*
|
|
488
|
+
* @example Share + check conversions later
|
|
489
|
+
* ```typescript
|
|
490
|
+
* // When the player taps "Invite friends":
|
|
491
|
+
* await JestSDK.referrals.shareReferralLink({
|
|
492
|
+
* reference: "unlock_party_mode_v1",
|
|
493
|
+
* shareTitle: "Come play this with me",
|
|
494
|
+
* shareText: "Join me — I want to unlock Party Mode.",
|
|
495
|
+
* });
|
|
496
|
+
*
|
|
497
|
+
* // Later (e.g. on resume): check how many invites converted
|
|
498
|
+
* const { referrals } = await JestSDK.referrals.listReferrals();
|
|
499
|
+
* const inviteCount = (referrals["unlock_party_mode_v1"] ?? []).length;
|
|
500
|
+
* if (inviteCount >= 3) {
|
|
501
|
+
* unlockPartyMode();
|
|
502
|
+
* }
|
|
503
|
+
* ```
|
|
504
|
+
*
|
|
505
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
506
|
+
*/
|
|
507
|
+
shareReferralLink: (opts: {
|
|
508
|
+
reference: string;
|
|
509
|
+
entryPayload?: Record<string, unknown>;
|
|
510
|
+
shareTitle?: string;
|
|
511
|
+
shareText?: string;
|
|
512
|
+
onboardingSlug?: string;
|
|
513
|
+
notificationTemplates?: ReferralNotificationTemplate[];
|
|
514
|
+
shareImage?: string;
|
|
515
|
+
}) => Promise<{
|
|
516
|
+
canceled: boolean;
|
|
517
|
+
}>;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Social - create social interactions between players.
|
|
521
|
+
*
|
|
522
|
+
* Get the profile of the current player if available. Only registered
|
|
523
|
+
* users will have a profile and only after they have set it up.
|
|
524
|
+
*
|
|
525
|
+
* In case you need to fill in gaps with bots, we provide avatars generated by
|
|
526
|
+
* the platform. Those are deterministically picked based on the provided username.
|
|
527
|
+
* The avatars can be scaled to the requested size to reduce network usage.
|
|
528
|
+
*
|
|
529
|
+
* Docs: https://docs.jest.com/sdk/html5/social
|
|
530
|
+
*/
|
|
531
|
+
export interface SocialModule {
|
|
532
|
+
/**
|
|
533
|
+
* Returns the profile of the currently logged in player.
|
|
534
|
+
* @param opts - Options for getting the profile
|
|
535
|
+
* @param opts.avatarSize - The size for width and height of the avatar
|
|
536
|
+
* @returns The profile of the currently logged in player
|
|
537
|
+
* @throws {Error} If the SDK is not initialized.
|
|
538
|
+
*/
|
|
539
|
+
getProfile: (opts?: {
|
|
540
|
+
avatarSize: AvatarSize;
|
|
541
|
+
}) => PlayerProfile | null;
|
|
542
|
+
/**
|
|
543
|
+
* Gets the avatar URL for a bot given its username
|
|
544
|
+
* @param opts - Options for getting the avatar URL
|
|
545
|
+
* @param opts.size - The size for width and height of the avatar in pixels
|
|
546
|
+
* @param opts.username - The username of the bot used as a seed for the avatar
|
|
547
|
+
* @returns The generated avatar URL
|
|
548
|
+
* @example
|
|
549
|
+
* ```typescript
|
|
550
|
+
* const avatarUrl = sdk.getBotAvatar({ size: 128, username: "bot" });
|
|
551
|
+
* ```
|
|
552
|
+
*/
|
|
553
|
+
getBotAvatar(opts: {
|
|
554
|
+
size?: AvatarSize;
|
|
555
|
+
username: string;
|
|
556
|
+
}): string;
|
|
557
|
+
/**
|
|
558
|
+
* Registers a custom screenshot source used when the platform asks the
|
|
559
|
+
* game for a screenshot, replacing the SDK's automatic canvas capture.
|
|
560
|
+
* The provider may be async. Return the screenshot as a base64-encoded PNG
|
|
561
|
+
* (raw or data URL), or null when no screenshot is available (e.g.
|
|
562
|
+
* mid-load or on a sensitive screen). Pass null to unregister the provider
|
|
563
|
+
* and restore automatic capture.
|
|
564
|
+
* @param provider - The screenshot source, or null to unregister.
|
|
565
|
+
* @example
|
|
566
|
+
* ```typescript
|
|
567
|
+
* JestSDK.social.setScreenshotProvider(() => {
|
|
568
|
+
* return myEngine.captureScreenshotAsBase64Png() ?? null;
|
|
569
|
+
* });
|
|
570
|
+
* ```
|
|
571
|
+
*/
|
|
572
|
+
setScreenshotProvider(provider: (() => string | null | Promise<string | null>) | null): void;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Payments — sell in-game products in USD.
|
|
576
|
+
*
|
|
577
|
+
* Players purchase products you configure in the Developer Console
|
|
578
|
+
* directly in USD on the Jest platform. Jest handles checkout
|
|
579
|
+
* end-to-end with the player.
|
|
580
|
+
*
|
|
581
|
+
* **Purchase lifecycle:**
|
|
582
|
+
* 1. List products via {@link PaymentsModule.getProducts | getProducts}.
|
|
583
|
+
* 2. Start checkout via {@link PaymentsModule.beginPurchase | beginPurchase}.
|
|
584
|
+
* 3. Grant the item to the player.
|
|
585
|
+
* 4. Confirm via {@link PaymentsModule.completePurchase | completePurchase}.
|
|
586
|
+
* 5. On startup, recover incomplete purchases via {@link PaymentsModule.getIncompletePurchases | getIncompletePurchases}.
|
|
587
|
+
*
|
|
588
|
+
* **Critical: always grant before confirming.** If the game crashes
|
|
589
|
+
* after confirming but before granting, the purchase can't be
|
|
590
|
+
* recovered (it's no longer incomplete).
|
|
591
|
+
*
|
|
592
|
+
* **Server-side verification (recommended):** for any grant that
|
|
593
|
+
* affects entitlements or currency, send `purchaseSigned` /
|
|
594
|
+
* `purchasesSigned` to your backend, verify the HS256 JWS with your
|
|
595
|
+
* shared secret, and use `purchaseToken` as an idempotency key.
|
|
596
|
+
*
|
|
597
|
+
* **Sandbox testing:** sandbox users see real product prices in the
|
|
598
|
+
* game UI, but the platform checkout modal makes clear that no charge
|
|
599
|
+
* will be made and the resulting purchase records 0 credits. Such a
|
|
600
|
+
* purchase carries `sandbox: true` — in the SDK payload and in the
|
|
601
|
+
* signed JWS — so your backend can grant the item while keeping test
|
|
602
|
+
* traffic out of revenue reporting. Purchases driven from the Developer
|
|
603
|
+
* Console simulator carry the same flag, at their configured price.
|
|
604
|
+
*
|
|
605
|
+
* Docs: https://docs.jest.com/sdk/html5/payments
|
|
606
|
+
*/
|
|
607
|
+
export interface PaymentsModule {
|
|
608
|
+
/**
|
|
609
|
+
* Lists products available for purchase, configured in the
|
|
610
|
+
* Developer Console.
|
|
611
|
+
*
|
|
612
|
+
* @returns Array of `{ sku, name, description, price, currency }` where
|
|
613
|
+
* `price` is in the specified `currency` (typically USD).
|
|
614
|
+
* @throws {Error} If the SDK is not initialized or the request fails.
|
|
615
|
+
*/
|
|
616
|
+
getProducts(): Promise<Array<{
|
|
617
|
+
sku: string;
|
|
618
|
+
name: string;
|
|
619
|
+
description: string | null;
|
|
620
|
+
price: number;
|
|
621
|
+
currency: string;
|
|
622
|
+
}>>;
|
|
623
|
+
/**
|
|
624
|
+
* Starts the platform checkout flow for a product.
|
|
625
|
+
*
|
|
626
|
+
* On success, the returned purchase is **incomplete** — your game
|
|
627
|
+
* must grant the item and then call `completePurchase`.
|
|
628
|
+
*
|
|
629
|
+
* @param options.productSku - SKU from `getProducts()`.
|
|
630
|
+
* @returns One of:
|
|
631
|
+
* - `{ result: "success", purchase, purchaseSigned }` — checkout
|
|
632
|
+
* completed. Use `purchaseSigned` for server-side verification.
|
|
633
|
+
* - `{ result: "cancel" }` — player canceled the flow.
|
|
634
|
+
* - `{ result: "error", error: "internal_error" }` — transient;
|
|
635
|
+
* safe to retry.
|
|
636
|
+
* - `{ result: "error", error: "invalid_product" }` — SKU not
|
|
637
|
+
* available; do NOT retry with the same SKU.
|
|
638
|
+
*
|
|
639
|
+
* May also throw on transient errors (e.g. timeout) — treat
|
|
640
|
+
* thrown errors as retryable.
|
|
641
|
+
*
|
|
642
|
+
* @example Full purchase flow (grant before confirm)
|
|
643
|
+
* ```typescript
|
|
644
|
+
* const result = await JestSDK.payments.beginPurchase({
|
|
645
|
+
* productSku: "powerup_pack_1",
|
|
646
|
+
* });
|
|
647
|
+
*
|
|
648
|
+
* if (result.result !== "success") {
|
|
649
|
+
* if (result.result === "error") {
|
|
650
|
+
* console.error("Purchase failed:", result.error);
|
|
651
|
+
* }
|
|
652
|
+
* return;
|
|
653
|
+
* }
|
|
654
|
+
*
|
|
655
|
+
* // Recommended: send result.purchaseSigned to your backend to verify
|
|
656
|
+
* // and grant. Use result.purchase.purchaseToken as an idempotency key.
|
|
657
|
+
* await grantItem(result.purchase.productSku);
|
|
658
|
+
*
|
|
659
|
+
* // Only confirm AFTER granting succeeded.
|
|
660
|
+
* await JestSDK.payments.completePurchase({
|
|
661
|
+
* purchaseToken: result.purchase.purchaseToken,
|
|
662
|
+
* });
|
|
663
|
+
* ```
|
|
664
|
+
*
|
|
665
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
666
|
+
*/
|
|
667
|
+
beginPurchase(options: {
|
|
668
|
+
productSku: string;
|
|
669
|
+
}): Promise<{
|
|
670
|
+
result: "success";
|
|
671
|
+
purchase: PurchaseData;
|
|
672
|
+
purchaseSigned: string;
|
|
673
|
+
} | {
|
|
674
|
+
result: "cancel";
|
|
675
|
+
} | {
|
|
676
|
+
result: "error";
|
|
677
|
+
error: "internal_error" | "invalid_product";
|
|
678
|
+
}>;
|
|
679
|
+
/**
|
|
680
|
+
* Confirms a purchase, marking it complete on the platform.
|
|
681
|
+
*
|
|
682
|
+
* **Only call this AFTER the item has been durably granted.**
|
|
683
|
+
* If you confirm first and crash before granting, the purchase
|
|
684
|
+
* can't be recovered.
|
|
685
|
+
*
|
|
686
|
+
* @param options.purchaseToken - Token from `beginPurchase` or
|
|
687
|
+
* `getIncompletePurchases`.
|
|
688
|
+
* @returns `{ result: "success" }` or:
|
|
689
|
+
* - `error: "internal_error"` — transient; retry later. Leaving
|
|
690
|
+
* the purchase incomplete is safe; it will reappear in
|
|
691
|
+
* `getIncompletePurchases`.
|
|
692
|
+
* - `error: "invalid_token"` — already confirmed, wrong player,
|
|
693
|
+
* etc. Do NOT retry with the same token.
|
|
694
|
+
*
|
|
695
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
696
|
+
*/
|
|
697
|
+
completePurchase(options: {
|
|
698
|
+
purchaseToken: string;
|
|
699
|
+
}): Promise<{
|
|
700
|
+
result: "success";
|
|
701
|
+
} | {
|
|
702
|
+
result: "error";
|
|
703
|
+
error: "internal_error" | "invalid_token";
|
|
704
|
+
}>;
|
|
705
|
+
/**
|
|
706
|
+
* Starts the platform checkout flow for a subscription.
|
|
707
|
+
*
|
|
708
|
+
* @param options.subscriptionSku - SKU of the subscription to subscribe to.
|
|
709
|
+
* @returns One of:
|
|
710
|
+
* - `{ result: "success", subscription, subscriptionSigned }` — subscribed.
|
|
711
|
+
* - `{ result: "cancel" }` — player canceled the flow.
|
|
712
|
+
* - `{ result: "error", error }` — see error code for details.
|
|
713
|
+
*
|
|
714
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
715
|
+
*/
|
|
716
|
+
beginSubscription(options: {
|
|
717
|
+
subscriptionSku: string;
|
|
718
|
+
}): Promise<{
|
|
719
|
+
result: "success";
|
|
720
|
+
subscription: SubscriptionData;
|
|
721
|
+
subscriptionSigned: string;
|
|
722
|
+
} | {
|
|
723
|
+
result: "cancel";
|
|
724
|
+
} | {
|
|
725
|
+
result: "error";
|
|
726
|
+
error: "internal_error" | "invalid_subscription" | "already_subscribed" | "guest_not_allowed";
|
|
727
|
+
}>;
|
|
728
|
+
/**
|
|
729
|
+
* Returns purchases that started checkout but were never confirmed.
|
|
730
|
+
*
|
|
731
|
+
* **Call this on every startup** to handle purchases that succeeded
|
|
732
|
+
* at checkout but never reached `completePurchase` (e.g. due to a
|
|
733
|
+
* crash, network failure, or app close).
|
|
734
|
+
*
|
|
735
|
+
* For each returned purchase: grant the item (using `productSku`),
|
|
736
|
+
* then call `completePurchase`. The response is capped at 50
|
|
737
|
+
* purchases — if `hasMore` is true, call again until it's false.
|
|
738
|
+
*
|
|
739
|
+
* Use `purchasesSigned` for server-side verification before granting.
|
|
740
|
+
*
|
|
741
|
+
* @example Recovery loop on startup
|
|
742
|
+
* ```typescript
|
|
743
|
+
* let hasMore = true;
|
|
744
|
+
* while (hasMore) {
|
|
745
|
+
* const result = await JestSDK.payments.getIncompletePurchases();
|
|
746
|
+
*
|
|
747
|
+
* for (const purchase of result.purchases) {
|
|
748
|
+
* // Recommended: verify result.purchasesSigned on your backend
|
|
749
|
+
* await grantItem(purchase.productSku);
|
|
750
|
+
*
|
|
751
|
+
* await JestSDK.payments.completePurchase({
|
|
752
|
+
* purchaseToken: purchase.purchaseToken,
|
|
753
|
+
* });
|
|
754
|
+
* }
|
|
755
|
+
*
|
|
756
|
+
* hasMore = result.hasMore;
|
|
757
|
+
* }
|
|
758
|
+
* ```
|
|
759
|
+
*/
|
|
760
|
+
getIncompletePurchases(): Promise<{
|
|
761
|
+
hasMore: boolean;
|
|
762
|
+
purchases: Array<PurchaseData>;
|
|
763
|
+
purchasesSigned: string;
|
|
764
|
+
}>;
|
|
765
|
+
/**
|
|
766
|
+
* Lists subscription offers for this game along with the player's
|
|
767
|
+
* current entitlement on each one.
|
|
768
|
+
*
|
|
769
|
+
* Subscriptions are configured per-game in the Developer Console and
|
|
770
|
+
* billed in USD on a recurring cadence (currently monthly only).
|
|
771
|
+
*
|
|
772
|
+
* Each entry's `active` field is `"active"` if the player currently
|
|
773
|
+
* holds the subscription and `"inactive"` otherwise. Grant the
|
|
774
|
+
* entitlement when `active === "active"`.
|
|
775
|
+
*
|
|
776
|
+
* Each entry also carries `trialEligible`: `true` only when the offer
|
|
777
|
+
* has a free trial and this wallet has never subscribed to it, so you
|
|
778
|
+
* can show a "Start free trial" call to action only when it applies.
|
|
779
|
+
*
|
|
780
|
+
* Similarly, `introOffer` is non-null only when the offer has an
|
|
781
|
+
* introductory price and this wallet has never subscribed to it: the
|
|
782
|
+
* player pays `introOffer.price` for the first
|
|
783
|
+
* `introOffer.durationPeriods` billing periods, then the standard
|
|
784
|
+
* `price` applies automatically.
|
|
785
|
+
*
|
|
786
|
+
* **Server-side verification (recommended):** for any grant tied to a
|
|
787
|
+
* subscription, send `signed` to your backend and verify the HS256
|
|
788
|
+
* JWS with your shared game secret. The signed payload carries the
|
|
789
|
+
* same `subscriptions` array.
|
|
790
|
+
*
|
|
791
|
+
* For sandbox users, and in the Developer Console simulator, every
|
|
792
|
+
* entry carries `sandbox: true`: `price` shows as configured, but any
|
|
793
|
+
* subscription started that way bills nothing. A sandbox user never
|
|
794
|
+
* gets `introOffer` or `retentionOffer` — a checkout already forced to
|
|
795
|
+
* 0 carries no discount.
|
|
796
|
+
*
|
|
797
|
+
* For guest players, `subscriptions` is an empty array.
|
|
798
|
+
*
|
|
799
|
+
* @throws {Error} If the SDK is not initialized or the request fails.
|
|
800
|
+
*/
|
|
801
|
+
getSubscriptions(): Promise<{
|
|
802
|
+
subscriptions: SubscriptionData[];
|
|
803
|
+
signed: string;
|
|
804
|
+
}>;
|
|
805
|
+
/**
|
|
806
|
+
* Opens a cancellation confirmation dialog for the specified subscription.
|
|
807
|
+
*
|
|
808
|
+
* If the user confirms, the subscription is cancelled at the end of the
|
|
809
|
+
* current billing period (the user retains access until then).
|
|
810
|
+
*
|
|
811
|
+
* @param options.subscriptionSku - The SKU of the subscription to cancel.
|
|
812
|
+
* @returns Result indicating success, user dismissed, or an error.
|
|
813
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
814
|
+
*/
|
|
815
|
+
cancelSubscription(options: {
|
|
816
|
+
subscriptionSku: string;
|
|
817
|
+
}): Promise<{
|
|
818
|
+
result: "success";
|
|
819
|
+
} | {
|
|
820
|
+
result: "cancel";
|
|
821
|
+
} | {
|
|
822
|
+
result: "error";
|
|
823
|
+
error: "internal_error" | "not_found" | "not_active" | "guest_not_allowed";
|
|
824
|
+
}>;
|
|
825
|
+
/**
|
|
826
|
+
* Applies the subscription's configured retention discount to the player's
|
|
827
|
+
* existing subscription — no checkout, applied instantly.
|
|
828
|
+
*
|
|
829
|
+
* Use this for retention flows: when an entitled player asks to cancel,
|
|
830
|
+
* offer the discount your subscription configures (exposed as
|
|
831
|
+
* `retentionOffer` on `SubscriptionData` while the player is eligible).
|
|
832
|
+
* On success the player's next `retentionOffer.durationPeriods` renewals
|
|
833
|
+
* bill at the discounted price, then the standard price returns
|
|
834
|
+
* automatically — same subscription, no interruption.
|
|
835
|
+
*
|
|
836
|
+
* Each player receives a subscription's retention discount only once,
|
|
837
|
+
* and not during a free trial or while an introductory offer window is
|
|
838
|
+
* still running — ineligible claims fail with `not_eligible`. Repeating
|
|
839
|
+
* the call for an already-claimed subscription re-confirms the same
|
|
840
|
+
* discount and succeeds, so retrying after an error is safe. The returned
|
|
841
|
+
* `subscription` reflects the post-claim state (`retentionOffer` becomes
|
|
842
|
+
* `null`).
|
|
843
|
+
*
|
|
844
|
+
* @param options.subscriptionSku - SKU of the subscription the player holds.
|
|
845
|
+
* @returns Result with the refreshed subscription data, or an error.
|
|
846
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
847
|
+
*/
|
|
848
|
+
claimRetentionOffer(options: {
|
|
849
|
+
subscriptionSku: string;
|
|
850
|
+
}): Promise<{
|
|
851
|
+
result: "success";
|
|
852
|
+
subscription: SubscriptionData;
|
|
853
|
+
subscriptionSigned: string;
|
|
854
|
+
} | {
|
|
855
|
+
result: "error";
|
|
856
|
+
error: "internal_error" | "not_eligible" | "guest_not_allowed";
|
|
857
|
+
}>;
|
|
858
|
+
}
|
|
859
|
+
export type Jest = JestSDK & {
|
|
860
|
+
/**
|
|
861
|
+
* Initializes the SDK. Must be called before any other SDK method.
|
|
862
|
+
* Safe to call multiple times — subsequent calls return the same
|
|
863
|
+
* ready promise.
|
|
864
|
+
*
|
|
865
|
+
* @example Typical app startup
|
|
866
|
+
* ```typescript
|
|
867
|
+
* await JestSDK.init();
|
|
868
|
+
*
|
|
869
|
+
* const player = JestSDK.getPlayer();
|
|
870
|
+
* if (!player.registered) {
|
|
871
|
+
* // Optionally prompt registration at the right moment
|
|
872
|
+
* }
|
|
873
|
+
*
|
|
874
|
+
* // Recover any purchases from a previous session that crashed
|
|
875
|
+
* await recoverPurchasesOnStartup();
|
|
876
|
+
*
|
|
877
|
+
* startGame();
|
|
878
|
+
* ```
|
|
879
|
+
*/
|
|
880
|
+
init: (opts?: {
|
|
881
|
+
/**
|
|
882
|
+
* When set to false, disables the automatic login reminder popups that appear
|
|
883
|
+
* at escalating intervals for guest users. Manual login via `JestSDK.login()`
|
|
884
|
+
* is unaffected. Defaults to true.
|
|
885
|
+
*/
|
|
886
|
+
autoLoginReminders?: boolean;
|
|
887
|
+
/**
|
|
888
|
+
* @deprecated mock is no longer used - it is automatically determined based on whether the app is run inside Jest or as standalone.
|
|
889
|
+
*/
|
|
890
|
+
mock?: boolean;
|
|
891
|
+
}) => Promise<void>;
|
|
892
|
+
};
|
|
893
|
+
/**
|
|
894
|
+
* The JestSDK runtime API, exposed as a global when the SDK script is
|
|
895
|
+
* loaded from `https://cdn.jest.com/sdk/latest/jestsdk.js`.
|
|
896
|
+
*
|
|
897
|
+
* Call {@link Jest.init} once on startup, then use the methods below to
|
|
898
|
+
* interact with the player, schedule notifications, sell products, and
|
|
899
|
+
* track referrals. Most methods throw if called before initialization.
|
|
900
|
+
*
|
|
901
|
+
* Docs: https://docs.jest.com/sdk/html5
|
|
902
|
+
*/
|
|
903
|
+
export interface JestSDK {
|
|
904
|
+
/**
|
|
905
|
+
* Resolves once the SDK is fully initialized and player data is loaded.
|
|
906
|
+
*
|
|
907
|
+
* `init()` already returns this promise — call `isReady()` only when
|
|
908
|
+
* you need to await initialization from a different code path that
|
|
909
|
+
* doesn't have access to the original `init()` promise.
|
|
910
|
+
*
|
|
911
|
+
* Safe to call any time after `init()` has been invoked, including before
|
|
912
|
+
* `init()` resolves; it chains off the same in-flight initialization.
|
|
913
|
+
*
|
|
914
|
+
* @throws {Error} If initialization times out or `init()` was never called.
|
|
915
|
+
*/
|
|
916
|
+
isReady(): Promise<void>;
|
|
917
|
+
/**
|
|
918
|
+
* Returns the entry payload for this game session, or an empty object
|
|
919
|
+
* if no payload was supplied.
|
|
920
|
+
*
|
|
921
|
+
* The entry payload is arbitrary metadata attached to the link the
|
|
922
|
+
* player used to enter the game. Common sources include:
|
|
923
|
+
* - Referral links (from `referrals.shareReferralLink`)
|
|
924
|
+
* - Notification links (from `notifications.scheduleNotification`)
|
|
925
|
+
* - Onboarding game handoffs
|
|
926
|
+
*
|
|
927
|
+
* Typical use cases: difficulty selection, referral attribution,
|
|
928
|
+
* restoring context after registration, A/B test variants.
|
|
929
|
+
*
|
|
930
|
+
* @returns The entry payload as a JSON object (always a `Record`, never null).
|
|
931
|
+
* @throws {Error} If the SDK is not initialized.
|
|
932
|
+
* @example
|
|
933
|
+
* ```typescript
|
|
934
|
+
* const payload = JestSDK.getEntryPayload();
|
|
935
|
+
* const difficulty = payload.difficulty ?? "normal";
|
|
936
|
+
* ```
|
|
937
|
+
*/
|
|
938
|
+
getEntryPayload(): Record<string, unknown>;
|
|
939
|
+
/**
|
|
940
|
+
* Returns the current player's identity.
|
|
941
|
+
*
|
|
942
|
+
* Each player has a `playerId` that is stable per-game and persists
|
|
943
|
+
* across sessions and devices, including when a guest later registers.
|
|
944
|
+
* Use this to key your own player state.
|
|
945
|
+
*
|
|
946
|
+
* - `registered: false` — guest player. Cannot receive notifications.
|
|
947
|
+
* Prompt registration via `login()` or `showRegistrationOverlay()`.
|
|
948
|
+
* - `registered: true` — has a Jest account. `username` and `avatarUrl`
|
|
949
|
+
* are platform values you may use in your UI.
|
|
950
|
+
*
|
|
951
|
+
* @returns The player object. `username`/`avatarUrl` are `null` for guests.
|
|
952
|
+
* @throws {Error} If the SDK is not initialized.
|
|
953
|
+
* @example
|
|
954
|
+
* ```typescript
|
|
955
|
+
* const player = JestSDK.getPlayer();
|
|
956
|
+
* if (!player.registered) {
|
|
957
|
+
* // Guest — consider prompting registration
|
|
958
|
+
* }
|
|
959
|
+
* ```
|
|
960
|
+
*/
|
|
961
|
+
getPlayer(): {
|
|
962
|
+
playerId: string;
|
|
963
|
+
registered: boolean;
|
|
964
|
+
username: string | null;
|
|
965
|
+
avatarUrl: string | null;
|
|
966
|
+
};
|
|
967
|
+
/**
|
|
968
|
+
* Returns a signed player payload for server-side verification.
|
|
969
|
+
*
|
|
970
|
+
* Use this when your game has a backend and needs to authenticate
|
|
971
|
+
* the player for server requests. `playerSigned` is a JWS (HS256)
|
|
972
|
+
* signed with your game's shared secret (configured in the Developer
|
|
973
|
+
* Console → Games → Secrets).
|
|
974
|
+
*
|
|
975
|
+
* Verify it on your backend with any standard JWT library. The
|
|
976
|
+
* decoded payload has the shape:
|
|
977
|
+
* ```ts
|
|
978
|
+
* {
|
|
979
|
+
* player: { playerId, registered, username, avatarUrl };
|
|
980
|
+
* iat: number; // issued-at timestamp
|
|
981
|
+
* aud: string; // game id
|
|
982
|
+
* sub: string; // player id
|
|
983
|
+
* }
|
|
984
|
+
* ```
|
|
985
|
+
*
|
|
986
|
+
* Jest does not set an explicit expiration; reject tokens older than
|
|
987
|
+
* a chosen threshold (e.g. 24h) and request a new one when needed.
|
|
988
|
+
*
|
|
989
|
+
* Works for both registered and guest players.
|
|
990
|
+
*
|
|
991
|
+
* Docs: https://docs.jest.com/sdk/html5/player#jestsdkgetplayersigned
|
|
992
|
+
*
|
|
993
|
+
* @example Authenticate a backend request
|
|
994
|
+
* ```typescript
|
|
995
|
+
* const { playerSigned } = await JestSDK.getPlayerSigned();
|
|
996
|
+
* await fetch("/api/save-progress", {
|
|
997
|
+
* method: "POST",
|
|
998
|
+
* headers: { authorization: `Bearer ${playerSigned}` },
|
|
999
|
+
* body: JSON.stringify({ score: 1500 }),
|
|
1000
|
+
* });
|
|
1001
|
+
* // Server verifies playerSigned (HS256, game's shared secret)
|
|
1002
|
+
* // before trusting the request.
|
|
1003
|
+
* ```
|
|
1004
|
+
*
|
|
1005
|
+
* @throws {Error} If the SDK is not initialized or the request fails.
|
|
1006
|
+
*/
|
|
1007
|
+
getPlayerSigned(): Promise<{
|
|
1008
|
+
player: {
|
|
1009
|
+
playerId: string;
|
|
1010
|
+
registered: boolean;
|
|
1011
|
+
username: string | null;
|
|
1012
|
+
avatarUrl: string | null;
|
|
1013
|
+
};
|
|
1014
|
+
playerSigned: string;
|
|
1015
|
+
}>;
|
|
1016
|
+
/**
|
|
1017
|
+
* Reports loading progress (0–100) to the platform loading overlay.
|
|
1018
|
+
*
|
|
1019
|
+
* Only takes effect when the game's loading screen mode is set to
|
|
1020
|
+
* "Manual" in the Developer Console. In Manual mode, the overlay is
|
|
1021
|
+
* shown automatically on game entry; the game is responsible for
|
|
1022
|
+
* progress and dismissal.
|
|
1023
|
+
*
|
|
1024
|
+
* - Values outside 0–100 are clamped; non-integers are rounded.
|
|
1025
|
+
* - Reaching 100 dismisses the overlay with a fade-out.
|
|
1026
|
+
* - Safety timeout: if no progress update is received for 15 seconds,
|
|
1027
|
+
* the platform exits the player to the home screen. Each call
|
|
1028
|
+
* resets this timer.
|
|
1029
|
+
*
|
|
1030
|
+
* Docs: https://docs.jest.com/sdk/html5/loading-screen
|
|
1031
|
+
*
|
|
1032
|
+
* @param progress - Loading progress from 0 to 100.
|
|
1033
|
+
*/
|
|
1034
|
+
setLoadingProgress(progress: number): void;
|
|
1035
|
+
/**
|
|
1036
|
+
* Signals to the Jest platform that the game is ready to be played —
|
|
1037
|
+
* all assets have loaded, initialization is complete, and the player
|
|
1038
|
+
* can now interact.
|
|
1039
|
+
*
|
|
1040
|
+
* For games in the Jest Fund, this is also an important signal that
|
|
1041
|
+
* lets the platform analyze the behavior of the traffic it sends to
|
|
1042
|
+
* the game.
|
|
1043
|
+
*
|
|
1044
|
+
* Call it the moment the player can start playing. Do not wait for
|
|
1045
|
+
* optional or user-gated post-init asset downloads; including that time
|
|
1046
|
+
* would inflate the measurement with the player's own reaction time.
|
|
1047
|
+
* Stream such assets in the background instead.
|
|
1048
|
+
*
|
|
1049
|
+
* Safe to call at any time, regardless of loading-screen mode; calls
|
|
1050
|
+
* after the first are no-ops.
|
|
1051
|
+
*
|
|
1052
|
+
* Also dismisses the manual loading overlay, as if
|
|
1053
|
+
* {@link setLoadingProgress} had been called with `100` — unless the game
|
|
1054
|
+
* already drove progress to 100 itself.
|
|
1055
|
+
*/
|
|
1056
|
+
markGameLoaded(): void;
|
|
1057
|
+
/**
|
|
1058
|
+
* Reports that the player reached your game's first meaningful
|
|
1059
|
+
* milestone — the earliest action that demonstrates the player has
|
|
1060
|
+
* experienced the core value of your game.
|
|
1061
|
+
*
|
|
1062
|
+
* You define what the milestone is. Good examples:
|
|
1063
|
+
* - Completing the first-time user experience (FTUE) or tutorial
|
|
1064
|
+
* - Completing Level 1
|
|
1065
|
+
* - Completing the first puzzle, match, or equivalent core gameplay
|
|
1066
|
+
* objective
|
|
1067
|
+
*
|
|
1068
|
+
* Call it every time the milestone is reached — you don't need to track
|
|
1069
|
+
* whether it was already reported. Calls after the first in a session are
|
|
1070
|
+
* no-ops, and the platform measures milestone reach as unique players.
|
|
1071
|
+
*
|
|
1072
|
+
* Implementing this event is required for launch. It gives the platform
|
|
1073
|
+
* a standardized early-engagement signal used to evaluate and support
|
|
1074
|
+
* games.
|
|
1075
|
+
*
|
|
1076
|
+
* Docs: https://docs.jest.com/launch-checklist
|
|
1077
|
+
*/
|
|
1078
|
+
markFirstMilestone(): void;
|
|
1079
|
+
/**
|
|
1080
|
+
* Opens the platform's built-in registration popup.
|
|
1081
|
+
*
|
|
1082
|
+
* The flow is completed via SMS/RCS, then the player is redirected
|
|
1083
|
+
* back into the game. Converting guests to registered players is
|
|
1084
|
+
* critical for retention — registered players can receive
|
|
1085
|
+
* notifications and won't lose progress when the session ends.
|
|
1086
|
+
*
|
|
1087
|
+
* Use this for the simplest integration; use
|
|
1088
|
+
* {@link showRegistrationOverlay} for a fully custom UI.
|
|
1089
|
+
*
|
|
1090
|
+
* The platform also triggers automatic registration prompts at
|
|
1091
|
+
* escalating intervals; opt out via `init({ autoLoginReminders: false })`.
|
|
1092
|
+
*
|
|
1093
|
+
* @param opts.entryPayload - Optional metadata embedded in the login link.
|
|
1094
|
+
* Available via {@link getEntryPayload} after the player returns.
|
|
1095
|
+
* Useful for tracking where login was initiated.
|
|
1096
|
+
* @returns A promise that resolves once the login popup is dismissed.
|
|
1097
|
+
* Resolves immediately if the player is already registered.
|
|
1098
|
+
* @throws {Error} If the SDK is not initialized or the entry payload is
|
|
1099
|
+
* invalid.
|
|
1100
|
+
*/
|
|
1101
|
+
login(opts?: {
|
|
1102
|
+
entryPayload?: Record<string, unknown>;
|
|
1103
|
+
}): Promise<void>;
|
|
1104
|
+
/**
|
|
1105
|
+
* Shows a minimal registration overlay and returns action handlers
|
|
1106
|
+
* for the game to wire into its own UI.
|
|
1107
|
+
*
|
|
1108
|
+
* This is the customizable alternative to {@link login}. The platform
|
|
1109
|
+
* still renders the required legal text and a close button, but the
|
|
1110
|
+
* game owns the rest of the UI (e.g. positioning, copy, buttons).
|
|
1111
|
+
*
|
|
1112
|
+
* @param opts.theme - "light" or "dark". Defaults to "dark".
|
|
1113
|
+
* @param opts.onClose - Called when the overlay is dismissed
|
|
1114
|
+
* (either via the built-in close button or `closeButtonAction`).
|
|
1115
|
+
* @param opts.entryPayload - Optional metadata embedded in the login
|
|
1116
|
+
* link, available via {@link getEntryPayload} after registration.
|
|
1117
|
+
* @param opts.message - Optional text the player's messaging app is
|
|
1118
|
+
* pre-filled with, in place of the platform's default wording. Must
|
|
1119
|
+
* contain `{{registrationCode}}` exactly once, with a space or
|
|
1120
|
+
* punctuation around it — the platform swaps in a one-time code there,
|
|
1121
|
+
* and that code is how the reply is matched back to this player. Keep the
|
|
1122
|
+
* message under 140 characters so it stays a single text. Emoji and
|
|
1123
|
+
* accented characters cost far more room, so past 70 characters they are
|
|
1124
|
+
* dropped from the pre-filled text rather than splitting it in two.
|
|
1125
|
+
* @returns Two functions to wire into your in-game UI:
|
|
1126
|
+
* - `loginButtonAction()` — starts the platform login flow
|
|
1127
|
+
* - `closeButtonAction()` — closes the overlay
|
|
1128
|
+
*
|
|
1129
|
+
* @example Wire actions to in-game UI
|
|
1130
|
+
* ```typescript
|
|
1131
|
+
* if (!JestSDK.getPlayer().registered) {
|
|
1132
|
+
* const { loginButtonAction, closeButtonAction } =
|
|
1133
|
+
* JestSDK.showRegistrationOverlay({
|
|
1134
|
+
* theme: "light",
|
|
1135
|
+
* message: "Let me into Dungeon Crawl! {{registrationCode}} is my code.",
|
|
1136
|
+
* onClose: () => closeGamePopup(),
|
|
1137
|
+
* });
|
|
1138
|
+
*
|
|
1139
|
+
* myLoginButton.onclick = loginButtonAction;
|
|
1140
|
+
* myCloseButton.onclick = closeButtonAction;
|
|
1141
|
+
* }
|
|
1142
|
+
* ```
|
|
1143
|
+
*
|
|
1144
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
1145
|
+
*/
|
|
1146
|
+
showRegistrationOverlay(opts?: {
|
|
1147
|
+
theme?: "light" | "dark";
|
|
1148
|
+
onClose?: () => void;
|
|
1149
|
+
entryPayload?: Record<string, unknown>;
|
|
1150
|
+
message?: string;
|
|
1151
|
+
}): {
|
|
1152
|
+
loginButtonAction: () => void;
|
|
1153
|
+
closeButtonAction: () => void;
|
|
1154
|
+
};
|
|
1155
|
+
/**
|
|
1156
|
+
* Records a custom analytics event for the current player.
|
|
1157
|
+
*
|
|
1158
|
+
* Events are visible in the Developer Console and can be used to
|
|
1159
|
+
* track in-game milestones, funnel steps, and feature usage.
|
|
1160
|
+
*
|
|
1161
|
+
* Property values should be JSON-serializable primitives or simple
|
|
1162
|
+
* objects. Avoid storing PII or sensitive data in event properties.
|
|
1163
|
+
*
|
|
1164
|
+
* @param eventName - Name of the event (e.g. `"level_complete"`).
|
|
1165
|
+
* Use stable, lowercase, snake_case names.
|
|
1166
|
+
* @param properties - Optional structured data attached to the event
|
|
1167
|
+
* (e.g. `{ level: 5, score: 1200 }`).
|
|
1168
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
1169
|
+
*/
|
|
1170
|
+
captureEvent(eventName: string, properties?: Record<string, unknown>): void;
|
|
1171
|
+
/**
|
|
1172
|
+
* Redirects the player to another game owned by the same team as the
|
|
1173
|
+
* calling game. The platform performs the navigation after verifying
|
|
1174
|
+
* team ownership; redirects to other teams' games are rejected.
|
|
1175
|
+
*
|
|
1176
|
+
* @param options.gameSlug - Slug of the target game (the `/g/<slug>`
|
|
1177
|
+
* part of its URL).
|
|
1178
|
+
* @param options.entryPayload - Optional metadata delivered to the
|
|
1179
|
+
* target game via {@link getEntryPayload}.
|
|
1180
|
+
* @param options.skipGameExitConfirm - When `true`, skips the exit
|
|
1181
|
+
* confirmation dialog. Defaults to `false`.
|
|
1182
|
+
* @returns `{ result: "success" }` once navigation begins, or
|
|
1183
|
+
* `{ result: "error", error }` with `"game_not_found"`,
|
|
1184
|
+
* `"different_team"`, or `"internal_error"` (transient; safe to retry).
|
|
1185
|
+
*
|
|
1186
|
+
* Docs: https://docs.jest.com/sdk/html5/game-redirects
|
|
1187
|
+
*
|
|
1188
|
+
* @example
|
|
1189
|
+
* ```typescript
|
|
1190
|
+
* const result = await JestSDK.redirectToTeamGame({
|
|
1191
|
+
* gameSlug: "trivia-battle",
|
|
1192
|
+
* entryPayload: { source: "cross_promo_button" },
|
|
1193
|
+
* });
|
|
1194
|
+
* if (result.result === "error") {
|
|
1195
|
+
* console.error("Redirect failed:", result.error);
|
|
1196
|
+
* }
|
|
1197
|
+
* ```
|
|
1198
|
+
*
|
|
1199
|
+
* @throws {Error} If the SDK is not initialized or arguments are invalid.
|
|
1200
|
+
*/
|
|
1201
|
+
redirectToTeamGame(options: {
|
|
1202
|
+
gameSlug: string;
|
|
1203
|
+
entryPayload?: Record<string, unknown>;
|
|
1204
|
+
skipGameExitConfirm?: boolean;
|
|
1205
|
+
}): Promise<{
|
|
1206
|
+
result: "success";
|
|
1207
|
+
} | {
|
|
1208
|
+
result: "error";
|
|
1209
|
+
error: RedirectToTeamGameErrorCode;
|
|
1210
|
+
}>;
|
|
1211
|
+
/** App visibility and platform exit events. */
|
|
1212
|
+
lifecycle: LifecycleModule;
|
|
1213
|
+
/** Player data — see {@link PlayerDataModule}. */
|
|
1214
|
+
data: PlayerDataModule;
|
|
1215
|
+
/** Notifications — see {@link NotificationsModule}. */
|
|
1216
|
+
notifications: NotificationsModule;
|
|
1217
|
+
/** Referrals — see {@link ReferralsModule}. */
|
|
1218
|
+
referrals: ReferralsModule;
|
|
1219
|
+
/** Payments — see {@link PaymentsModule}. */
|
|
1220
|
+
payments: PaymentsModule;
|
|
1221
|
+
/** Social - see {@link SocialModule} */
|
|
1222
|
+
social: SocialModule;
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* The global JestSDK singleton — the entry point for all SDK usage in
|
|
1226
|
+
* HTML5 games.
|
|
1227
|
+
*
|
|
1228
|
+
* Available as `window.JestSDK` after loading the SDK script:
|
|
1229
|
+
* ```html
|
|
1230
|
+
* <script src="https://cdn.jest.com/sdk/latest/jestsdk.js"></script>
|
|
1231
|
+
* ```
|
|
1232
|
+
*
|
|
1233
|
+
* Call `JestSDK.init()` first; then use the methods documented on the
|
|
1234
|
+
* {@link JestSDK} interface (e.g. `JestSDK.getPlayer()`,
|
|
1235
|
+
* `JestSDK.notifications.scheduleNotification(...)`).
|
|
1236
|
+
*
|
|
1237
|
+
* The {@link Jest} type describes this singleton's full shape,
|
|
1238
|
+
* including the `init` method.
|
|
1239
|
+
*/
|
|
1240
|
+
declare global {
|
|
1241
|
+
const JestSDK: Jest;
|
|
1242
|
+
interface Window {
|
|
1243
|
+
JestSDK: Jest;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jest-com/types",
|
|
3
|
+
"version": "1.260826.3",
|
|
4
|
+
"description": "TypeScript types for the Jest HTML5 game SDK global (window.JestSDK).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"jest",
|
|
7
|
+
"jestsdk",
|
|
8
|
+
"html5",
|
|
9
|
+
"games",
|
|
10
|
+
"types",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://docs.jest.com/sdk/html5",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"types": "./index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"index.d.ts",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"registry": "https://registry.npmjs.org/"
|
|
29
|
+
}
|
|
30
|
+
}
|