@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.
@@ -0,0 +1,204 @@
1
+ import type { IsoTimestamp, NonEmptyString, PositiveQuantity } from './commerce-contract.js';
2
+ import type { PlatformEvidenceEnvelope, PlatformProviderAvailability } from './index.js';
3
+ /** Open topic id declared by the game's topic catalog. */
4
+ export type NotificationTopicId = string & Record<never, never>;
5
+ /** What a topic is for; it decides the consent a send needs (RFC "Topics and purposes"). */
6
+ export type NotificationPurpose = 'service' | 'requested' | 'advertising';
7
+ /** `remote` is a server-sent push, `local` a device-scheduled notification. */
8
+ export type NotificationChannel = 'remote' | 'local';
9
+ /** Open provider id. Reference ids: 'fcm', 'apps-in-toss', 'devvit'. */
10
+ export type NotificationProviderId = NonEmptyString;
11
+ /** The player action a host needs before it can prompt. */
12
+ export type NotificationRequiredAction = 'login' | 'host-settings';
13
+ export type NotificationPermissionState = 'granted' | 'prompt'
14
+ /** Android after one denial: explain, then the dialog may show once more. */
15
+ | 'prompt-with-rationale'
16
+ /** Refused; this host lets the game ask again later. */
17
+ | 'denied'
18
+ /** Only OS or host settings can change it. */
19
+ | 'blocked'
20
+ /** The host gives no read; the server record decides. */
21
+ | 'unknown'
22
+ /** The host needs a player action first: logging in on Devvit. */
23
+ | 'action-required' | 'unsupported';
24
+ export interface NotificationPermissionSnapshot {
25
+ /** Present when `permission.scope` is 'app'. */
26
+ readonly app?: NotificationPermissionState;
27
+ readonly topics: Readonly<Record<NotificationTopicId, NotificationPermissionState>>;
28
+ /** Present with an `action-required` state. */
29
+ readonly requiredAction?: NotificationRequiredAction;
30
+ }
31
+ /**
32
+ * What one `requestPermission` call produced for a topic: the host's answer
33
+ * when the prompt ran ('granted', 'already-granted', 'denied', and
34
+ * 'dismissed' when it closed without an answer), 'blocked' when the host
35
+ * refused for good or the call did not reach the prompt because the host read
36
+ * was already blocked, 'action-required' when the host needs a player action
37
+ * before it can prompt (`requiredAction` names it), and 'unavailable' when the
38
+ * provider is unsupported or the topic has no mapping for it. The snapshot
39
+ * carries the resulting state, so a second Android denial reports outcome
40
+ * 'denied' with snapshot 'blocked'.
41
+ */
42
+ export type NotificationPermissionOutcome = 'granted' | 'already-granted' | 'denied' | 'blocked' | 'dismissed' | 'action-required' | 'unavailable';
43
+ export interface NotificationPermissionResult {
44
+ readonly snapshot: NotificationPermissionSnapshot;
45
+ readonly outcomes: Readonly<Record<NotificationTopicId, NotificationPermissionOutcome>>;
46
+ readonly requiredAction?: NotificationRequiredAction;
47
+ /** Host decision for the server record, for example an Apps in Toss agreement event. */
48
+ readonly evidence?: PlatformEvidenceEnvelope;
49
+ }
50
+ export type NotificationPermissionScope = 'app' | 'topic';
51
+ export type NotificationPermissionPrompt = 'os' | 'host' | 'game';
52
+ export type NotificationPermissionAllowance = 'persistent' | 'per-send';
53
+ export type NotificationRemoteAddressing = 'device' | 'platform-user' | 'none';
54
+ export type NotificationRemoteContent = 'free-form' | 'host-template';
55
+ export type NotificationOpenPath = 'payload' | 'landing-url' | 'host-link' | 'none';
56
+ export interface NotificationPermissionCapabilities {
57
+ /** Where delivery permission lives: one for the app, or one per topic. */
58
+ readonly scope: NotificationPermissionScope;
59
+ /** Who renders the consent UI. */
60
+ readonly prompt: NotificationPermissionPrompt;
61
+ /** Whether the client can read the current state without prompting. */
62
+ readonly readable: boolean;
63
+ /** Whether the backend can read the current state at send time (Devvit `isOptedIn`). */
64
+ readonly serverReadable: boolean;
65
+ /** 'per-send': one acceptance allows one delivery (WeChat one-time subscriptions). */
66
+ readonly allowance: NotificationPermissionAllowance;
67
+ readonly userGesture: boolean;
68
+ /**
69
+ * How many denials the host accepts before only settings can change the
70
+ * answer: iOS 1, Android 13+ 2. A dismissed dialog is not a denial. Absent
71
+ * when the host can always ask again (Apps in Toss, Devvit, WeChat).
72
+ */
73
+ readonly promptLimit?: PositiveQuantity;
74
+ readonly maxTopicsPerPrompt?: PositiveQuantity;
75
+ /** Whether `openSettings` can open the OS or host notification settings. */
76
+ readonly settings: boolean;
77
+ }
78
+ export interface NotificationRemoteCapabilities {
79
+ readonly supported: boolean;
80
+ /** Purposes the host lets the server deliver. */
81
+ readonly purposes: readonly NotificationPurpose[];
82
+ /** `'none'` exactly when `supported` is false. */
83
+ readonly addressing: NotificationRemoteAddressing;
84
+ readonly content: NotificationRemoteContent;
85
+ }
86
+ /** Local reminders are always inexact, so the capability carries no timing. */
87
+ export interface NotificationLocalCapabilities {
88
+ readonly supported: boolean;
89
+ /** Pending-notification ceiling the provider must stay under (iOS: 64). */
90
+ readonly maxPending?: PositiveQuantity;
91
+ }
92
+ /**
93
+ * Notifications v2 capability declaration. A provider returns a fresh object
94
+ * on every read; code branches on these declarations, never on a provider id
95
+ * or target name. Release gates such as Devvit's `approval-required` stay in
96
+ * the target config's `integrations.notifications` state.
97
+ */
98
+ export interface NotificationCapabilities {
99
+ readonly availability: PlatformProviderAvailability;
100
+ readonly permission: NotificationPermissionCapabilities;
101
+ readonly remote: NotificationRemoteCapabilities;
102
+ readonly local: NotificationLocalCapabilities;
103
+ /** How an open reaches the game. */
104
+ readonly open: NotificationOpenPath;
105
+ }
106
+ export type NotificationEndpointAddress = {
107
+ readonly kind: 'fcm-token' | 'fcm-installation';
108
+ readonly value: NonEmptyString;
109
+ readonly environment?: 'production' | 'sandbox';
110
+ /** The device's app-scope permission when this registration was sent. */
111
+ readonly permission: NotificationPermissionState;
112
+ }
113
+ /** The server resolves the recipient from the authenticated player. */
114
+ | {
115
+ readonly kind: 'platform-user';
116
+ };
117
+ /**
118
+ * Where the server reaches this installation or player. The installation id
119
+ * is the authenticated session's, never a field of the registration.
120
+ */
121
+ export interface NotificationEndpointRegistration {
122
+ readonly provider: NotificationProviderId;
123
+ readonly address: NotificationEndpointAddress;
124
+ readonly locale?: NonEmptyString;
125
+ /** IANA zone, used for a topic's quiet hours. */
126
+ readonly timeZone?: NonEmptyString;
127
+ }
128
+ export interface LocalNotificationRequest {
129
+ /** Stable logical key; the provider derives its native id from it. */
130
+ readonly key: NonEmptyString;
131
+ readonly topic: NotificationTopicId;
132
+ /** RFC 3339 with an explicit offset. */
133
+ readonly fireAt: IsoTimestamp;
134
+ readonly title: string;
135
+ readonly body: string;
136
+ /** The topic's root-relative route. Advertising topics never schedule locally. */
137
+ readonly route?: string;
138
+ }
139
+ /** A pending local notification as `listLocal` reports it. */
140
+ export interface LocalNotificationEntry {
141
+ readonly key: NonEmptyString;
142
+ readonly topic: NotificationTopicId;
143
+ readonly fireAt: IsoTimestamp;
144
+ }
145
+ export type LocalScheduleResult = {
146
+ readonly status: 'scheduled' | 'replaced';
147
+ } | {
148
+ readonly status: 'permission-required' | 'capacity-exceeded' | 'unsupported';
149
+ };
150
+ export type NotificationEvent = {
151
+ readonly type: 'opened';
152
+ readonly channel: NotificationChannel;
153
+ readonly source: 'cold' | 'warm';
154
+ readonly topic?: NotificationTopicId;
155
+ /**
156
+ * Untrusted, like `data`: the router opens it only when
157
+ * `isRootRelativeNotificationRoute` accepts it.
158
+ */
159
+ readonly route?: string;
160
+ /** Untrusted routing data. Never a grant. */
161
+ readonly data?: Readonly<Record<string, string>>;
162
+ /** Host referrer when known, for example Apps in Toss 'push' or 'inbox'. */
163
+ readonly referrer?: string;
164
+ } | {
165
+ readonly type: 'received';
166
+ readonly channel: NotificationChannel;
167
+ readonly topic?: NotificationTopicId;
168
+ } | {
169
+ readonly type: 'endpoint-changed';
170
+ readonly registration: NotificationEndpointRegistration;
171
+ } | {
172
+ readonly type: 'permission-changed';
173
+ readonly snapshot: NotificationPermissionSnapshot;
174
+ };
175
+ export interface NotificationTopicsInput {
176
+ readonly topics: readonly NotificationTopicId[];
177
+ }
178
+ export interface CancelLocalNotificationsInput {
179
+ readonly keys: readonly NonEmptyString[];
180
+ }
181
+ export type NotificationSettingsResult = 'opened' | 'unavailable';
182
+ /**
183
+ * A target's notifications provider. Rules every provider follows (RFC
184
+ * "Client contract"): reads are fresh, events emitted before the first
185
+ * `subscribe` go to that first subscriber exactly once, only
186
+ * `requestPermission` may show a consent UI, an unsupported provider answers
187
+ * instead of throwing, local keys map to stable native ids, and failures use
188
+ * the `NOTIFICATIONS_*` `PlatformOperationError` codes.
189
+ */
190
+ export interface NotificationProvider {
191
+ readonly provider: NotificationProviderId;
192
+ getCapabilities(): Promise<NotificationCapabilities>;
193
+ getPermission(input: NotificationTopicsInput): Promise<NotificationPermissionSnapshot>;
194
+ requestPermission(input: NotificationTopicsInput): Promise<NotificationPermissionResult>;
195
+ /** Exists exactly when `permission.prompt` is 'game': withdraws the game-rendered consent. */
196
+ revokePermission?(input: NotificationTopicsInput): Promise<NotificationPermissionResult>;
197
+ /** Remote providers: the address the server should send to. */
198
+ register?(): Promise<NotificationEndpointRegistration | null>;
199
+ scheduleLocal?(input: LocalNotificationRequest): Promise<LocalScheduleResult>;
200
+ cancelLocal?(input: CancelLocalNotificationsInput): Promise<void>;
201
+ listLocal?(): Promise<readonly LocalNotificationEntry[]>;
202
+ openSettings?(): Promise<NotificationSettingsResult>;
203
+ subscribe(listener: (event: NotificationEvent) => void): () => void;
204
+ }
@@ -0,0 +1 @@
1
+ export {};