@mpgd/adapter-devvit 0.13.4 → 0.13.6
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.
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { assertBridgeNotificationsV2Result, } from '@mpgd/bridge/notifications';
|
|
1
|
+
import { assertBridgeNotificationsV2Result, readBridgeNotificationsHostCode, } from '@mpgd/bridge/notifications';
|
|
2
2
|
import { isNotificationErrorCode, isValidNotificationTimeZone, NotificationError, notificationsProtocol, toNotificationSubscriptionAdapter, } from '@mpgd/platform/notifications';
|
|
3
|
-
import {
|
|
3
|
+
import { devvitNotificationProviderId } from './notifications-contract.js';
|
|
4
4
|
/**
|
|
5
5
|
* The Devvit profile's provider: reads, the opt-in request, the opt-out
|
|
6
6
|
* (`revokePermission`, because the game renders the consent), and the
|
|
@@ -108,7 +108,6 @@ function defaultLocale() {
|
|
|
108
108
|
const navigatorLike = globalThis.navigator;
|
|
109
109
|
return typeof navigatorLike?.language === 'string' ? navigatorLike.language : undefined;
|
|
110
110
|
}
|
|
111
|
-
const hostCodePattern = new RegExp(`\\(host code (${devvitHostCodeCharacters})\\)$`, 'u');
|
|
112
111
|
function toNotificationError(error) {
|
|
113
112
|
if (error instanceof NotificationError) {
|
|
114
113
|
return error;
|
|
@@ -119,7 +118,7 @@ function toNotificationError(error) {
|
|
|
119
118
|
const message = typeof failure.message === 'string' ? failure.message : undefined;
|
|
120
119
|
const explicit = typeof failure.retryable === 'boolean' ? { retryable: failure.retryable } : {};
|
|
121
120
|
if (isNotificationErrorCode(failure.code)) {
|
|
122
|
-
const hostCode = message === undefined ? undefined :
|
|
121
|
+
const hostCode = message === undefined ? undefined : readBridgeNotificationsHostCode(message);
|
|
123
122
|
return new NotificationError(failure.code, {
|
|
124
123
|
...(message === undefined ? {} : { message }),
|
|
125
124
|
...explicit,
|
|
@@ -46,7 +46,9 @@ export declare const devvitNotificationsPerAppPerDay = 25000;
|
|
|
46
46
|
export declare function devvitNotificationTextLength(text: string): number;
|
|
47
47
|
/**
|
|
48
48
|
* The characters of a Devvit host code. The server bridge names a host code
|
|
49
|
-
* in an error message as `(host code <code>)`, and the webview reads it back
|
|
49
|
+
* in an error message as `(host code <code>)`, and the webview reads it back,
|
|
50
|
+
* both through `@mpgd/bridge/notifications` (`createNotificationsV2BridgeHandler`
|
|
51
|
+
* and `readBridgeNotificationsHostCode`), whose pattern this equals.
|
|
50
52
|
*/
|
|
51
53
|
export declare const devvitHostCodeCharacters = "[A-Za-z0-9_.:-]{1,128}";
|
|
52
54
|
/** A post (`t3_`) or comment (`t1_`) id, the only links `enqueue` accepts. */
|
|
@@ -47,7 +47,9 @@ export function devvitNotificationTextLength(text) {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* The characters of a Devvit host code. The server bridge names a host code
|
|
50
|
-
* in an error message as `(host code <code>)`, and the webview reads it back
|
|
50
|
+
* in an error message as `(host code <code>)`, and the webview reads it back,
|
|
51
|
+
* both through `@mpgd/bridge/notifications` (`createNotificationsV2BridgeHandler`
|
|
52
|
+
* and `readBridgeNotificationsHostCode`), whose pattern this equals.
|
|
51
53
|
*/
|
|
52
54
|
export const devvitHostCodeCharacters = '[A-Za-z0-9_.:-]{1,128}';
|
|
53
55
|
/** A post (`t3_`) or comment (`t1_`) id, the only links `enqueue` accepts. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BridgeRequest, BridgeResponse } from '@mpgd/bridge';
|
|
2
2
|
import { type NotificationProvider } from '@mpgd/platform/notifications';
|
|
3
3
|
/**
|
|
4
4
|
* The game-rendered consent of the Devvit profile (notifications v2 RFC,
|
|
@@ -95,14 +95,17 @@ export interface DevvitNotificationsV2BridgeHandler {
|
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* The server bridge side of notifications v2 on Devvit (RFC "Bridge and
|
|
98
|
-
* versioning"):
|
|
99
|
-
* `@mpgd/bridge/notifications`
|
|
100
|
-
*
|
|
98
|
+
* versioning"): `createNotificationsV2BridgeHandler` from
|
|
99
|
+
* `@mpgd/bridge/notifications` over the Devvit server provider. Each method's
|
|
100
|
+
* payload and answer are checked with the `@mpgd/bridge/notifications`
|
|
101
|
+
* validators. A payload the validator rejects answers
|
|
102
|
+
* `NOTIFICATIONS_INVALID_INPUT`; a method the profile lacks (the
|
|
101
103
|
* local-notification methods) `NOTIFICATIONS_UNSUPPORTED`; `openSettings`
|
|
102
104
|
* answers `unavailable`, because Devvit has no settings link; an answer the
|
|
103
105
|
* contract rejects `NOTIFICATIONS_CONFIGURATION_REQUIRED`. A thrown
|
|
104
106
|
* `NotificationError` crosses as `{ code, message, retryable }` with its host
|
|
105
|
-
* code named in the message
|
|
106
|
-
*
|
|
107
|
+
* code named in the message, and an unexpected failure's text stays on the
|
|
108
|
+
* server. Devvit has no server-to-webview event channel, so the bridge
|
|
109
|
+
* carries no notifications events.
|
|
107
110
|
*/
|
|
108
111
|
export declare function createDevvitNotificationsV2BridgeHandler(input: CreateDevvitNotificationsV2BridgeHandlerInput): DevvitNotificationsV2BridgeHandler;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { isNotificationErrorCode, NotificationError, notificationsProtocolVersion, } from '@mpgd/platform/notifications';
|
|
5
|
-
import { createDevvitNotificationCapabilities, devvitHostCodeCharacters, devvitNotificationProviderId, devvitUserIdPattern, } from '../notifications-contract.js';
|
|
1
|
+
import { createNotificationsV2BridgeHandler } from '@mpgd/bridge/notifications';
|
|
2
|
+
import { NotificationError, } from '@mpgd/platform/notifications';
|
|
3
|
+
import { createDevvitNotificationCapabilities, devvitNotificationProviderId, devvitUserIdPattern, } from '../notifications-contract.js';
|
|
6
4
|
/** Host codes of the failures this provider normalizes. */
|
|
7
5
|
export const devvitNotificationHostCodes = Object.freeze({
|
|
8
6
|
optInRefused: 'DEVVIT_OPT_IN_REFUSED',
|
|
@@ -159,119 +157,31 @@ export function createDevvitServerNotificationProvider(input) {
|
|
|
159
157
|
}
|
|
160
158
|
/**
|
|
161
159
|
* The server bridge side of notifications v2 on Devvit (RFC "Bridge and
|
|
162
|
-
* versioning"):
|
|
163
|
-
* `@mpgd/bridge/notifications`
|
|
164
|
-
*
|
|
160
|
+
* versioning"): `createNotificationsV2BridgeHandler` from
|
|
161
|
+
* `@mpgd/bridge/notifications` over the Devvit server provider. Each method's
|
|
162
|
+
* payload and answer are checked with the `@mpgd/bridge/notifications`
|
|
163
|
+
* validators. A payload the validator rejects answers
|
|
164
|
+
* `NOTIFICATIONS_INVALID_INPUT`; a method the profile lacks (the
|
|
165
165
|
* local-notification methods) `NOTIFICATIONS_UNSUPPORTED`; `openSettings`
|
|
166
166
|
* answers `unavailable`, because Devvit has no settings link; an answer the
|
|
167
167
|
* contract rejects `NOTIFICATIONS_CONFIGURATION_REQUIRED`. A thrown
|
|
168
168
|
* `NotificationError` crosses as `{ code, message, retryable }` with its host
|
|
169
|
-
* code named in the message
|
|
170
|
-
*
|
|
169
|
+
* code named in the message, and an unexpected failure's text stays on the
|
|
170
|
+
* server. Devvit has no server-to-webview event channel, so the bridge
|
|
171
|
+
* carries no notifications events.
|
|
171
172
|
*/
|
|
172
173
|
export function createDevvitNotificationsV2BridgeHandler(input) {
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
switch (method) {
|
|
182
|
-
case 'notifications.v2.getCapabilities':
|
|
183
|
-
validPayload(method, payload);
|
|
184
|
-
return {
|
|
185
|
-
protocolVersion: notificationsProtocolVersion,
|
|
186
|
-
provider: provider.provider,
|
|
187
|
-
capabilities: await provider.getCapabilities(),
|
|
188
|
-
};
|
|
189
|
-
case 'notifications.v2.getPermission':
|
|
190
|
-
return provider.getPermission(validPayload(method, payload));
|
|
191
|
-
case 'notifications.v2.requestPermission':
|
|
192
|
-
return provider.requestPermission(validPayload(method, payload));
|
|
193
|
-
case 'notifications.v2.revokePermission':
|
|
194
|
-
return provider.revokePermission
|
|
195
|
-
.call(provider, validPayload(method, payload));
|
|
196
|
-
case 'notifications.v2.register':
|
|
197
|
-
validPayload(method, payload);
|
|
198
|
-
return { registration: (await provider.register?.()) ?? null };
|
|
199
|
-
case 'notifications.v2.openSettings':
|
|
200
|
-
validPayload(method, payload);
|
|
201
|
-
return { status: 'unavailable' };
|
|
202
|
-
case 'notifications.v2.scheduleLocal':
|
|
203
|
-
case 'notifications.v2.cancelLocal':
|
|
204
|
-
case 'notifications.v2.listLocal':
|
|
205
|
-
validPayload(method, payload);
|
|
206
|
-
throw new NotificationError('NOTIFICATIONS_UNSUPPORTED', {
|
|
207
|
-
message: `Devvit has no local notifications (${method}).`,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
return {
|
|
212
|
-
async request(request) {
|
|
213
|
-
if (!isBridgeNotificationsV2Method(request.method)) {
|
|
214
|
-
return createBridgeError(request.id, 'UNSUPPORTED_METHOD', `${request.method} is not a notifications v2 method.`);
|
|
215
|
-
}
|
|
216
|
-
const method = request.method;
|
|
217
|
-
try {
|
|
218
|
-
const data = await serve(method, request.payload);
|
|
219
|
-
return { id: request.id, ok: true, data: validResult(method, data) };
|
|
220
|
-
}
|
|
221
|
-
catch (error) {
|
|
222
|
-
if (!(error instanceof InvalidPayloadError)) {
|
|
223
|
-
try {
|
|
224
|
-
const logged = onError(error, { method });
|
|
225
|
-
if (logged instanceof Promise) {
|
|
226
|
-
logged.catch(() => { });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
catch {
|
|
230
|
-
// A failing log never changes the answer.
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return toBridgeError(request, error);
|
|
174
|
+
const handler = createNotificationsV2BridgeHandler({
|
|
175
|
+
provider: createDevvitServerNotificationProvider(input),
|
|
176
|
+
label: 'Devvit',
|
|
177
|
+
// An unexpected failure's text stays on the server.
|
|
178
|
+
unexpectedFailureMessage: (method) => `${method} failed on the Devvit server.`,
|
|
179
|
+
onError: input.onError ?? ((error, context) => {
|
|
180
|
+
if (error instanceof NotificationError && error.code === 'NOTIFICATIONS_UNSUPPORTED') {
|
|
181
|
+
return;
|
|
234
182
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
name = 'InvalidPayloadError';
|
|
240
|
-
}
|
|
241
|
-
function validPayload(method, payload) {
|
|
242
|
-
try {
|
|
243
|
-
return assertBridgeNotificationsV2Payload(method, payload);
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
throw new InvalidPayloadError(`${method} payload is invalid: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
function validResult(method, data) {
|
|
250
|
-
try {
|
|
251
|
-
return assertBridgeNotificationsV2Result(method, data);
|
|
252
|
-
}
|
|
253
|
-
catch (error) {
|
|
254
|
-
throw new NotificationError('NOTIFICATIONS_CONFIGURATION_REQUIRED', {
|
|
255
|
-
message: `The Devvit notifications provider answered ${method} with invalid data: ${error instanceof Error ? error.message : String(error)}`,
|
|
256
|
-
cause: error,
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
const hostCodeValue = new RegExp(`^${devvitHostCodeCharacters}$`, 'u');
|
|
261
|
-
function toBridgeError(request, error) {
|
|
262
|
-
if (error instanceof InvalidPayloadError) {
|
|
263
|
-
return createBridgeError(request.id, 'NOTIFICATIONS_INVALID_INPUT', error.message);
|
|
264
|
-
}
|
|
265
|
-
if (error instanceof NotificationError) {
|
|
266
|
-
return createBridgeError(request.id, error.code, error.hostCode === undefined || !hostCodeValue.test(error.hostCode)
|
|
267
|
-
? error.message
|
|
268
|
-
: `${error.message} (host code ${error.hostCode})`, error.retryable);
|
|
269
|
-
}
|
|
270
|
-
if (error instanceof PlatformOperationError) {
|
|
271
|
-
return isNotificationErrorCode(error.code)
|
|
272
|
-
? createBridgeError(request.id, error.code, error.message, error.retryable)
|
|
273
|
-
: createBridgeError(request.id, 'NOTIFICATIONS_TEMPORARILY_UNAVAILABLE', `${request.method} failed: ${error.message}`, error.retryable);
|
|
274
|
-
}
|
|
275
|
-
// An unexpected failure's text stays on the server.
|
|
276
|
-
return createBridgeError(request.id, 'NOTIFICATIONS_TEMPORARILY_UNAVAILABLE', `${request.method} failed on the Devvit server.`, true);
|
|
183
|
+
console.warn(`devvit notifications ${context.method} failed`, error);
|
|
184
|
+
}),
|
|
185
|
+
});
|
|
186
|
+
return { request: (request) => handler.request(request) };
|
|
277
187
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/adapter-devvit",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.6",
|
|
4
4
|
"description": "Reddit Devvit Web platform adapter for mpgd games.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
"dist"
|
|
55
55
|
],
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@devvit/web": "0.
|
|
58
|
-
"@mpgd/bridge": "0.
|
|
59
|
-
"@mpgd/platform": "0.27.
|
|
60
|
-
"@mpgd/game-services": "0.
|
|
57
|
+
"@devvit/web": "0.15.0",
|
|
58
|
+
"@mpgd/bridge": "0.15.0",
|
|
59
|
+
"@mpgd/platform": "0.27.2",
|
|
60
|
+
"@mpgd/game-services": "0.28.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@types/node": "^24.0.0",
|