@mpgd/platform 0.22.0 → 0.23.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/dist/capability-conformance.d.ts +6 -5
- package/dist/capability-conformance.js +178 -42
- package/dist/commerce-capabilities.d.ts +0 -6
- package/dist/commerce-capabilities.js +2 -8
- package/dist/commerce-contract.d.ts +12 -2
- package/dist/commerce.d.ts +8 -17
- package/dist/commerce.js +10 -94
- package/dist/index.d.ts +80 -43
- package/dist/index.js +3 -36
- package/dist/notifications-conformance.d.ts +529 -0
- package/dist/notifications-conformance.js +3537 -0
- package/dist/notifications-contract.d.ts +204 -0
- package/dist/notifications-contract.js +1 -0
- package/dist/notifications-policy.d.ts +885 -0
- package/dist/notifications-policy.js +1658 -0
- package/dist/notifications-vectors.d.ts +427 -0
- package/dist/notifications-vectors.js +4949 -0
- package/dist/notifications.d.ts +265 -0
- package/dist/notifications.js +4958 -0
- package/dist/route-vectors.d.ts +23 -0
- package/dist/route-vectors.js +96 -0
- package/dist/routes.d.ts +60 -0
- package/dist/routes.js +209 -0
- package/dist/share-link-conformance.d.ts +47 -0
- package/dist/share-link-conformance.js +100 -0
- package/package.json +26 -2
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import { type CancelNotificationReminderInput, type LocalNotificationEntry, type LocalNotificationRequest, type NotificationCapabilities, type NotificationConsentRecord, type NotificationDeliveryContent, type NotificationDeliveryOutcome, type NotificationDeliveryProviderEntry, type NotificationDeployment, type NotificationEndpointRegistration, type NotificationNotice, type NotificationNoticeIdentity, type NotificationNoticeSettings, type NotificationPermissionState, type NotificationProvider, type NotificationRuntime, type NotificationTemplateData, type NotificationTopicDeclaration, type NotificationTopicMapping, type SetNotificationReminderInput } from './notifications.js';
|
|
2
|
+
import { notificationConformanceDefaultLocale, type NotificationConformanceDeliverInput, type NotificationConformanceEndpointFixture, type NotificationConformanceHostFixture, type NotificationConformanceInstallationFixture, type NotificationConformanceProfile, type NotificationConformanceProfileLimits, type NotificationConformancePromptResult, type NotificationConformanceRejectionReason, type NotificationConformanceTapInput, type NotificationConformanceVector } from './notifications-vectors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Notifications v2 conformance runner (RFC "Conformance vectors") with the
|
|
5
|
+
* in-memory reference pieces every vector needs: a fake clock; a fake host
|
|
6
|
+
* with its fixture `NotificationProvider`; a fake backend that keeps sessions,
|
|
7
|
+
* consent records, endpoints, server reminders, notices, and an idempotency
|
|
8
|
+
* ledger, and applies the pure policy gate before every provider call; and the
|
|
9
|
+
* reference notifications runtime the runner drives as the `game` actor. Pure
|
|
10
|
+
* in-process code: no Phaser, DOM, network, or SDK imports. Nothing here sends
|
|
11
|
+
* a real notification or grants anything.
|
|
12
|
+
*
|
|
13
|
+
* A P-phase provider runs the same vectors by supplying `createProvider`; the
|
|
14
|
+
* runner reads the provider's capabilities and skips, with a reason, every
|
|
15
|
+
* vector whose `appliesTo` they do not satisfy.
|
|
16
|
+
*/
|
|
17
|
+
export { notificationConformanceDefaultLocale };
|
|
18
|
+
/** The evidence schema the fake host attaches to a host-rendered agreement. */
|
|
19
|
+
export declare const notificationConformanceAgreementSchema = "fixture.host-agreement.v1";
|
|
20
|
+
/** The fake session's installation id. */
|
|
21
|
+
export declare const notificationConformanceInstallationId = "installation-1";
|
|
22
|
+
/** Seconds a first `rate-limited` provider answer defers the send by when it names no delay. */
|
|
23
|
+
export declare const notificationConformanceRetryAfterSeconds = 60;
|
|
24
|
+
/** Seconds the fake ledger's claim lease lasts; an `ambiguous` answer keeps the claim this long. */
|
|
25
|
+
export declare const notificationConformanceLeaseSeconds = 300;
|
|
26
|
+
/** An operation the vector did not script; it fails the vector with its message. */
|
|
27
|
+
export declare class NotificationConformanceScriptError extends Error {
|
|
28
|
+
readonly name = "NotificationConformanceScriptError";
|
|
29
|
+
}
|
|
30
|
+
export interface NotificationConformanceClock {
|
|
31
|
+
/** RFC 3339 timestamp of the fake "now", in the canonical form. */
|
|
32
|
+
now(): string;
|
|
33
|
+
set(at: string): void;
|
|
34
|
+
advance(seconds: number): void;
|
|
35
|
+
}
|
|
36
|
+
export declare function createNotificationConformanceClock(initial: string): NotificationConformanceClock;
|
|
37
|
+
export interface NotificationConformanceLocalOperation {
|
|
38
|
+
readonly op: 'schedule' | 'replace' | 'cancel';
|
|
39
|
+
readonly key: string;
|
|
40
|
+
readonly topic: string;
|
|
41
|
+
readonly fireAt?: string;
|
|
42
|
+
/** Local reminders are always inexact. */
|
|
43
|
+
readonly exact?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** The `host` actor: what the vector scripts on the device or host side. */
|
|
46
|
+
export interface NotificationConformanceHostActor {
|
|
47
|
+
queuePrompt(result: NotificationConformancePromptResult): void;
|
|
48
|
+
setPermission(state: NotificationPermissionState): void;
|
|
49
|
+
issueEndpoint(endpoint: NotificationConformanceEndpointFixture): void;
|
|
50
|
+
/** The player completes the action the host waited for, such as logging in. */
|
|
51
|
+
completeAction(): void;
|
|
52
|
+
tap(input: NotificationConformanceTapInput): void;
|
|
53
|
+
/** A process restart: provider listeners and buffered events go, host state stays. */
|
|
54
|
+
restart?(): void;
|
|
55
|
+
/** Calls that reached the host prompt API. */
|
|
56
|
+
readonly prompts: number;
|
|
57
|
+
/** Native local-notification calls, in order. */
|
|
58
|
+
readonly localOperations: readonly NotificationConformanceLocalOperation[];
|
|
59
|
+
/** Pending local notifications, read when the provider has no `listLocal`. */
|
|
60
|
+
readonly localPending?: readonly LocalNotificationEntry[];
|
|
61
|
+
/** The backend's send-time read for a `permission.serverReadable` provider (Devvit `isOptedIn`). */
|
|
62
|
+
readServerPermission?(input: {
|
|
63
|
+
readonly player: string;
|
|
64
|
+
readonly topic: string;
|
|
65
|
+
}): NotificationPermissionState | Promise<NotificationPermissionState>;
|
|
66
|
+
}
|
|
67
|
+
export interface NotificationConformanceHost extends NotificationConformanceHostActor {
|
|
68
|
+
readonly provider: NotificationProvider;
|
|
69
|
+
readonly localPending: readonly LocalNotificationEntry[];
|
|
70
|
+
/** Every `scheduleLocal` request the provider received, in order. */
|
|
71
|
+
readonly localRequests: readonly LocalNotificationRequest[];
|
|
72
|
+
restart(): void;
|
|
73
|
+
readServerPermission(input: {
|
|
74
|
+
readonly player: string;
|
|
75
|
+
readonly topic: string;
|
|
76
|
+
}): NotificationPermissionState;
|
|
77
|
+
}
|
|
78
|
+
export interface CreateNotificationConformanceHostInput {
|
|
79
|
+
readonly profile: NotificationConformanceProfile;
|
|
80
|
+
readonly catalog: readonly NotificationTopicDeclaration[];
|
|
81
|
+
readonly host?: NotificationConformanceHostFixture;
|
|
82
|
+
/** The device zone a registration reports. */
|
|
83
|
+
readonly timeZone?: string;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The fake host and its fixture provider (RFC "Vector format", fake defaults).
|
|
87
|
+
*
|
|
88
|
+
* - States start at `prompt` for `os` and `game` prompts, unreadable `host`
|
|
89
|
+
* prompts read `unknown`, an unsupported profile answers `unsupported`, and a
|
|
90
|
+
* topic whose mapping needs no agreement reads `granted` and never prompts. A
|
|
91
|
+
* game-rendered consent is one boolean, as Devvit's `isOptedIn`: opted in
|
|
92
|
+
* reads `granted`, otherwise `prompt`.
|
|
93
|
+
* - `actionRequired` holds the provider in `availability: 'action-required'`
|
|
94
|
+
* with `requiredAction: 'login'` until `completeAction`.
|
|
95
|
+
* - A prompt consumes the next `queuePrompt` result and fails the vector when
|
|
96
|
+
* none is queued. A `denied` answer reports `prompt-with-rationale` while
|
|
97
|
+
* denials remain under `permission.promptLimit`, `blocked` once they are used
|
|
98
|
+
* up, and `denied` without a limit; `dismissed` leaves the state and counts
|
|
99
|
+
* no denial; `host-terms-declined` reaches the prompt and answers
|
|
100
|
+
* `action-required` with `requiredAction: 'host-settings'`. An unreadable
|
|
101
|
+
* host still reports the state its prompt just decided.
|
|
102
|
+
* - Registrations carry the device permission and zone, never an installation
|
|
103
|
+
* id; `issueEndpoint` after the provider's first subscriber emits
|
|
104
|
+
* `endpoint-changed`. Events before the first subscriber are buffered for
|
|
105
|
+
* it, and a tap is `cold` before that subscriber and `warm` after. A
|
|
106
|
+
* listener's error fails the host call that delivered the event, after every
|
|
107
|
+
* listener ran.
|
|
108
|
+
*/
|
|
109
|
+
export declare function createNotificationConformanceHost(input: CreateNotificationConformanceHostInput): NotificationConformanceHost;
|
|
110
|
+
/**
|
|
111
|
+
* The registry entry the fake backend derives for a provider (RFC "Vector
|
|
112
|
+
* format"): purposes, addressing, content, and permission fields from its
|
|
113
|
+
* capabilities; `expiry` and `actions` for a free-form device provider; `link`
|
|
114
|
+
* for `host-link` opens; per-recipient limits from `perUserPerDay` and
|
|
115
|
+
* `perUserPerMinute`, the daily one first; and no `rate`. `undefined` for a
|
|
116
|
+
* provider without remote delivery.
|
|
117
|
+
*/
|
|
118
|
+
export declare function notificationConformanceRegistryEntry(input: {
|
|
119
|
+
readonly provider: string;
|
|
120
|
+
readonly capabilities: NotificationCapabilities;
|
|
121
|
+
readonly limits?: NotificationConformanceProfileLimits;
|
|
122
|
+
}): NotificationDeliveryProviderEntry | undefined;
|
|
123
|
+
/** An authenticated game-services session; its installation id is the session's, never a request's. */
|
|
124
|
+
export interface NotificationConformanceSession {
|
|
125
|
+
readonly id: string;
|
|
126
|
+
readonly player: string;
|
|
127
|
+
readonly installationId: string;
|
|
128
|
+
}
|
|
129
|
+
/** What setting a server reminder answers: stored, or the gate step 3 to 5 refusal. */
|
|
130
|
+
export type NotificationConformanceReminderSetStatus = 'scheduled' | 'replaced' | 'permission-required' | 'request-missing' | 'preference-off' | 'advertising-consent-missing';
|
|
131
|
+
export type NotificationConformanceNoticeChannel = 'in-game' | 'push' | 'other';
|
|
132
|
+
/** The backend surface the runtime calls for the authenticated session. */
|
|
133
|
+
export interface NotificationConformanceBackendClient {
|
|
134
|
+
/** The authenticated session; `undefined` before authentication and after the session ends. */
|
|
135
|
+
session(): NotificationConformanceSession | undefined;
|
|
136
|
+
/** Calls `listener` whenever a session starts; returns the unsubscribe. */
|
|
137
|
+
onAuthenticated(listener: (session: NotificationConformanceSession) => void): () => void;
|
|
138
|
+
/** Appends a record for the session's player; the backend stamps `at` with its own clock. */
|
|
139
|
+
recordConsent(record: NotificationConsentRecord): Promise<void>;
|
|
140
|
+
/** The session player's consent records, oldest first. */
|
|
141
|
+
listConsent(): Promise<readonly NotificationConsentRecord[]>;
|
|
142
|
+
/**
|
|
143
|
+
* Binds a device registration to the session's installation and player, or
|
|
144
|
+
* keeps a platform-user registration's locale and zone for the player.
|
|
145
|
+
*/
|
|
146
|
+
registerEndpoint(registration: NotificationEndpointRegistration): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Unbinds the session's installation from `player`, the player whose
|
|
149
|
+
* session sent the unbind; an installation another player took over since
|
|
150
|
+
* stays bound.
|
|
151
|
+
*/
|
|
152
|
+
unregisterEndpoint(input: {
|
|
153
|
+
readonly provider: string;
|
|
154
|
+
readonly player: string;
|
|
155
|
+
}): Promise<void>;
|
|
156
|
+
/** Checks gate steps 3 to 5, then stores or replaces the session player's reminder with this key. */
|
|
157
|
+
setReminder(input: SetNotificationReminderInput): Promise<NotificationConformanceReminderSetStatus>;
|
|
158
|
+
/** Whether a pending reminder with this key, or a waiting send of one, existed. */
|
|
159
|
+
cancelReminder(input: CancelNotificationReminderInput): Promise<boolean>;
|
|
160
|
+
/** The session player's listed notices; none unless the deployment applies the Korean rules. */
|
|
161
|
+
listNotices(): Promise<readonly NotificationNotice[]>;
|
|
162
|
+
/**
|
|
163
|
+
* Records a notice given to the session's player, named by its identity
|
|
164
|
+
* fields, while that notice is listed; recording one that is not listed
|
|
165
|
+
* changes nothing.
|
|
166
|
+
*/
|
|
167
|
+
recordNotice(input: NotificationNoticeIdentity & {
|
|
168
|
+
readonly channel: NotificationConformanceNoticeChannel;
|
|
169
|
+
}): Promise<void>;
|
|
170
|
+
}
|
|
171
|
+
export type NotificationConformanceStoredConsent = NotificationConsentRecord & {
|
|
172
|
+
readonly player: string;
|
|
173
|
+
};
|
|
174
|
+
export interface NotificationConformanceEndpoint {
|
|
175
|
+
readonly provider: string;
|
|
176
|
+
readonly kind: 'fcm-token' | 'fcm-installation';
|
|
177
|
+
readonly value: string;
|
|
178
|
+
readonly environment?: 'production' | 'sandbox';
|
|
179
|
+
readonly installationId: string;
|
|
180
|
+
/** The device permission the latest registration reported. */
|
|
181
|
+
readonly permission: NotificationPermissionState;
|
|
182
|
+
readonly player: string;
|
|
183
|
+
readonly status: 'valid' | 'invalid';
|
|
184
|
+
readonly locale?: string;
|
|
185
|
+
readonly timeZone?: string;
|
|
186
|
+
readonly lastRegisteredAt: string;
|
|
187
|
+
/** The session that registered the endpoint; its end unbinds it. */
|
|
188
|
+
readonly session?: string;
|
|
189
|
+
}
|
|
190
|
+
/** One outcome answered or stored for a send key, in order. */
|
|
191
|
+
export type NotificationConformanceDelivery = NotificationDeliveryOutcome & {
|
|
192
|
+
readonly idempotencyKey: string;
|
|
193
|
+
readonly topic: string;
|
|
194
|
+
readonly player: string;
|
|
195
|
+
readonly at: string;
|
|
196
|
+
};
|
|
197
|
+
export interface NotificationConformanceProviderCall {
|
|
198
|
+
/** The ledger key: the send key, or `<key>:<installation id>` for a device endpoint. */
|
|
199
|
+
readonly idempotencyKey: string;
|
|
200
|
+
readonly sendKey: string;
|
|
201
|
+
readonly provider: string;
|
|
202
|
+
readonly topic: string;
|
|
203
|
+
readonly player: string;
|
|
204
|
+
/** The device address value, for device addressing. */
|
|
205
|
+
readonly address?: string;
|
|
206
|
+
readonly mapping?: NotificationTopicMapping;
|
|
207
|
+
/** The send's link, else the mapping's. */
|
|
208
|
+
readonly link?: string;
|
|
209
|
+
readonly content: NotificationDeliveryContent;
|
|
210
|
+
readonly expiresAt?: string;
|
|
211
|
+
readonly templateData?: NotificationTemplateData;
|
|
212
|
+
readonly data?: Readonly<Record<string, string>>;
|
|
213
|
+
readonly at: string;
|
|
214
|
+
}
|
|
215
|
+
export interface NotificationConformanceServerReminder {
|
|
216
|
+
readonly player: string;
|
|
217
|
+
readonly key: string;
|
|
218
|
+
readonly topic: string;
|
|
219
|
+
readonly fireAt: string;
|
|
220
|
+
readonly templateData?: NotificationTemplateData;
|
|
221
|
+
readonly link?: string;
|
|
222
|
+
}
|
|
223
|
+
/** A notice given to a player: which notice, how, and when. */
|
|
224
|
+
export interface NotificationConformanceNoticeRecord extends NotificationNoticeIdentity {
|
|
225
|
+
readonly player: string;
|
|
226
|
+
readonly channel: NotificationConformanceNoticeChannel;
|
|
227
|
+
/** When it was given. */
|
|
228
|
+
readonly at: string;
|
|
229
|
+
}
|
|
230
|
+
/** A queued answer of the server-side delivery provider (RFC "Provider answers"). */
|
|
231
|
+
export type NotificationConformanceProviderResult = {
|
|
232
|
+
readonly status: 'accepted';
|
|
233
|
+
} | {
|
|
234
|
+
readonly status: 'unregistered';
|
|
235
|
+
} | {
|
|
236
|
+
readonly status: 'rate-limited';
|
|
237
|
+
readonly retryAfterSeconds?: number;
|
|
238
|
+
} | {
|
|
239
|
+
readonly status: 'rejected';
|
|
240
|
+
readonly reason: NotificationConformanceRejectionReason;
|
|
241
|
+
} | {
|
|
242
|
+
readonly status: 'ambiguous';
|
|
243
|
+
};
|
|
244
|
+
export interface NotificationConformanceBackend extends NotificationConformanceBackendClient {
|
|
245
|
+
/** Starts a new session for `player` on the fake installation and tells the listeners. */
|
|
246
|
+
authenticate(player: string): NotificationConformanceSession;
|
|
247
|
+
/** Ends the current session; the endpoints it registered are unbound. */
|
|
248
|
+
revokeSession(): void;
|
|
249
|
+
/** Binds another installation of the default player (the `backend.registerEndpoint` action). */
|
|
250
|
+
registerInstallation(input: NotificationConformanceInstallationFixture): void;
|
|
251
|
+
/** One send through the idempotency ledger, the policy gate, and the delivery provider. */
|
|
252
|
+
deliver(input: NotificationConformanceDeliverInput): Promise<NotificationDeliveryOutcome>;
|
|
253
|
+
queueProviderResult(result: NotificationConformanceProviderResult): void;
|
|
254
|
+
/** Processes every due server reminder, stored send, and notice, earliest first. */
|
|
255
|
+
runScheduler(): Promise<void>;
|
|
256
|
+
/** The provider's registry entry; `undefined` when the provider has no remote delivery. */
|
|
257
|
+
readonly entry: NotificationDeliveryProviderEntry | undefined;
|
|
258
|
+
readonly consent: readonly NotificationConformanceStoredConsent[];
|
|
259
|
+
readonly endpoints: readonly NotificationConformanceEndpoint[];
|
|
260
|
+
readonly deliveries: readonly NotificationConformanceDelivery[];
|
|
261
|
+
readonly providerCalls: readonly NotificationConformanceProviderCall[];
|
|
262
|
+
readonly reminders: readonly NotificationConformanceServerReminder[];
|
|
263
|
+
/** Every player's listed notices, ordered by `dueBy` and then `kind`. */
|
|
264
|
+
readonly notices: readonly (NotificationNotice & {
|
|
265
|
+
readonly player: string;
|
|
266
|
+
})[];
|
|
267
|
+
readonly noticesGiven: readonly NotificationConformanceNoticeRecord[];
|
|
268
|
+
}
|
|
269
|
+
export interface CreateNotificationConformanceBackendInput {
|
|
270
|
+
/** The delivery provider's registry entry; `undefined` for a provider without remote delivery. */
|
|
271
|
+
readonly entry: NotificationDeliveryProviderEntry | undefined;
|
|
272
|
+
readonly catalog: readonly NotificationTopicDeclaration[];
|
|
273
|
+
readonly clock: Pick<NotificationConformanceClock, 'now'>;
|
|
274
|
+
readonly deployment?: NotificationDeployment;
|
|
275
|
+
/**
|
|
276
|
+
* The catalog's `notices` block as is: the topic consent notice pushes go
|
|
277
|
+
* out through, and their copy, which follows the catalog's rules
|
|
278
|
+
* (`notificationNoticeCopyIssues` with `defaultLocale`). Without it the
|
|
279
|
+
* scheduler sends no notice.
|
|
280
|
+
*/
|
|
281
|
+
readonly notices?: NotificationNoticeSettings;
|
|
282
|
+
readonly defaultLocale: string;
|
|
283
|
+
/** The recipient of a `deliver` without a `player`, and the owner of `registerInstallation`. */
|
|
284
|
+
readonly defaultPlayer: string;
|
|
285
|
+
/** The host's answer at send time; required for a server-readable provider. */
|
|
286
|
+
readonly readServerPermission?: (input: {
|
|
287
|
+
readonly player: string;
|
|
288
|
+
readonly topic: string;
|
|
289
|
+
}) => NotificationPermissionState | Promise<NotificationPermissionState>;
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* The fake backend (RFC "Server").
|
|
293
|
+
*
|
|
294
|
+
* - Sessions: each `authenticate` starts a new session on the fake
|
|
295
|
+
* installation, and `revokeSession` ends it and unbinds the endpoints it
|
|
296
|
+
* registered. Consent records, registrations, reminders, and notices belong
|
|
297
|
+
* to the session's player. The backend stamps each record's `at` with its
|
|
298
|
+
* own clock, strictly after the player's previous record, so a record's
|
|
299
|
+
* kind and time name it, and so does each notice that reports on it. It
|
|
300
|
+
* records a notice only while that notice is listed, so a retried or early
|
|
301
|
+
* call changes nothing, and a pushed notice the game gave while the push
|
|
302
|
+
* waited is not sent again. Notices go out through `notices.topic` only on
|
|
303
|
+
* a target that declares it; a target that delivers advertising under the
|
|
304
|
+
* Korean rules must declare it, as the catalog validator requires, and
|
|
305
|
+
* `notices.copy` must follow the catalog's rules.
|
|
306
|
+
* - Endpoints: a device endpoint is keyed by provider and installation, keeps
|
|
307
|
+
* the latest address, permission, and `lastRegisteredAt`, and moves with the
|
|
308
|
+
* installation to a newly authenticated player. An address is unique per
|
|
309
|
+
* provider, a dead address stays dead until the installation reports a new
|
|
310
|
+
* one, and an endpoint not refreshed for 60 days counts as missing. A
|
|
311
|
+
* platform-user provider resolves the authenticated identity and keeps the
|
|
312
|
+
* player's locale and zone.
|
|
313
|
+
* - Sends: a send is validated (input bounds, reserved prefixes), bound to
|
|
314
|
+
* its fingerprint (`NOTIFICATIONS_IDEMPOTENCY_CONFLICT` for another request),
|
|
315
|
+
* and answered from its key state: `delivered` with `alreadyProcessed`, the
|
|
316
|
+
* stored `deferred` result, or `in-flight`. A new key runs the gate for each
|
|
317
|
+
* recipient (every valid endpoint in installation-id order, each under
|
|
318
|
+
* `<key>:<installation id>`, or the platform identity) and calls the
|
|
319
|
+
* provider for the allowed ones. The send is `delivered` when any recipient
|
|
320
|
+
* accepted, else `in-flight`, `deferred` (the earliest), a provider's
|
|
321
|
+
* refusal, a policy refusal, and last an unresolved recipient. Only
|
|
322
|
+
* delivered, deferred, and in-flight rows are stored.
|
|
323
|
+
* - Provider answers come from `queueProviderResult`, `accepted` when none is
|
|
324
|
+
* queued: `unregistered` invalidates the endpoint; `rate-limited` defers by
|
|
325
|
+
* `retryAfterSeconds` (60 when absent), then by doubling delays without
|
|
326
|
+
* jitter, never past 24 hours after the first, and then gives up;
|
|
327
|
+
* `rejected` is not stored, and with
|
|
328
|
+
* `agreement` appends a `host-agreement` record of `denied` from the
|
|
329
|
+
* provider; `ambiguous` keeps the claim for the 300-second lease.
|
|
330
|
+
* - The scheduler processes due server reminders (as `reminder:` sends), due
|
|
331
|
+
* stored rows (deferred ones, and in-flight ones of scheduled sends or
|
|
332
|
+
* device endpoints after the lease, replayed through the gate without the
|
|
333
|
+
* key lookup), and, under the Korean rules, every unrecorded notice from the
|
|
334
|
+
* Asia/Seoul date seven days before it is due (as a `notice:` send of the
|
|
335
|
+
* notice topic in the variant of `notices.copy` it is listed with, filled
|
|
336
|
+
* with `{{consentDate}}`, and recorded with channel `push` once delivered),
|
|
337
|
+
* earliest first and within the registry entry's `rate`. Only a provider
|
|
338
|
+
* that can carry a notice (`notificationProviderCarriesNotices`) sends one,
|
|
339
|
+
* and never with its mapping's own text; a notice that cannot render is
|
|
340
|
+
* not tried again. A direct caller replays its own in-flight platform send
|
|
341
|
+
* after the lease. Keys are kept for 30 days after their last change.
|
|
342
|
+
* - The notice topic is the scheduler's: `deliver` and `setReminder` through
|
|
343
|
+
* it throw `NOTIFICATIONS_INVALID_INPUT`.
|
|
344
|
+
* - Setting or cancelling a reminder drops its sends' rows that still wait
|
|
345
|
+
* (deferred) or are in flight, so neither is replayed; a send the provider
|
|
346
|
+
* may already have accepted cannot be recalled. A send's limits count the
|
|
347
|
+
* player's other sends, never an earlier delivery of the same send to
|
|
348
|
+
* another endpoint. A send the gate cannot evaluate as asked (such as missing
|
|
349
|
+
* template data) answers `NOTIFICATIONS_INVALID_INPUT` directly and fails
|
|
350
|
+
* once when scheduled; any other failure, such as a host read that throws,
|
|
351
|
+
* is retried on the next run, and one failing item never stops the others.
|
|
352
|
+
*/
|
|
353
|
+
export declare function createNotificationConformanceBackend(input: CreateNotificationConformanceBackendInput): NotificationConformanceBackend;
|
|
354
|
+
export interface NotificationConformanceRuntime extends NotificationRuntime {
|
|
355
|
+
/**
|
|
356
|
+
* Subscribes to the provider and to authentication, compares the provider
|
|
357
|
+
* with its registry entry, and reconciles pending local reminders. Provider
|
|
358
|
+
* events are buffered for the game's first listener.
|
|
359
|
+
*/
|
|
360
|
+
start(): Promise<void>;
|
|
361
|
+
/** Drops listeners, buffered events, and both subscriptions. */
|
|
362
|
+
stop(): void;
|
|
363
|
+
/** Waits for event-driven work (records, registrations, local reminders) and rethrows its first failure. */
|
|
364
|
+
runUntilIdle(): Promise<void>;
|
|
365
|
+
}
|
|
366
|
+
/** What the runtime keeps across restarts. */
|
|
367
|
+
export interface NotificationConformanceRuntimeStorage {
|
|
368
|
+
/** The last registration the backend acknowledged. */
|
|
369
|
+
registration?: NotificationConformancePersistedRegistration;
|
|
370
|
+
/** Set by `unregister()`; `register()` or the next authentication clears it. */
|
|
371
|
+
unbound?: boolean;
|
|
372
|
+
}
|
|
373
|
+
export interface NotificationConformancePersistedRegistration {
|
|
374
|
+
readonly registration: NotificationEndpointRegistration;
|
|
375
|
+
readonly player: string;
|
|
376
|
+
readonly session: string;
|
|
377
|
+
readonly at: string;
|
|
378
|
+
}
|
|
379
|
+
export interface CreateNotificationConformanceRuntimeInput {
|
|
380
|
+
readonly provider: NotificationProvider;
|
|
381
|
+
readonly backend: NotificationConformanceBackendClient;
|
|
382
|
+
/** The provider's topics, each with this target's mapping. */
|
|
383
|
+
readonly catalog: readonly NotificationTopicDeclaration[];
|
|
384
|
+
readonly clock: Pick<NotificationConformanceClock, 'now'>;
|
|
385
|
+
/** The deployment's zone, for local quiet hours while the device has registered none. */
|
|
386
|
+
readonly timeZone: string;
|
|
387
|
+
readonly defaultLocale: string;
|
|
388
|
+
/** The device locale for local reminder copy; defaults to `defaultLocale`. */
|
|
389
|
+
readonly locale?: string;
|
|
390
|
+
/** Survives restarts; a fresh object when absent. */
|
|
391
|
+
readonly storage?: NotificationConformanceRuntimeStorage;
|
|
392
|
+
/**
|
|
393
|
+
* The backend's registry entry for this provider. When the provider's
|
|
394
|
+
* capabilities disagree with it at start, every call throws
|
|
395
|
+
* `NOTIFICATIONS_CONFIGURATION_REQUIRED`.
|
|
396
|
+
*/
|
|
397
|
+
readonly registryEntry?: NotificationDeliveryProviderEntry;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* The reference notifications runtime (RFC "Runtime"): the provider composed
|
|
401
|
+
* with the backend for the authenticated session.
|
|
402
|
+
*
|
|
403
|
+
* - Reads return the provider snapshot. A topic the host reads `unknown`
|
|
404
|
+
* takes the latest record, `sources` says which answered, and a
|
|
405
|
+
* game-rendered consent that is not opted in reads `prompt` until a
|
|
406
|
+
* decision is recorded and `denied` after it. A topic whose mapping needs no
|
|
407
|
+
* agreement reads `granted`. A read that observes a changed decided state
|
|
408
|
+
* records it, and every read still answers when the backend cannot.
|
|
409
|
+
* - Reads, requests, and revocations take each topic once, however often it
|
|
410
|
+
* is listed.
|
|
411
|
+
* - Requests throw `NOTIFICATIONS_USER_GESTURE_REQUIRED` without a gesture on
|
|
412
|
+
* a gesture-only provider. An unsupported provider answers `unavailable`, a
|
|
413
|
+
* provider in `action-required` answers that with its `requiredAction`, an
|
|
414
|
+
* undeclared or unmapped topic answers `unavailable`, a topic that needs no
|
|
415
|
+
* agreement answers `already-granted`, and a topic the host reads `blocked`
|
|
416
|
+
* answers `blocked`, all without reaching the host; every other topic
|
|
417
|
+
* reaches the prompt.
|
|
418
|
+
* - Records: decided states that differ from the latest record of their kind
|
|
419
|
+
* and scope, never `dismissed` or `action-required` answers. Device
|
|
420
|
+
* providers write none (the endpoint holds the permission), other app-scope
|
|
421
|
+
* providers write `delivery-permission`, and topic-scope providers write
|
|
422
|
+
* `host-agreement` with the host evidence, plus a `topic-preference` of
|
|
423
|
+
* `granted` when the player accepts a `requested` topic's agreement.
|
|
424
|
+
* Per-send acceptances are appended every time. A record belongs to the
|
|
425
|
+
* player signed in when the call or event began: one observed before
|
|
426
|
+
* authentication waits for it (only the latest per kind and scope), and one
|
|
427
|
+
* observed for a player waits for that player's session. A player action
|
|
428
|
+
* (`setTopicPreference`, `setAdvertisingConsent`) needs an authenticated
|
|
429
|
+
* player and throws `NOTIFICATIONS_TEMPORARILY_UNAVAILABLE` without one, as
|
|
430
|
+
* does every other backend call (a server reminder, `reminders.cancel`'s
|
|
431
|
+
* server part, and `notices`). A backend call belongs to the player signed
|
|
432
|
+
* in when it began and is refused when another player signed in since. A
|
|
433
|
+
* record the backend would refuse is dropped and its error surfaces.
|
|
434
|
+
* - Registrations: the provider's registration goes to the backend at
|
|
435
|
+
* authentication, on `register()`, on `endpoint-changed` (a rotated device
|
|
436
|
+
* address, or a platform user's changed locale or zone), and when a read
|
|
437
|
+
* or request, or a revocation, changes the device's decided permission; and
|
|
438
|
+
* at start and on reads once the last one the backend acknowledged is older
|
|
439
|
+
* than 7 days. An identical one for the same player and session is skipped
|
|
440
|
+
* for 7 days, except on `register()`. `unregister()` unbinds the
|
|
441
|
+
* installation from the player signed in when it was called and cancels the
|
|
442
|
+
* pending local reminders, even when the unbind fails, and nothing binds it
|
|
443
|
+
* again, across restarts, until `register()` or the next authentication.
|
|
444
|
+
* - Reminders: one key across both routes, and `cancel` removes it from each
|
|
445
|
+
* route that has it. Local ones pass the client gate steps (permission,
|
|
446
|
+
* then the topic preference from the backend, then quiet hours, which move
|
|
447
|
+
* them or refuse a topic that is not deferrable, then the topic's daily
|
|
448
|
+
* cap); server ones are checked and stored by the backend. Turning a topic off cancels its local
|
|
449
|
+
* reminders, a known permission other than granted cancels them, and start,
|
|
450
|
+
* authentication, and every backend read reconcile them with the catalog
|
|
451
|
+
* (a topic that no longer takes the local route loses its reminders), the
|
|
452
|
+
* latest preference, and permission.
|
|
453
|
+
* - Game listeners run after the runtime's own handling of an event; a
|
|
454
|
+
* listener that throws stops neither the others nor the runtime, and its
|
|
455
|
+
* error surfaces from `runUntilIdle`, as do failures of background work:
|
|
456
|
+
* the first one, or an `AggregateError` that counts them all. `stop()` and
|
|
457
|
+
* a later `start()` buffer events for the next first subscriber again.
|
|
458
|
+
*/
|
|
459
|
+
export declare function createNotificationConformanceRuntime(input: CreateNotificationConformanceRuntimeInput): NotificationConformanceRuntime;
|
|
460
|
+
export interface NotificationConformanceHarness {
|
|
461
|
+
readonly provider: NotificationProvider;
|
|
462
|
+
/**
|
|
463
|
+
* Handles `host` steps and counts prompts and local operations. A vector
|
|
464
|
+
* that scripts the host, or expects `prompts` or `local.operations` (or
|
|
465
|
+
* `local.pending` from a provider without `listLocal`), fails without it.
|
|
466
|
+
*/
|
|
467
|
+
readonly host?: NotificationConformanceHostActor;
|
|
468
|
+
/**
|
|
469
|
+
* The evidence schema this provider attaches to a host agreement. Vector
|
|
470
|
+
* expectations naming the fixture schema `fixture.host-agreement.v1` match
|
|
471
|
+
* it instead.
|
|
472
|
+
*/
|
|
473
|
+
readonly hostAgreementEvidenceSchema?: string;
|
|
474
|
+
}
|
|
475
|
+
export interface NotificationConformanceContext {
|
|
476
|
+
readonly vector: NotificationConformanceVector;
|
|
477
|
+
readonly profile: NotificationConformanceProfile;
|
|
478
|
+
readonly catalog: readonly NotificationTopicDeclaration[];
|
|
479
|
+
readonly clock: NotificationConformanceClock;
|
|
480
|
+
readonly player: string;
|
|
481
|
+
/** The device zone, which the provider registers. */
|
|
482
|
+
readonly timeZone: string;
|
|
483
|
+
}
|
|
484
|
+
export interface NotificationConformanceRunOptions {
|
|
485
|
+
/** Supplies a provider over the caller's fake SDK; defaults to the fake host's fixture provider. */
|
|
486
|
+
readonly createProvider?: (context: NotificationConformanceContext) => NotificationConformanceHarness | Promise<NotificationConformanceHarness>;
|
|
487
|
+
/**
|
|
488
|
+
* The backend's and the runtime's default locale;
|
|
489
|
+
* `notificationConformanceDefaultLocale` when absent. A vector's
|
|
490
|
+
* `notices.copy` must include it, or the vector fails at `backend`.
|
|
491
|
+
*/
|
|
492
|
+
readonly defaultLocale?: string;
|
|
493
|
+
}
|
|
494
|
+
export interface NotificationConformanceFailure {
|
|
495
|
+
readonly path: string;
|
|
496
|
+
readonly expected: unknown;
|
|
497
|
+
readonly actual: unknown;
|
|
498
|
+
}
|
|
499
|
+
export type NotificationConformanceVectorStatus = 'passed' | 'failed' | 'skipped';
|
|
500
|
+
export interface NotificationConformanceVectorResult {
|
|
501
|
+
readonly vector: string;
|
|
502
|
+
/** `skipped` vectors did not run; `passed` is false and `reason` says why. */
|
|
503
|
+
readonly status: NotificationConformanceVectorStatus;
|
|
504
|
+
readonly passed: boolean;
|
|
505
|
+
readonly failures: readonly NotificationConformanceFailure[];
|
|
506
|
+
readonly reason?: string;
|
|
507
|
+
}
|
|
508
|
+
export declare function createNotificationConformanceHarness(context: NotificationConformanceContext): NotificationConformanceHarness;
|
|
509
|
+
/** Why `vector` does not apply to a provider with `capabilities`, or `undefined` when it runs. */
|
|
510
|
+
export declare function notificationConformanceSkipReason(vector: NotificationConformanceVector, capabilities: NotificationCapabilities): string | undefined;
|
|
511
|
+
/**
|
|
512
|
+
* Runs one vector: reads the provider's capabilities and skips the vector when
|
|
513
|
+
* they do not satisfy `appliesTo`, executes every step (a step's `returns` is
|
|
514
|
+
* matched as a subset, its `throws` by `code`), lets the runtime settle after
|
|
515
|
+
* each, then matches `expect`. Lists match exactly in length and order with
|
|
516
|
+
* only the listed fields of each item asserted, a field set to `null` asserts
|
|
517
|
+
* that it is absent, and timestamps compare as instants. Any unscripted
|
|
518
|
+
* operation fails the vector at the step that needed it.
|
|
519
|
+
*
|
|
520
|
+
* The backend's registry entry is derived from the provider's own
|
|
521
|
+
* capabilities and the profile's limits. A `setReminder` step that expects a
|
|
522
|
+
* local `permission-required` also calls the provider's `scheduleLocal`
|
|
523
|
+
* directly: it must answer `permission-required` without prompting or
|
|
524
|
+
* scheduling. With `options.createProvider`, the fixture provider id and the
|
|
525
|
+
* fixture agreement evidence schema in `returns` and `expect` match the
|
|
526
|
+
* provider under test's own id and `hostAgreementEvidenceSchema`.
|
|
527
|
+
*/
|
|
528
|
+
export declare function runNotificationConformanceVector(vector: NotificationConformanceVector, options?: NotificationConformanceRunOptions): Promise<NotificationConformanceVectorResult>;
|
|
529
|
+
export declare function runNotificationConformanceVectors(vectors: readonly NotificationConformanceVector[], options?: NotificationConformanceRunOptions): Promise<readonly NotificationConformanceVectorResult[]>;
|