@mpgd/adapter-ait 0.5.2 → 0.6.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/index.d.ts CHANGED
@@ -3,6 +3,17 @@ import type { PlatformGateway } from '@mpgd/platform';
3
3
  export interface GamePlatformBridge {
4
4
  request(input: BridgeRequest): Promise<BridgeResponse>;
5
5
  }
6
+ export interface AitSandboxStorage {
7
+ readonly getItem: (key: string) => string | null;
8
+ readonly setItem: (key: string, value: string) => void;
9
+ readonly removeItem?: (key: string) => void;
10
+ }
11
+ export interface CreateAitSandboxBridgeOptions {
12
+ /** Persistent browser storage used by local AIT playtests. Defaults to localStorage when available. */
13
+ readonly storage?: AitSandboxStorage | null;
14
+ /** Namespace for persisted sandbox values so they cannot collide with application-owned keys. */
15
+ readonly storageKeyPrefix?: string;
16
+ }
6
17
  export declare function createAitPlatformGateway(input: {
7
18
  readonly appVersion: string;
8
19
  readonly buildId: string;
@@ -10,4 +21,4 @@ export declare function createAitPlatformGateway(input: {
10
21
  readonly fallbackBridge?: GamePlatformBridge;
11
22
  }): PlatformGateway;
12
23
  export { aitLifecyclePauseEvent, aitLifecycleResumeEvent, createAitLifecycleAdapter, dispatchAitLifecycleEvent, } from './lifecycle.js';
13
- export declare function createAitSandboxBridge(): GamePlatformBridge;
24
+ export declare function createAitSandboxBridge(options?: CreateAitSandboxBridgeOptions): GamePlatformBridge;
package/dist/index.js CHANGED
@@ -72,8 +72,18 @@ export { aitLifecyclePauseEvent, aitLifecycleResumeEvent, createAitLifecycleAdap
72
72
  function getBridge() {
73
73
  return globalThis.__GAME_PLATFORM_BRIDGE__;
74
74
  }
75
- export function createAitSandboxBridge() {
75
+ export function createAitSandboxBridge(options = {}) {
76
76
  const storage = new Map();
77
+ let persistentStorage;
78
+ if (options.storage === undefined) {
79
+ persistentStorage = resolveAitSandboxBrowserStorage();
80
+ }
81
+ else {
82
+ persistentStorage = options.storage ?? undefined;
83
+ }
84
+ // PlatformGateway storage exposes load/save only. Sandbox callers should use stable,
85
+ // namespaced keys whose values are overwritten instead of creating an unbounded key stream.
86
+ const storageKeyPrefix = options.storageKeyPrefix ?? 'mpgd:ait-sandbox:';
77
87
  return {
78
88
  async request(input) {
79
89
  switch (input.method) {
@@ -167,41 +177,141 @@ export function createAitSandboxBridge() {
167
177
  return ok(input, {});
168
178
  case 'storage.load': {
169
179
  const payload = input.payload;
170
- const found = payload.key !== undefined && storage.has(payload.key);
171
- const value = found && payload.key !== undefined
172
- ? cloneJsonValue(storage.get(payload.key))
173
- : undefined;
174
- return ok(input, !found
175
- ? { __mpgdBridgeProtocol: bridgeStorageLoadProtocol, found: false }
176
- : { __mpgdBridgeProtocol: bridgeStorageLoadProtocol, found: true, value });
180
+ if (payload.key === undefined) {
181
+ return ok(input, {
182
+ __mpgdBridgeProtocol: bridgeStorageLoadProtocol,
183
+ found: false,
184
+ });
185
+ }
186
+ if (storage.has(payload.key)) {
187
+ return ok(input, {
188
+ __mpgdBridgeProtocol: bridgeStorageLoadProtocol,
189
+ found: true,
190
+ value: cloneJsonValue(storage.get(payload.key)),
191
+ });
192
+ }
193
+ const persistent = loadPersistentSandboxValue(persistentStorage, `${storageKeyPrefix}${payload.key}`);
194
+ // Browser storage access failures commonly remain blocked for the page lifetime. The
195
+ // sandbox intentionally degrades once per bridge instead of retrying and logging on every
196
+ // load. Production AIT storage uses the host bridge and is not affected by this fallback.
197
+ if (!persistent.storageAvailable && persistentStorage !== undefined) {
198
+ console.warn('[mpgd/adapter-ait] AIT sandbox persistent storage is unavailable; subsequent saves are memory-only and will not survive reloads.');
199
+ persistentStorage = undefined;
200
+ }
201
+ if (persistent.found) {
202
+ // Keep the hydrated cache detached from the returned parsed value. Callers may mutate
203
+ // their result, while a later memory load must still model serialized storage.
204
+ storage.set(payload.key, cloneJsonValue(persistent.value));
205
+ return ok(input, {
206
+ __mpgdBridgeProtocol: bridgeStorageLoadProtocol,
207
+ found: true,
208
+ value: persistent.value,
209
+ });
210
+ }
211
+ return ok(input, {
212
+ __mpgdBridgeProtocol: bridgeStorageLoadProtocol,
213
+ found: false,
214
+ });
177
215
  }
178
216
  case 'storage.save': {
179
217
  const payload = input.payload;
180
218
  if (payload.key !== undefined) {
181
- storage.set(payload.key, cloneJsonValue(payload.value));
219
+ let serialized;
220
+ let value;
221
+ try {
222
+ serialized = serializeJsonValue(payload.value);
223
+ value = JSON.parse(serialized);
224
+ }
225
+ catch {
226
+ return createSandboxBridgeError(input, 'STORAGE_SERIALIZATION_FAILED', 'AIT sandbox storage values must be JSON-serializable.');
227
+ }
228
+ try {
229
+ savePersistentSandboxValue(persistentStorage, `${storageKeyPrefix}${payload.key}`, serialized);
230
+ }
231
+ catch {
232
+ // The helper reports the original failure and the loss of reload durability. The
233
+ // sandbox still honors its memory fallback so local gameplay can continue.
234
+ persistentStorage = undefined;
235
+ }
236
+ storage.set(payload.key, value);
182
237
  }
183
238
  return ok(input, {});
184
239
  }
185
240
  default:
186
- return {
187
- id: input.id,
188
- ok: false,
189
- error: {
190
- code: 'UNSUPPORTED_METHOD',
191
- message: `Unsupported AIT sandbox method: ${input.method}`,
192
- retryable: false,
193
- },
194
- };
241
+ return createSandboxBridgeError(input, 'UNSUPPORTED_METHOD', `Unsupported AIT sandbox method: ${input.method}`);
195
242
  }
196
243
  },
197
244
  };
198
245
  }
246
+ function resolveAitSandboxBrowserStorage() {
247
+ try {
248
+ return globalThis.localStorage;
249
+ }
250
+ catch (error) {
251
+ reportAitSandboxStorageResolutionFailure(error);
252
+ return undefined;
253
+ }
254
+ }
255
+ function loadPersistentSandboxValue(storage, key) {
256
+ if (storage === undefined) {
257
+ return { found: false, storageAvailable: false };
258
+ }
259
+ let serialized;
260
+ try {
261
+ serialized = storage.getItem(key);
262
+ }
263
+ catch (error) {
264
+ return { found: false, storageAvailable: false };
265
+ }
266
+ if (serialized === null) {
267
+ return { found: false, storageAvailable: true };
268
+ }
269
+ try {
270
+ return {
271
+ found: true,
272
+ storageAvailable: true,
273
+ value: JSON.parse(serialized),
274
+ };
275
+ }
276
+ catch (error) {
277
+ console.warn(`[mpgd/adapter-ait] Removing corrupt sandbox storage value for key "${key}".`, error);
278
+ if (storage.removeItem === undefined) {
279
+ console.warn('[mpgd/adapter-ait] Corrupt sandbox storage cannot be removed; disabling persistent storage for this bridge.');
280
+ return { found: false, storageAvailable: false };
281
+ }
282
+ try {
283
+ storage.removeItem(key);
284
+ }
285
+ catch (cleanupError) {
286
+ console.warn('[mpgd/adapter-ait] Corrupt sandbox storage cleanup failed; disabling persistent storage for this bridge.', cleanupError);
287
+ return { found: false, storageAvailable: false };
288
+ }
289
+ return { found: false, storageAvailable: true };
290
+ }
291
+ }
292
+ function savePersistentSandboxValue(storage, key, serialized) {
293
+ if (storage === undefined) {
294
+ return;
295
+ }
296
+ try {
297
+ storage.setItem(key, serialized);
298
+ }
299
+ catch (error) {
300
+ console.warn('[mpgd/adapter-ait] AIT sandbox persistent storage save failed; subsequent saves are memory-only and will not survive reloads.', error);
301
+ throw error;
302
+ }
303
+ }
304
+ function reportAitSandboxStorageResolutionFailure(error) {
305
+ }
199
306
  function cloneJsonValue(input) {
307
+ return JSON.parse(serializeJsonValue(input));
308
+ }
309
+ function serializeJsonValue(input) {
200
310
  const serialized = JSON.stringify(input);
201
311
  if (serialized === undefined) {
202
312
  throw new Error('AIT sandbox storage values must be JSON-serializable.');
203
313
  }
204
- return JSON.parse(serialized);
314
+ return serialized;
205
315
  }
206
316
  function ok(input, data) {
207
317
  return {
@@ -210,3 +320,10 @@ function ok(input, data) {
210
320
  data,
211
321
  };
212
322
  }
323
+ function createSandboxBridgeError(input, code, message, retryable = false) {
324
+ return {
325
+ id: input.id,
326
+ ok: false,
327
+ error: { code, message, retryable },
328
+ };
329
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mpgd/adapter-ait",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Apps in Toss WebView platform adapter for mpgd games.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -47,8 +47,8 @@
47
47
  "dist"
48
48
  ],
49
49
  "dependencies": {
50
- "@mpgd/platform": "0.7.0",
51
- "@mpgd/bridge": "0.6.1"
50
+ "@mpgd/bridge": "0.6.1",
51
+ "@mpgd/platform": "0.7.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@apps-in-toss/web-framework": ">=2.10.4 <3"