@mpgd/adapter-devvit 0.8.2 → 0.8.4
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/index.js +25 -173
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { createBridgeError, } from '@mpgd/bridge';
|
|
1
|
+
import { bridgeStorageLoadProtocol, createBridgeError, decodeBridgeStorageLoadData, } from '@mpgd/bridge';
|
|
2
2
|
import { createBridgeOrpcClient, defaultBridgeRpcEndpoint, } from '@mpgd/bridge/orpc';
|
|
3
3
|
export * from './payments.js';
|
|
4
4
|
export const defaultDevvitRpcEndpoint = defaultBridgeRpcEndpoint;
|
|
5
|
-
const devvitFallbackStoragePrefix = 'mpgd:devvit:fallback:';
|
|
6
5
|
export class DevvitBridgeError extends Error {
|
|
7
6
|
code;
|
|
8
7
|
requestId;
|
|
@@ -16,7 +15,6 @@ export class DevvitBridgeError extends Error {
|
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
17
|
export function createDevvitPlatformGateway(input) {
|
|
19
|
-
let lastResolvedStorageFallbackNamespace;
|
|
20
18
|
async function request(method, payload) {
|
|
21
19
|
const bridge = input.bridge ??
|
|
22
20
|
getBridge() ??
|
|
@@ -40,18 +38,12 @@ export function createDevvitPlatformGateway(input) {
|
|
|
40
38
|
}
|
|
41
39
|
return {
|
|
42
40
|
target: 'reddit',
|
|
43
|
-
getCapabilities
|
|
41
|
+
async getCapabilities() {
|
|
42
|
+
return { ...await request('runtime.getCapabilities', {}) };
|
|
43
|
+
},
|
|
44
44
|
identity: {
|
|
45
|
-
getPlayer:
|
|
46
|
-
|
|
47
|
-
rememberStorageFallbackNamespace(player);
|
|
48
|
-
return player;
|
|
49
|
-
},
|
|
50
|
-
getSession: async () => {
|
|
51
|
-
const session = await request('identity.getSession', {});
|
|
52
|
-
rememberStorageFallbackNamespace(session.playerId === undefined ? null : { playerId: session.playerId });
|
|
53
|
-
return session;
|
|
54
|
-
},
|
|
45
|
+
getPlayer: () => request('identity.getPlayer', {}),
|
|
46
|
+
getSession: () => request('identity.getSession', {}),
|
|
55
47
|
requestUpgrade: (payload) => request('identity.requestUpgrade', payload),
|
|
56
48
|
},
|
|
57
49
|
presentation: {
|
|
@@ -91,100 +83,16 @@ export function createDevvitPlatformGateway(input) {
|
|
|
91
83
|
},
|
|
92
84
|
storage: {
|
|
93
85
|
async load(payload) {
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
value = await request('storage.load', payload);
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
const fallback = await loadStorageFallback(payload.key, {
|
|
100
|
-
useCachedNamespaceOnFailure: true,
|
|
101
|
-
});
|
|
102
|
-
if (fallback !== null) {
|
|
103
|
-
return fallback;
|
|
104
|
-
}
|
|
105
|
-
throw error;
|
|
106
|
-
}
|
|
107
|
-
const fallback = await loadStorageFallback(payload.key, {
|
|
108
|
-
useCachedNamespaceOnFailure: value === null,
|
|
109
|
-
});
|
|
110
|
-
return fallback ?? (value === null ? null : { value });
|
|
86
|
+
return decodeBridgeStorageLoadData(await request('storage.load', payload));
|
|
111
87
|
},
|
|
112
88
|
async save(payload) {
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
result = await request('storage.save', payload);
|
|
116
|
-
}
|
|
117
|
-
catch (error) {
|
|
118
|
-
if (await saveStorageFallback(payload, undefined, {
|
|
119
|
-
useCachedNamespaceOnFailure: true,
|
|
120
|
-
})) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
throw error;
|
|
124
|
-
}
|
|
89
|
+
const result = await request('storage.save', payload);
|
|
125
90
|
if (result.saved !== true) {
|
|
126
|
-
|
|
127
|
-
useCachedNamespaceOnFailure: false,
|
|
128
|
-
})) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
throw new Error('Devvit storage save was not persisted and local fallback storage is unavailable.');
|
|
91
|
+
throw new Error('Devvit storage save was not persisted.');
|
|
132
92
|
}
|
|
133
|
-
await removeStorageFallback(payload.key, result.playerId);
|
|
134
93
|
},
|
|
135
94
|
},
|
|
136
95
|
};
|
|
137
|
-
async function loadStorageFallback(key, options) {
|
|
138
|
-
if (!hasDevvitStorageFallbackCandidate(key, input.appVersion)) {
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
const namespace = await resolveNamespace(undefined, options);
|
|
142
|
-
return namespace === undefined ? null : loadDevvitStorageFallback(key, namespace);
|
|
143
|
-
}
|
|
144
|
-
async function saveStorageFallback(payload, playerId, options) {
|
|
145
|
-
const namespace = await resolveNamespace(playerId, options);
|
|
146
|
-
return namespace === undefined ? false : saveDevvitStorageFallback(payload, namespace);
|
|
147
|
-
}
|
|
148
|
-
async function removeStorageFallback(key, playerId) {
|
|
149
|
-
const namespace = await resolveNamespace(playerId, {
|
|
150
|
-
useCachedNamespaceOnFailure: true,
|
|
151
|
-
});
|
|
152
|
-
if (namespace === undefined) {
|
|
153
|
-
console.warn(`devvit storage fallback cleanup skipped due to unresolved namespace for key: ${key}`);
|
|
154
|
-
}
|
|
155
|
-
removeDevvitStorageFallback(key, namespace);
|
|
156
|
-
}
|
|
157
|
-
async function resolveNamespace(playerId, options) {
|
|
158
|
-
const namespace = storageFallbackNamespaceFromPlayerId(playerId);
|
|
159
|
-
if (namespace !== undefined) {
|
|
160
|
-
lastResolvedStorageFallbackNamespace = namespace;
|
|
161
|
-
return namespace;
|
|
162
|
-
}
|
|
163
|
-
try {
|
|
164
|
-
return await resolveStorageFallbackNamespace();
|
|
165
|
-
}
|
|
166
|
-
catch (error) {
|
|
167
|
-
console.warn(`devvit storage fallback namespace resolution failed: ${errorMessage(error)}`);
|
|
168
|
-
return options.useCachedNamespaceOnFailure ? lastResolvedStorageFallbackNamespace : undefined;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
async function resolveStorageFallbackNamespace() {
|
|
172
|
-
const player = await request('identity.getPlayer', {});
|
|
173
|
-
return rememberStorageFallbackNamespace(player);
|
|
174
|
-
}
|
|
175
|
-
function rememberStorageFallbackNamespace(player) {
|
|
176
|
-
const playerId = player !== null && typeof player.playerId === 'string' && player.playerId.length > 0
|
|
177
|
-
? player.playerId
|
|
178
|
-
: 'anonymous';
|
|
179
|
-
const namespace = devvitStorageFallbackNamespace(input.appVersion, playerId);
|
|
180
|
-
lastResolvedStorageFallbackNamespace = namespace;
|
|
181
|
-
return namespace;
|
|
182
|
-
}
|
|
183
|
-
function storageFallbackNamespaceFromPlayerId(playerId) {
|
|
184
|
-
return playerId !== undefined && playerId.length > 0
|
|
185
|
-
? devvitStorageFallbackNamespace(input.appVersion, playerId)
|
|
186
|
-
: undefined;
|
|
187
|
-
}
|
|
188
96
|
}
|
|
189
97
|
export function createDevvitOrpcBridge(input = {}) {
|
|
190
98
|
const client = input.client ?? createBridgeOrpcClient({
|
|
@@ -305,13 +213,20 @@ export function createDevvitSandboxBridge() {
|
|
|
305
213
|
case 'storage.load': {
|
|
306
214
|
const payload = optionalObjectPayload(input.payload);
|
|
307
215
|
const key = typeof payload.key === 'string' ? payload.key : undefined;
|
|
308
|
-
|
|
216
|
+
const value = key === undefined ? undefined : storage.get(key);
|
|
217
|
+
return ok(input, value === undefined
|
|
218
|
+
? { __mpgdBridgeProtocol: bridgeStorageLoadProtocol, found: false }
|
|
219
|
+
: {
|
|
220
|
+
__mpgdBridgeProtocol: bridgeStorageLoadProtocol,
|
|
221
|
+
found: true,
|
|
222
|
+
value: cloneJsonValue(value),
|
|
223
|
+
});
|
|
309
224
|
}
|
|
310
225
|
case 'storage.save': {
|
|
311
226
|
const payload = optionalObjectPayload(input.payload);
|
|
312
227
|
const key = typeof payload.key === 'string' ? payload.key : undefined;
|
|
313
228
|
if (key !== undefined) {
|
|
314
|
-
storage.set(key, payload.value);
|
|
229
|
+
storage.set(key, cloneJsonValue(payload.value));
|
|
315
230
|
}
|
|
316
231
|
return ok(input, {
|
|
317
232
|
saved: key !== undefined,
|
|
@@ -323,6 +238,13 @@ export function createDevvitSandboxBridge() {
|
|
|
323
238
|
},
|
|
324
239
|
};
|
|
325
240
|
}
|
|
241
|
+
function cloneJsonValue(value) {
|
|
242
|
+
const serialized = JSON.stringify(value);
|
|
243
|
+
if (serialized === undefined) {
|
|
244
|
+
throw new TypeError('Storage values must be JSON-serializable.');
|
|
245
|
+
}
|
|
246
|
+
return JSON.parse(serialized);
|
|
247
|
+
}
|
|
326
248
|
function optionalObjectPayload(payload) {
|
|
327
249
|
if (typeof payload !== 'object' || payload === null) {
|
|
328
250
|
return {};
|
|
@@ -336,73 +258,3 @@ function ok(input, data) {
|
|
|
336
258
|
data,
|
|
337
259
|
};
|
|
338
260
|
}
|
|
339
|
-
function loadDevvitStorageFallback(key, namespace) {
|
|
340
|
-
const storage = browserLocalStorage();
|
|
341
|
-
if (storage === undefined) {
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
const fallbackKey = devvitStorageFallbackKey(key, namespace);
|
|
345
|
-
const stored = storage.getItem(fallbackKey);
|
|
346
|
-
if (stored === null) {
|
|
347
|
-
return null;
|
|
348
|
-
}
|
|
349
|
-
try {
|
|
350
|
-
return {
|
|
351
|
-
value: JSON.parse(stored),
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
catch {
|
|
355
|
-
storage.removeItem(fallbackKey);
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
function saveDevvitStorageFallback(input, namespace) {
|
|
360
|
-
const storage = browserLocalStorage();
|
|
361
|
-
if (storage === undefined) {
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
364
|
-
try {
|
|
365
|
-
storage.setItem(devvitStorageFallbackKey(input.key, namespace), JSON.stringify(input.value));
|
|
366
|
-
return true;
|
|
367
|
-
}
|
|
368
|
-
catch {
|
|
369
|
-
return false;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
function removeDevvitStorageFallback(key, namespace) {
|
|
373
|
-
const storage = browserLocalStorage();
|
|
374
|
-
if (storage !== undefined) {
|
|
375
|
-
if (namespace !== undefined) {
|
|
376
|
-
storage.removeItem(devvitStorageFallbackKey(key, namespace));
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
function hasDevvitStorageFallbackCandidate(key, appVersion) {
|
|
381
|
-
const storage = browserLocalStorage();
|
|
382
|
-
if (storage === undefined) {
|
|
383
|
-
return false;
|
|
384
|
-
}
|
|
385
|
-
const prefix = `${devvitFallbackStoragePrefix}${encodeURIComponent(appVersion)}:`;
|
|
386
|
-
const suffix = `:${encodeURIComponent(key)}`;
|
|
387
|
-
for (let index = 0; index < storage.length; index += 1) {
|
|
388
|
-
const candidate = storage.key(index);
|
|
389
|
-
if (candidate?.startsWith(prefix) === true && candidate.endsWith(suffix)) {
|
|
390
|
-
return true;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
return false;
|
|
394
|
-
}
|
|
395
|
-
function devvitStorageFallbackNamespace(appVersion, playerId) {
|
|
396
|
-
return `${encodeURIComponent(appVersion)}:${encodeURIComponent(playerId)}`;
|
|
397
|
-
}
|
|
398
|
-
function devvitStorageFallbackKey(key, namespace) {
|
|
399
|
-
return `${devvitFallbackStoragePrefix}${namespace}:${encodeURIComponent(key)}`;
|
|
400
|
-
}
|
|
401
|
-
function browserLocalStorage() {
|
|
402
|
-
try {
|
|
403
|
-
return globalThis.localStorage;
|
|
404
|
-
}
|
|
405
|
-
catch {
|
|
406
|
-
return undefined;
|
|
407
|
-
}
|
|
408
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/adapter-devvit",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Reddit Devvit Web platform adapter for mpgd games.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -55,9 +55,9 @@
|
|
|
55
55
|
],
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@devvit/web": "0.13.8",
|
|
58
|
-
"@mpgd/
|
|
59
|
-
"@mpgd/
|
|
60
|
-
"@mpgd/
|
|
58
|
+
"@mpgd/game-services": "0.10.0",
|
|
59
|
+
"@mpgd/platform": "0.7.0",
|
|
60
|
+
"@mpgd/bridge": "0.6.1"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"ttsc": "0.18.4",
|