@mpgd/adapter-devvit 0.9.19 → 0.10.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/commerce-contract.d.ts +85 -0
- package/dist/commerce-contract.js +145 -0
- package/dist/commerce-provider.d.ts +93 -0
- package/dist/commerce-provider.js +472 -0
- package/dist/index.d.ts +41 -2
- package/dist/index.js +154 -3
- package/dist/payments.d.ts +17 -0
- package/dist/payments.js +3 -2
- package/dist/server/commerce-bridge.d.ts +111 -0
- package/dist/server/commerce-bridge.js +402 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
import { createBridgeError, } from '@mpgd/bridge';
|
|
2
|
+
import { PlatformOperationError } from '@mpgd/platform';
|
|
3
|
+
import { assertCommerceBridgeFetchProductsPayload, assertCommerceBridgeFetchProductsResult, assertCommerceBridgeFinishTransactionPayload, assertCommerceBridgeGetAvailablePurchasesPayload, assertCommerceBridgeGetAvailablePurchasesResult, assertCommerceBridgeGetCapabilitiesPayload, assertCommerceBridgeGetCapabilitiesResult, assertCommerceBridgeRequestPurchasePayload, assertCommerceBridgeRestorePurchasesPayload, assertCommerceBridgeRestorePurchasesResult, assertStoreProduct, CommerceError, commerceErrorInfoOf, commerceProtocolVersion, } from '@mpgd/platform/commerce';
|
|
4
|
+
import { createDevvitCommerceCapabilities, createDevvitStorePurchase, devvitCommerceStoreId, maximumDevvitAvailableOrders, readDevvitIdentifier, readDevvitOrderTime, } from '../commerce-contract.js';
|
|
5
|
+
/**
|
|
6
|
+
* Host side of commerce v2 for Devvit: the server bridge serves the six
|
|
7
|
+
* `commerce.v2.*` methods from the Devvit `payments` plugin
|
|
8
|
+
* (`@devvit/payments/server`, re-exported by `@devvit/web/server`). The
|
|
9
|
+
* plugin runs in the request context of the authenticated Reddit user:
|
|
10
|
+
* Reddit documents server-side `payments.getOrders()` as the current user's
|
|
11
|
+
* orders, newest first (Devvit docs, "Manage Payments", "Check orders"),
|
|
12
|
+
* which the fulfillment handler's own user binding backs. The checkout is a webview
|
|
13
|
+
* effect, so `requestPurchase` is answered by the client provider, never
|
|
14
|
+
* here. This module imports no Devvit SDK; the plugin surface it needs is
|
|
15
|
+
* the structural `DevvitServerPaymentsLike`.
|
|
16
|
+
*/
|
|
17
|
+
/** Bridge error for a commerce v2 payload the typia assert rejected. */
|
|
18
|
+
export const devvitCommerceV2InvalidPayloadBridgeErrorCode = 'COMMERCE_V2_INVALID_PAYLOAD';
|
|
19
|
+
/** Bridge error for `finishTransaction`: the Devvit profile has nothing to finish. */
|
|
20
|
+
export const devvitCommerceV2MethodUnsupportedBridgeErrorCode = 'COMMERCE_V2_METHOD_UNSUPPORTED';
|
|
21
|
+
/** Bridge error for `requestPurchase` reaching the server: the checkout opens in the webview. */
|
|
22
|
+
export const devvitCommerceCheckoutClientOnlyBridgeErrorCode = 'DEVVIT_COMMERCE_CHECKOUT_CLIENT_ONLY';
|
|
23
|
+
/** Bridge error when the payments plugin fails; retryable. */
|
|
24
|
+
export const devvitPaymentsUnavailableBridgeErrorCode = 'DEVVIT_PAYMENTS_UNAVAILABLE';
|
|
25
|
+
/** `OrderStatus.ORDER_STATUS_PAID` of `@devvit/protos`, and its handler-type spelling. */
|
|
26
|
+
const paidOrderStatuses = new Set([
|
|
27
|
+
3,
|
|
28
|
+
'PAID',
|
|
29
|
+
'ORDER_STATUS_PAID',
|
|
30
|
+
]);
|
|
31
|
+
/** `OrderStatus.ORDER_STATUS_DELIVERED` of `@devvit/protos`, and its handler-type spelling. */
|
|
32
|
+
const deliveredOrderStatuses = new Set([
|
|
33
|
+
4,
|
|
34
|
+
'DELIVERED',
|
|
35
|
+
'ORDER_STATUS_DELIVERED',
|
|
36
|
+
]);
|
|
37
|
+
/** `Environment` of `@devvit/protos`, and its string spellings. */
|
|
38
|
+
const sandboxEnvironments = new Set([
|
|
39
|
+
1,
|
|
40
|
+
'SANDBOX',
|
|
41
|
+
'ENVIRONMENT_SANDBOX',
|
|
42
|
+
]);
|
|
43
|
+
const productionEnvironments = new Set([
|
|
44
|
+
2,
|
|
45
|
+
'PRODUCTION',
|
|
46
|
+
'ENVIRONMENT_PRODUCTION',
|
|
47
|
+
]);
|
|
48
|
+
/** `AccountingType` of `@devvit/protos`: instant and consumable goods are consumables, durable goods are durable. */
|
|
49
|
+
const consumableAccountingTypes = new Set([
|
|
50
|
+
1,
|
|
51
|
+
3,
|
|
52
|
+
'INSTANT',
|
|
53
|
+
'CONSUMABLE',
|
|
54
|
+
'ACCOUNTING_TYPE_INSTANT',
|
|
55
|
+
'ACCOUNTING_TYPE_CONSUMABLE',
|
|
56
|
+
]);
|
|
57
|
+
const durableAccountingTypes = new Set([
|
|
58
|
+
2,
|
|
59
|
+
'DURABLE',
|
|
60
|
+
'ACCOUNTING_TYPE_DURABLE',
|
|
61
|
+
]);
|
|
62
|
+
const orderPageSize = maximumDevvitAvailableOrders;
|
|
63
|
+
/**
|
|
64
|
+
* Hard cap on the pages scanned per listing. The plugin's `status` filter
|
|
65
|
+
* does nothing in `@devvit/payments` 0.14 (its proto marks it so, and the
|
|
66
|
+
* `PaymentsClient` forwards it unchanged), so every status is scanned. The
|
|
67
|
+
* Devvit docs describe the list as newest first ("Manage Payments", "Check
|
|
68
|
+
* orders"); that is documentation, not a plugin guarantee, so the scan stops
|
|
69
|
+
* early only after two consecutive pages without a paid or delivered order,
|
|
70
|
+
* and warns when it stops with pages left.
|
|
71
|
+
*/
|
|
72
|
+
const maximumOrderPages = 8;
|
|
73
|
+
/** Consecutive pages without a paid or delivered order after which the scan stops early. */
|
|
74
|
+
const settledPagesBeforeStop = 2;
|
|
75
|
+
/**
|
|
76
|
+
* Serves `commerce.v2.*` from the payments plugin:
|
|
77
|
+
*
|
|
78
|
+
* - `getCapabilities` answers the Devvit profile (`createDevvitCommerceCapabilities`).
|
|
79
|
+
* - `fetchProducts` lists the configured SKUs from `payments.getProducts`
|
|
80
|
+
* (instant and consumable goods as `consumable`, durable goods as
|
|
81
|
+
* `durable`; time-limited goods are not listed) priced in Reddit Gold.
|
|
82
|
+
* - `getAvailablePurchases` lists the authenticated user's `PAID` and
|
|
83
|
+
* `DELIVERED` orders for configured SKUs from `payments.getOrders` as
|
|
84
|
+
* `purchased` (the order id as `transactionId` and `token`,
|
|
85
|
+
* `devvit.order.v1` evidence whose `status` says which), bounded by
|
|
86
|
+
* `maximumDevvitAvailableOrders`. Reddit marks an order `DELIVERED` as soon
|
|
87
|
+
* as the fulfillment handler answered, so the delivered orders are the ones
|
|
88
|
+
* whose ledger entry exists; the webview provider lists only the `PAID`
|
|
89
|
+
* ones as available and reports both as events, so a session that lost its
|
|
90
|
+
* checkout result still finds the order. The scan reads pages of 64
|
|
91
|
+
* (newest first, as the Devvit docs describe) and stops after two
|
|
92
|
+
* consecutive pages without a paid or delivered order or after
|
|
93
|
+
* `maximumOrderPages`, warning when pages are left.
|
|
94
|
+
* - `requestPurchase` is refused with `DEVVIT_COMMERCE_CHECKOUT_CLIENT_ONLY`,
|
|
95
|
+
* `finishTransaction` with `COMMERCE_V2_METHOD_UNSUPPORTED`, and
|
|
96
|
+
* `restorePurchases` answers `{ restored: false }`: the webview provider
|
|
97
|
+
* re-reads the list itself.
|
|
98
|
+
*
|
|
99
|
+
* Payloads and results are validated with the `@mpgd/platform/commerce`
|
|
100
|
+
* asserts; a plugin failure crosses the bridge as the retryable
|
|
101
|
+
* `DEVVIT_PAYMENTS_UNAVAILABLE`.
|
|
102
|
+
*/
|
|
103
|
+
export function createDevvitCommerceV2BridgeHandler(input) {
|
|
104
|
+
const store = input.store ?? devvitCommerceStoreId;
|
|
105
|
+
if (store.length === 0) {
|
|
106
|
+
throw new TypeError('A Devvit commerce bridge handler requires a non-empty store id.');
|
|
107
|
+
}
|
|
108
|
+
const products = normalizeBridgeProducts(input.products);
|
|
109
|
+
const productsBySku = new Map(products.map((product) => [product.sku, product]));
|
|
110
|
+
const warn = input.warn ?? ((message) => {
|
|
111
|
+
console.warn(message);
|
|
112
|
+
});
|
|
113
|
+
const listPaidOrders = async () => {
|
|
114
|
+
const orders = [];
|
|
115
|
+
let cursor;
|
|
116
|
+
let settledPages = 0;
|
|
117
|
+
for (let page = 0; page < maximumOrderPages; page += 1) {
|
|
118
|
+
const response = await input.payments.getOrders({
|
|
119
|
+
limit: orderPageSize,
|
|
120
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
121
|
+
});
|
|
122
|
+
const next = response.pageInfo;
|
|
123
|
+
const hasNextPage = next !== undefined && next.hasNextPage && next.endCursor.length > 0;
|
|
124
|
+
let listableOnPage = 0;
|
|
125
|
+
for (const [index, order] of response.orders.entries()) {
|
|
126
|
+
if (paidOrderStatuses.has(order.status) || deliveredOrderStatuses.has(order.status)) {
|
|
127
|
+
listableOnPage += 1;
|
|
128
|
+
}
|
|
129
|
+
const paid = readListedOrder(order, productsBySku);
|
|
130
|
+
if (paid !== undefined) {
|
|
131
|
+
orders.push(paid);
|
|
132
|
+
}
|
|
133
|
+
if (orders.length >= maximumDevvitAvailableOrders) {
|
|
134
|
+
if (index + 1 < response.orders.length || hasNextPage) {
|
|
135
|
+
warn('Devvit paid-order list reached the available-purchase bound; older orders wait.');
|
|
136
|
+
}
|
|
137
|
+
return orders;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (!hasNextPage || next === undefined) {
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
settledPages = listableOnPage === 0 ? settledPages + 1 : 0;
|
|
144
|
+
if (settledPages >= settledPagesBeforeStop) {
|
|
145
|
+
// Newest first, as documented: the older pages left hold nothing to list.
|
|
146
|
+
warn(`Devvit order scan stopped after ${String(settledPagesBeforeStop)} pages without a `
|
|
147
|
+
+ 'paid or delivered order; older pages were not read.');
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
if (page + 1 === maximumOrderPages) {
|
|
151
|
+
warn('Devvit order scan reached its page cap; older pages were not read.');
|
|
152
|
+
}
|
|
153
|
+
cursor = next.endCursor;
|
|
154
|
+
}
|
|
155
|
+
return orders;
|
|
156
|
+
};
|
|
157
|
+
const serve = async (request) => {
|
|
158
|
+
const { payload } = request;
|
|
159
|
+
switch (request.method) {
|
|
160
|
+
case 'commerce.v2.getCapabilities': {
|
|
161
|
+
assertCommerceBridgeGetCapabilitiesPayload(payload);
|
|
162
|
+
return assertResult('commerce.v2.getCapabilities', {
|
|
163
|
+
protocolVersion: commerceProtocolVersion,
|
|
164
|
+
store,
|
|
165
|
+
capabilities: createDevvitCommerceCapabilities(products.length === 0 ? 'configuration-required' : 'available'),
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
case 'commerce.v2.fetchProducts': {
|
|
169
|
+
const filter = assertCommerceBridgeFetchProductsPayload(payload);
|
|
170
|
+
if ((filter.type ?? 'all') === 'subscription' || products.length === 0) {
|
|
171
|
+
return assertResult('commerce.v2.fetchProducts', { products: [] });
|
|
172
|
+
}
|
|
173
|
+
const skus = products
|
|
174
|
+
.filter((product) => filter.logicalIds === undefined || filter.logicalIds.includes(product.productId))
|
|
175
|
+
.map((product) => product.sku);
|
|
176
|
+
if (skus.length === 0) {
|
|
177
|
+
return assertResult('commerce.v2.fetchProducts', { products: [] });
|
|
178
|
+
}
|
|
179
|
+
const response = await paymentsCall(() => input.payments.getProducts({ skus }));
|
|
180
|
+
const listed = [];
|
|
181
|
+
for (const product of response.products) {
|
|
182
|
+
const storeProduct = toStoreProduct(store, product, productsBySku);
|
|
183
|
+
if (storeProduct !== undefined && skus.includes(storeProduct.storeSku)) {
|
|
184
|
+
listed.push(storeProduct);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return assertResult('commerce.v2.fetchProducts', { products: listed });
|
|
188
|
+
}
|
|
189
|
+
case 'commerce.v2.requestPurchase':
|
|
190
|
+
assertCommerceBridgeRequestPurchasePayload(payload);
|
|
191
|
+
throw new PlatformOperationError({
|
|
192
|
+
code: devvitCommerceCheckoutClientOnlyBridgeErrorCode,
|
|
193
|
+
message: 'Devvit checkout opens in the webview; requestPurchase is served by the '
|
|
194
|
+
+ 'client commerce provider, not by the server bridge.',
|
|
195
|
+
});
|
|
196
|
+
case 'commerce.v2.getAvailablePurchases': {
|
|
197
|
+
// The payload is asserted as sent; `includePending` adds nothing here.
|
|
198
|
+
assertCommerceBridgeGetAvailablePurchasesPayload(payload);
|
|
199
|
+
const orders = await paymentsCall(listPaidOrders);
|
|
200
|
+
const purchases = [];
|
|
201
|
+
for (const order of orders) {
|
|
202
|
+
const product = productsBySku.get(order.sku);
|
|
203
|
+
if (product === undefined) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
purchases.push(createDevvitStorePurchase({
|
|
208
|
+
store,
|
|
209
|
+
orderId: order.orderId,
|
|
210
|
+
sku: order.sku,
|
|
211
|
+
logicalId: product.productId,
|
|
212
|
+
source: 'order-list',
|
|
213
|
+
paidAt: order.paidAt,
|
|
214
|
+
environment: order.environment,
|
|
215
|
+
delivered: order.delivered ?? false,
|
|
216
|
+
}));
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
// One invalid order must not hide the others.
|
|
220
|
+
warn(`Devvit paid order was not listed: ${errorMessage(error)}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return assertResult('commerce.v2.getAvailablePurchases', { purchases });
|
|
224
|
+
}
|
|
225
|
+
case 'commerce.v2.finishTransaction':
|
|
226
|
+
assertCommerceBridgeFinishTransactionPayload(payload);
|
|
227
|
+
throw new PlatformOperationError({
|
|
228
|
+
code: devvitCommerceV2MethodUnsupportedBridgeErrorCode,
|
|
229
|
+
message: `Store ${store} finishes nothing: the fulfillment handler settles on the server.`,
|
|
230
|
+
});
|
|
231
|
+
case 'commerce.v2.restorePurchases':
|
|
232
|
+
assertCommerceBridgeRestorePurchasesPayload(payload);
|
|
233
|
+
return assertResult('commerce.v2.restorePurchases', { restored: false });
|
|
234
|
+
default:
|
|
235
|
+
throw new PlatformOperationError({
|
|
236
|
+
code: 'UNSUPPORTED_METHOD',
|
|
237
|
+
message: `${request.method} is not a commerce v2 method.`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
return {
|
|
242
|
+
async request(request) {
|
|
243
|
+
try {
|
|
244
|
+
return { id: request.id, ok: true, data: await serve(request) };
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
return toBridgeError(request, error);
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/** A `PAID` or `DELIVERED` order for a configured SKU with exactly one product, or nothing. */
|
|
253
|
+
function readListedOrder(order, productsBySku) {
|
|
254
|
+
const delivered = deliveredOrderStatuses.has(order.status);
|
|
255
|
+
if (!delivered && !paidOrderStatuses.has(order.status)) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
const orderId = readDevvitIdentifier(order.id);
|
|
259
|
+
// A Devvit order holds one product (the payment handler normalizer requires it too).
|
|
260
|
+
const sku = order.products.length === 1
|
|
261
|
+
? readDevvitIdentifier(order.products[0]?.sku)
|
|
262
|
+
: undefined;
|
|
263
|
+
if (orderId === undefined || sku === undefined || !productsBySku.has(sku)) {
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
const paidAt = readDevvitOrderTime(order.updatedAt ?? order.createdAt ?? undefined);
|
|
267
|
+
const environment = readEnvironment(order.environment);
|
|
268
|
+
return {
|
|
269
|
+
orderId,
|
|
270
|
+
sku,
|
|
271
|
+
...(paidAt === undefined ? {} : { paidAt }),
|
|
272
|
+
...(environment === undefined ? {} : { environment }),
|
|
273
|
+
delivered,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function readEnvironment(value) {
|
|
277
|
+
if (value === undefined) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
if (sandboxEnvironments.has(value)) {
|
|
281
|
+
return 'sandbox';
|
|
282
|
+
}
|
|
283
|
+
if (productionEnvironments.has(value)) {
|
|
284
|
+
return 'production';
|
|
285
|
+
}
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
function toStoreProduct(store, product, productsBySku) {
|
|
289
|
+
const sku = readDevvitIdentifier(product.sku);
|
|
290
|
+
const configured = sku === undefined ? undefined : productsBySku.get(sku);
|
|
291
|
+
if (sku === undefined || configured === undefined) {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
const kind = consumableAccountingTypes.has(product.accountingType)
|
|
295
|
+
? 'consumable'
|
|
296
|
+
: durableAccountingTypes.has(product.accountingType)
|
|
297
|
+
? 'durable'
|
|
298
|
+
: undefined;
|
|
299
|
+
if (kind === undefined) {
|
|
300
|
+
// Time-limited goods (`VALID_FOR_*`) have no v2 kind yet.
|
|
301
|
+
return undefined;
|
|
302
|
+
}
|
|
303
|
+
const amount = product.price?.amount;
|
|
304
|
+
const formatted = typeof amount === 'number' && Number.isFinite(amount)
|
|
305
|
+
? `${String(amount)} Gold`
|
|
306
|
+
: 'Reddit Gold';
|
|
307
|
+
try {
|
|
308
|
+
return assertStoreProduct({
|
|
309
|
+
logicalId: configured.productId,
|
|
310
|
+
store,
|
|
311
|
+
storeSku: sku,
|
|
312
|
+
kind,
|
|
313
|
+
title: product.name,
|
|
314
|
+
description: product.description,
|
|
315
|
+
price: { formatted, currencyCode: 'GOLD' },
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
throw new CommerceError('INVALID_RESPONSE', {
|
|
320
|
+
message: `Devvit reported an invalid product for ${sku}.`,
|
|
321
|
+
cause: error,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function normalizeBridgeProducts(input) {
|
|
326
|
+
const productIds = new Set();
|
|
327
|
+
const skus = new Set();
|
|
328
|
+
return input.map((product) => {
|
|
329
|
+
if (readDevvitIdentifier(product.productId) === undefined) {
|
|
330
|
+
throw new TypeError('Devvit commerce bridge product ids must be non-empty identifiers.');
|
|
331
|
+
}
|
|
332
|
+
if (readDevvitIdentifier(product.sku) === undefined) {
|
|
333
|
+
throw new TypeError('Devvit commerce bridge SKUs must be non-empty identifiers.');
|
|
334
|
+
}
|
|
335
|
+
if (productIds.has(product.productId)) {
|
|
336
|
+
throw new TypeError(`Duplicate Devvit logical product ID: ${product.productId}`);
|
|
337
|
+
}
|
|
338
|
+
if (skus.has(product.sku)) {
|
|
339
|
+
throw new TypeError(`Duplicate Devvit product SKU: ${product.sku}`);
|
|
340
|
+
}
|
|
341
|
+
productIds.add(product.productId);
|
|
342
|
+
skus.add(product.sku);
|
|
343
|
+
return { productId: product.productId, sku: product.sku };
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
async function paymentsCall(call) {
|
|
347
|
+
try {
|
|
348
|
+
return await call();
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
if (error instanceof PlatformOperationError) {
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
354
|
+
throw new PlatformOperationError({
|
|
355
|
+
code: devvitPaymentsUnavailableBridgeErrorCode,
|
|
356
|
+
message: `Devvit payments plugin request failed: ${errorMessage(error)}`,
|
|
357
|
+
retryable: true,
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Each entry is the typia assert of that method's result type; the lookup is
|
|
362
|
+
// typed by `assertDevvitCommerceBridgeResult`.
|
|
363
|
+
const resultAsserts = {
|
|
364
|
+
'commerce.v2.getCapabilities': assertCommerceBridgeGetCapabilitiesResult,
|
|
365
|
+
'commerce.v2.fetchProducts': assertCommerceBridgeFetchProductsResult,
|
|
366
|
+
'commerce.v2.getAvailablePurchases': assertCommerceBridgeGetAvailablePurchasesResult,
|
|
367
|
+
'commerce.v2.restorePurchases': assertCommerceBridgeRestorePurchasesResult,
|
|
368
|
+
};
|
|
369
|
+
function assertResult(method, data) {
|
|
370
|
+
const assert = resultAsserts[method];
|
|
371
|
+
if (assert === undefined) {
|
|
372
|
+
throw new PlatformOperationError({
|
|
373
|
+
code: devvitCommerceV2MethodUnsupportedBridgeErrorCode,
|
|
374
|
+
message: `${method} is not served by the Devvit server bridge.`,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
return assert(data);
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
throw new PlatformOperationError({
|
|
382
|
+
code: 'NATIVE_PROVIDER_INVALID_RESPONSE',
|
|
383
|
+
message: `Devvit commerce bridge produced invalid data for ${method}: ${errorMessage(error)}`,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function toBridgeError(request, error) {
|
|
388
|
+
if (isTypiaGuardError(error)) {
|
|
389
|
+
return createBridgeError(request.id, devvitCommerceV2InvalidPayloadBridgeErrorCode, `${request.method} payload is invalid: ${error.message}`);
|
|
390
|
+
}
|
|
391
|
+
if (error instanceof PlatformOperationError) {
|
|
392
|
+
return createBridgeError(request.id, error.code, error.message, error.retryable);
|
|
393
|
+
}
|
|
394
|
+
const info = commerceErrorInfoOf(error);
|
|
395
|
+
return createBridgeError(request.id, info.code, info.message ?? `${request.method} failed.`, info.retryable);
|
|
396
|
+
}
|
|
397
|
+
function isTypiaGuardError(error) {
|
|
398
|
+
return error instanceof Error && error.name === 'TypeGuardError';
|
|
399
|
+
}
|
|
400
|
+
function errorMessage(error) {
|
|
401
|
+
return error instanceof Error ? error.message : String(error);
|
|
402
|
+
}
|
package/dist/server/index.d.ts
CHANGED
package/dist/server/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/adapter-devvit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Reddit Devvit Web platform adapter for mpgd games.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -55,11 +55,12 @@
|
|
|
55
55
|
],
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@devvit/web": "0.14.1",
|
|
58
|
-
"@mpgd/
|
|
59
|
-
"@mpgd/
|
|
60
|
-
"@mpgd/
|
|
58
|
+
"@mpgd/bridge": "0.12.2",
|
|
59
|
+
"@mpgd/game-services": "0.21.1",
|
|
60
|
+
"@mpgd/platform": "0.21.0"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
+
"@types/node": "^24.0.0",
|
|
63
64
|
"ttsc": "0.30.4",
|
|
64
65
|
"typescript": "7.0.2",
|
|
65
66
|
"vitest": "^4.0.0"
|