@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 { devvitHostCodeCharacters, devvitNotificationProviderId, } from './notifications-contract.js';
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 : hostCodePattern.exec(message)?.[1];
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 { type BridgeRequest, type BridgeResponse } from '@mpgd/bridge';
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"): each method's payload and answer are checked with the
99
- * `@mpgd/bridge/notifications` validators. A payload the validator rejects
100
- * answers `NOTIFICATIONS_INVALID_INPUT`; a method the profile lacks (the
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. Devvit has no server-to-webview event channel,
106
- * so the bridge carries no notifications events.
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 { createBridgeError, isBridgeNotificationsV2Method, } from '@mpgd/bridge';
2
- import { assertBridgeNotificationsV2Payload, assertBridgeNotificationsV2Result, } from '@mpgd/bridge/notifications';
3
- import { PlatformOperationError } from '@mpgd/platform';
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"): each method's payload and answer are checked with the
163
- * `@mpgd/bridge/notifications` validators. A payload the validator rejects
164
- * answers `NOTIFICATIONS_INVALID_INPUT`; a method the profile lacks (the
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. Devvit has no server-to-webview event channel,
170
- * so the bridge carries no notifications events.
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 provider = createDevvitServerNotificationProvider(input);
174
- const onError = input.onError ?? ((error, context) => {
175
- if (error instanceof NotificationError && error.code === 'NOTIFICATIONS_UNSUPPORTED') {
176
- return;
177
- }
178
- console.warn(`devvit notifications ${context.method} failed`, error);
179
- });
180
- const serve = async (method, payload) => {
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
- class InvalidPayloadError extends Error {
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.4",
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.14.1",
58
- "@mpgd/bridge": "0.14.3",
59
- "@mpgd/platform": "0.27.1",
60
- "@mpgd/game-services": "0.27.1"
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",