@chipmobilesdk/rn-payment 0.1.0 → 0.2.1
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/CHANGELOG.md +113 -1
- package/README.md +885 -761
- package/package.json +54 -54
- package/skills/sdk-rn-payment/SKILL.md +33 -6
- package/src/adapter/createStoreProvider.ts +117 -117
- package/src/adapter/iap-adapter.ts +547 -516
- package/src/adapter/port.ts +12 -0
- package/src/config/types.ts +40 -0
- package/src/diagnostics/events.ts +9 -0
- package/src/engine/engine.ts +951 -854
- package/src/engine/finalize.ts +298 -298
- package/src/engine/pending.ts +118 -0
- package/src/engine/products.ts +259 -259
- package/src/engine/purchase.ts +526 -495
- package/src/engine/subscriptions.ts +157 -157
- package/src/entitlements/aggregate.ts +21 -4
- package/src/index.ts +4 -0
- package/src/persistence/mmkv-store.ts +16 -8
- package/src/react/hooks.ts +274 -238
- package/src/testing/fakeStore.ts +33 -0
|
@@ -1,157 +1,157 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Subscription plan changes and the route to the store's management screen (US12).
|
|
3
|
-
*
|
|
4
|
-
* Two platform rules drive this module:
|
|
5
|
-
*
|
|
6
|
-
* - **Proration is the app's decision, inside what the store permits** (FR-065).
|
|
7
|
-
* Whether a user who upgrades gets charged immediately, keeps their remaining
|
|
8
|
-
* time, or starts a fresh period is a product and pricing choice, so the
|
|
9
|
-
* package forwards it rather than picking.
|
|
10
|
-
* - **Cancellation belongs to the store.** Platforms forbid an app presenting its
|
|
11
|
-
* own cancellation screen, so this module offers only a route to the store's
|
|
12
|
-
* (FR-066) — there is deliberately no self-built alternative anywhere in the
|
|
13
|
-
* package.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type {
|
|
17
|
-
ProductKey,
|
|
18
|
-
PurchaseOptions,
|
|
19
|
-
PurchaseResult,
|
|
20
|
-
} from '../config/types';
|
|
21
|
-
import type { ResolvedConfig } from '../config/validate';
|
|
22
|
-
import type { ProviderPort } from '../adapter/port';
|
|
23
|
-
import { storeIdForProductKey } from '../entitlements/mapping';
|
|
24
|
-
import type { PersistedSnapshot } from '../persistence/snapshot';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* How already-paid time is handled when a plan changes.
|
|
28
|
-
*
|
|
29
|
-
* Named for the user-visible effect rather than mirroring a store's enum, so the
|
|
30
|
-
* same vocabulary will map onto StoreKit later (FR-004).
|
|
31
|
-
*/
|
|
32
|
-
export type ProrationMode =
|
|
33
|
-
/** Switch now; credit unused time against the new plan. The usual upgrade. */
|
|
34
|
-
| 'immediate_with_time_proration'
|
|
35
|
-
/** Switch now; charge the full new price immediately and restart the period. */
|
|
36
|
-
| 'immediate_and_charge_full_price'
|
|
37
|
-
/** Switch now; charge the prorated difference for the current period. */
|
|
38
|
-
| 'immediate_and_charge_prorated_price'
|
|
39
|
-
/** Keep the current plan until it expires, then start the new one. Usual downgrade. */
|
|
40
|
-
| 'deferred';
|
|
41
|
-
|
|
42
|
-
/** Store-specific replacement codes stay here, behind the neutral vocabulary above. */
|
|
43
|
-
const PRORATION_CODES: Record<ProrationMode, string> = {
|
|
44
|
-
immediate_with_time_proration: 'WITH_TIME_PRORATION',
|
|
45
|
-
immediate_and_charge_full_price: 'CHARGE_FULL_PRICE',
|
|
46
|
-
immediate_and_charge_prorated_price: 'CHARGE_PRORATED_PRICE',
|
|
47
|
-
deferred: 'DEFERRED',
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export type PriceChangeResult =
|
|
51
|
-
| { kind: 'confirmed' }
|
|
52
|
-
| { kind: 'dismissed' }
|
|
53
|
-
/** The platform has no client-side flow; it notifies the user itself (FR-067). */
|
|
54
|
-
| { kind: 'unsupported' }
|
|
55
|
-
| { kind: 'failed'; code: string };
|
|
56
|
-
|
|
57
|
-
export interface SubscriptionsDeps {
|
|
58
|
-
resolved: ResolvedConfig;
|
|
59
|
-
provider: ProviderPort;
|
|
60
|
-
snapshot: () => PersistedSnapshot;
|
|
61
|
-
purchase: (
|
|
62
|
-
productKey: ProductKey,
|
|
63
|
-
options?: PurchaseOptions & { replaceProductKey?: ProductKey; prorationMode?: string },
|
|
64
|
-
) => Promise<PurchaseResult>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface Subscriptions {
|
|
68
|
-
changePlan(
|
|
69
|
-
fromProductKey: ProductKey,
|
|
70
|
-
toProductKey: ProductKey,
|
|
71
|
-
mode: ProrationMode,
|
|
72
|
-
options?: PurchaseOptions,
|
|
73
|
-
): Promise<PurchaseResult>;
|
|
74
|
-
openManagement(productKey?: ProductKey): Promise<void>;
|
|
75
|
-
confirmPriceChange(productKey: ProductKey): Promise<PriceChangeResult>;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function createSubscriptions(deps: SubscriptionsDeps): Subscriptions {
|
|
79
|
-
const { resolved } = deps;
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
async changePlan(
|
|
83
|
-
fromProductKey,
|
|
84
|
-
toProductKey,
|
|
85
|
-
mode,
|
|
86
|
-
options,
|
|
87
|
-
): Promise<PurchaseResult> {
|
|
88
|
-
if (!deps.provider.capabilities().supportsPlanChange) {
|
|
89
|
-
return { kind: 'unsupported' };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const target = resolved.entries.get(toProductKey);
|
|
93
|
-
if (!target) {
|
|
94
|
-
return { kind: 'unknown_product', productKey: toProductKey };
|
|
95
|
-
}
|
|
96
|
-
if (!resolved.entries.has(fromProductKey)) {
|
|
97
|
-
return { kind: 'unknown_product', productKey: fromProductKey };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// The store needs the *token* of the subscription being replaced, not its
|
|
101
|
-
// product id — that is how it knows which active purchase to migrate, and
|
|
102
|
-
// it is why a plan change needs a live local record rather than just two
|
|
103
|
-
// product keys.
|
|
104
|
-
const active = deps
|
|
105
|
-
.snapshot()
|
|
106
|
-
.purchases.find(
|
|
107
|
-
p =>
|
|
108
|
-
p.productKey === fromProductKey &&
|
|
109
|
-
p.productType === 'subscription' &&
|
|
110
|
-
p.state === 'purchased',
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
if (!active) {
|
|
114
|
-
// No subscription to replace. Treated as a plain purchase would be
|
|
115
|
-
// surprising — the caller asked to *change* something that is not there,
|
|
116
|
-
// which almost always means its own state is stale.
|
|
117
|
-
return { kind: 'already_owned', productKey: fromProductKey };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Routed through the normal purchase flow on purpose: a plan change is a
|
|
121
|
-
// purchase, so it must inherit the single-flight lock, the environment
|
|
122
|
-
// guard, validation, and the finalization queue rather than reimplementing
|
|
123
|
-
// any of them.
|
|
124
|
-
return deps.purchase(toProductKey, {
|
|
125
|
-
...options,
|
|
126
|
-
replaceProductKey: fromProductKey,
|
|
127
|
-
prorationMode: PRORATION_CODES[mode],
|
|
128
|
-
});
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
async openManagement(productKey?: ProductKey): Promise<void> {
|
|
132
|
-
const storeProductId = productKey
|
|
133
|
-
? storeIdForProductKey(resolved, productKey)
|
|
134
|
-
: undefined;
|
|
135
|
-
// No-ops rather than throwing where the platform has no such screen: this
|
|
136
|
-
// is wired to a button, and a button that crashes is worse than one that
|
|
137
|
-
// does nothing on a platform that cannot honour it.
|
|
138
|
-
await deps.provider.openSubscriptionManagement(storeProductId);
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
async confirmPriceChange(productKey): Promise<PriceChangeResult> {
|
|
142
|
-
if (!deps.provider.capabilities().supportsPriceChangeConfirmation) {
|
|
143
|
-
return { kind: 'unsupported' };
|
|
144
|
-
}
|
|
145
|
-
const storeProductId = storeIdForProductKey(resolved, productKey);
|
|
146
|
-
if (!storeProductId) {
|
|
147
|
-
return { kind: 'failed', code: 'PAYMENT_UNKNOWN_PRODUCT' };
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const outcome = await deps.provider.confirmPriceChange(storeProductId);
|
|
151
|
-
if (!outcome.ok) {
|
|
152
|
-
return { kind: 'failed', code: 'PAYMENT_STORE_ERROR' };
|
|
153
|
-
}
|
|
154
|
-
return outcome.confirmed ? { kind: 'confirmed' } : { kind: 'dismissed' };
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Subscription plan changes and the route to the store's management screen (US12).
|
|
3
|
+
*
|
|
4
|
+
* Two platform rules drive this module:
|
|
5
|
+
*
|
|
6
|
+
* - **Proration is the app's decision, inside what the store permits** (FR-065).
|
|
7
|
+
* Whether a user who upgrades gets charged immediately, keeps their remaining
|
|
8
|
+
* time, or starts a fresh period is a product and pricing choice, so the
|
|
9
|
+
* package forwards it rather than picking.
|
|
10
|
+
* - **Cancellation belongs to the store.** Platforms forbid an app presenting its
|
|
11
|
+
* own cancellation screen, so this module offers only a route to the store's
|
|
12
|
+
* (FR-066) — there is deliberately no self-built alternative anywhere in the
|
|
13
|
+
* package.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {
|
|
17
|
+
ProductKey,
|
|
18
|
+
PurchaseOptions,
|
|
19
|
+
PurchaseResult,
|
|
20
|
+
} from '../config/types';
|
|
21
|
+
import type { ResolvedConfig } from '../config/validate';
|
|
22
|
+
import type { ProviderPort } from '../adapter/port';
|
|
23
|
+
import { storeIdForProductKey } from '../entitlements/mapping';
|
|
24
|
+
import type { PersistedSnapshot } from '../persistence/snapshot';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* How already-paid time is handled when a plan changes.
|
|
28
|
+
*
|
|
29
|
+
* Named for the user-visible effect rather than mirroring a store's enum, so the
|
|
30
|
+
* same vocabulary will map onto StoreKit later (FR-004).
|
|
31
|
+
*/
|
|
32
|
+
export type ProrationMode =
|
|
33
|
+
/** Switch now; credit unused time against the new plan. The usual upgrade. */
|
|
34
|
+
| 'immediate_with_time_proration'
|
|
35
|
+
/** Switch now; charge the full new price immediately and restart the period. */
|
|
36
|
+
| 'immediate_and_charge_full_price'
|
|
37
|
+
/** Switch now; charge the prorated difference for the current period. */
|
|
38
|
+
| 'immediate_and_charge_prorated_price'
|
|
39
|
+
/** Keep the current plan until it expires, then start the new one. Usual downgrade. */
|
|
40
|
+
| 'deferred';
|
|
41
|
+
|
|
42
|
+
/** Store-specific replacement codes stay here, behind the neutral vocabulary above. */
|
|
43
|
+
const PRORATION_CODES: Record<ProrationMode, string> = {
|
|
44
|
+
immediate_with_time_proration: 'WITH_TIME_PRORATION',
|
|
45
|
+
immediate_and_charge_full_price: 'CHARGE_FULL_PRICE',
|
|
46
|
+
immediate_and_charge_prorated_price: 'CHARGE_PRORATED_PRICE',
|
|
47
|
+
deferred: 'DEFERRED',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type PriceChangeResult =
|
|
51
|
+
| { kind: 'confirmed' }
|
|
52
|
+
| { kind: 'dismissed' }
|
|
53
|
+
/** The platform has no client-side flow; it notifies the user itself (FR-067). */
|
|
54
|
+
| { kind: 'unsupported' }
|
|
55
|
+
| { kind: 'failed'; code: string };
|
|
56
|
+
|
|
57
|
+
export interface SubscriptionsDeps {
|
|
58
|
+
resolved: ResolvedConfig;
|
|
59
|
+
provider: ProviderPort;
|
|
60
|
+
snapshot: () => PersistedSnapshot;
|
|
61
|
+
purchase: (
|
|
62
|
+
productKey: ProductKey,
|
|
63
|
+
options?: PurchaseOptions & { replaceProductKey?: ProductKey; prorationMode?: string },
|
|
64
|
+
) => Promise<PurchaseResult>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface Subscriptions {
|
|
68
|
+
changePlan(
|
|
69
|
+
fromProductKey: ProductKey,
|
|
70
|
+
toProductKey: ProductKey,
|
|
71
|
+
mode: ProrationMode,
|
|
72
|
+
options?: PurchaseOptions,
|
|
73
|
+
): Promise<PurchaseResult>;
|
|
74
|
+
openManagement(productKey?: ProductKey): Promise<void>;
|
|
75
|
+
confirmPriceChange(productKey: ProductKey): Promise<PriceChangeResult>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createSubscriptions(deps: SubscriptionsDeps): Subscriptions {
|
|
79
|
+
const { resolved } = deps;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
async changePlan(
|
|
83
|
+
fromProductKey,
|
|
84
|
+
toProductKey,
|
|
85
|
+
mode,
|
|
86
|
+
options,
|
|
87
|
+
): Promise<PurchaseResult> {
|
|
88
|
+
if (!deps.provider.capabilities().supportsPlanChange) {
|
|
89
|
+
return { kind: 'unsupported' };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const target = resolved.entries.get(toProductKey);
|
|
93
|
+
if (!target) {
|
|
94
|
+
return { kind: 'unknown_product', productKey: toProductKey };
|
|
95
|
+
}
|
|
96
|
+
if (!resolved.entries.has(fromProductKey)) {
|
|
97
|
+
return { kind: 'unknown_product', productKey: fromProductKey };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The store needs the *token* of the subscription being replaced, not its
|
|
101
|
+
// product id — that is how it knows which active purchase to migrate, and
|
|
102
|
+
// it is why a plan change needs a live local record rather than just two
|
|
103
|
+
// product keys.
|
|
104
|
+
const active = deps
|
|
105
|
+
.snapshot()
|
|
106
|
+
.purchases.find(
|
|
107
|
+
p =>
|
|
108
|
+
p.productKey === fromProductKey &&
|
|
109
|
+
p.productType === 'subscription' &&
|
|
110
|
+
p.state === 'purchased',
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (!active) {
|
|
114
|
+
// No subscription to replace. Treated as a plain purchase would be
|
|
115
|
+
// surprising — the caller asked to *change* something that is not there,
|
|
116
|
+
// which almost always means its own state is stale.
|
|
117
|
+
return { kind: 'already_owned', productKey: fromProductKey };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Routed through the normal purchase flow on purpose: a plan change is a
|
|
121
|
+
// purchase, so it must inherit the single-flight lock, the environment
|
|
122
|
+
// guard, validation, and the finalization queue rather than reimplementing
|
|
123
|
+
// any of them.
|
|
124
|
+
return deps.purchase(toProductKey, {
|
|
125
|
+
...options,
|
|
126
|
+
replaceProductKey: fromProductKey,
|
|
127
|
+
prorationMode: PRORATION_CODES[mode],
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
async openManagement(productKey?: ProductKey): Promise<void> {
|
|
132
|
+
const storeProductId = productKey
|
|
133
|
+
? storeIdForProductKey(resolved, productKey)
|
|
134
|
+
: undefined;
|
|
135
|
+
// No-ops rather than throwing where the platform has no such screen: this
|
|
136
|
+
// is wired to a button, and a button that crashes is worse than one that
|
|
137
|
+
// does nothing on a platform that cannot honour it.
|
|
138
|
+
await deps.provider.openSubscriptionManagement(storeProductId);
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
async confirmPriceChange(productKey): Promise<PriceChangeResult> {
|
|
142
|
+
if (!deps.provider.capabilities().supportsPriceChangeConfirmation) {
|
|
143
|
+
return { kind: 'unsupported' };
|
|
144
|
+
}
|
|
145
|
+
const storeProductId = storeIdForProductKey(resolved, productKey);
|
|
146
|
+
if (!storeProductId) {
|
|
147
|
+
return { kind: 'failed', code: 'PAYMENT_UNKNOWN_PRODUCT' };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const outcome = await deps.provider.confirmPriceChange(storeProductId);
|
|
151
|
+
if (!outcome.ok) {
|
|
152
|
+
return { kind: 'failed', code: 'PAYMENT_STORE_ERROR' };
|
|
153
|
+
}
|
|
154
|
+
return outcome.confirmed ? { kind: 'confirmed' } : { kind: 'dismissed' };
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -134,17 +134,34 @@ function subscriptionDetailFor(
|
|
|
134
134
|
* touch I/O (PERF-001). The engine calls it after any state change and caches the
|
|
135
135
|
* result.
|
|
136
136
|
*/
|
|
137
|
-
|
|
137
|
+
/**
|
|
138
|
+
* How far the snapshot can be trusted right now.
|
|
139
|
+
*
|
|
140
|
+
* Shared by entitlements and the pending set (FR-103) so the two can never
|
|
141
|
+
* disagree about whether the package knows anything — a paywall that showed
|
|
142
|
+
* "not entitled" next to "nothing pending" from one stale snapshot would offer
|
|
143
|
+
* Buy to a user whose payment is in flight.
|
|
144
|
+
*/
|
|
145
|
+
export function snapshotCertainty(input: {
|
|
146
|
+
config: Pick<ResolvedConfig, 'staleAfterMs'>;
|
|
147
|
+
snapshot: Pick<PersistedSnapshot, 'lastSyncedAt'>;
|
|
148
|
+
hasSynced: boolean;
|
|
149
|
+
now: number;
|
|
150
|
+
}): { isStale: boolean; undetermined: boolean } {
|
|
138
151
|
const { config, snapshot, hasSynced, now } = input;
|
|
139
|
-
|
|
140
152
|
const isStale =
|
|
141
153
|
snapshot.lastSyncedAt !== undefined &&
|
|
142
154
|
now - snapshot.lastSyncedAt > config.staleAfterMs;
|
|
143
155
|
|
|
144
|
-
// Both roads lead to `unknown`, never to
|
|
156
|
+
// Both roads lead to `unknown`, never to a confident "no". Never having synced
|
|
145
157
|
// and having synced too long ago are the same epistemic state: we cannot
|
|
146
158
|
// currently support a claim either way.
|
|
147
|
-
|
|
159
|
+
return { isStale, undetermined: !hasSynced || isStale };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function aggregate(input: AggregateInput): EntitlementMap {
|
|
163
|
+
const { config, snapshot } = input;
|
|
164
|
+
const { isStale, undetermined } = snapshotCertainty(input);
|
|
148
165
|
|
|
149
166
|
const valid = snapshot.purchases.filter(p => isValidSource(p, input));
|
|
150
167
|
|
package/src/index.ts
CHANGED
|
@@ -35,6 +35,9 @@ export type {
|
|
|
35
35
|
PaymentConfig,
|
|
36
36
|
PaymentStatus,
|
|
37
37
|
PaymentStatusKind,
|
|
38
|
+
PendingPurchase,
|
|
39
|
+
PendingPurchasesState,
|
|
40
|
+
PendingResolution,
|
|
38
41
|
ProductKey,
|
|
39
42
|
ProductType,
|
|
40
43
|
PurchaseOptions,
|
|
@@ -127,6 +130,7 @@ export {
|
|
|
127
130
|
useEntitlement,
|
|
128
131
|
useEntitlements,
|
|
129
132
|
usePaymentStatus,
|
|
133
|
+
usePendingPurchases,
|
|
130
134
|
useProducts,
|
|
131
135
|
usePurchase,
|
|
132
136
|
useSync,
|
|
@@ -23,10 +23,11 @@ import {
|
|
|
23
23
|
const INSTANCE_ID = 'chipmobilesdk.payment';
|
|
24
24
|
const KEY_PREFIX = 'snapshot';
|
|
25
25
|
|
|
26
|
+
/** The slice of a react-native-mmkv v4 instance this store uses (v4 names). */
|
|
26
27
|
interface MmkvLike {
|
|
27
28
|
getString(key: string): string | undefined;
|
|
28
29
|
set(key: string, value: string): void;
|
|
29
|
-
|
|
30
|
+
remove(key: string): unknown;
|
|
30
31
|
clearAll(): void;
|
|
31
32
|
getAllKeys(): string[];
|
|
32
33
|
}
|
|
@@ -54,21 +55,28 @@ function createMemoryBacking(): MmkvLike {
|
|
|
54
55
|
set(key, value) {
|
|
55
56
|
map.set(key, value);
|
|
56
57
|
},
|
|
57
|
-
delete(key)
|
|
58
|
-
map.delete(key);
|
|
59
|
-
},
|
|
58
|
+
remove: key => map.delete(key),
|
|
60
59
|
clearAll: () => map.clear(),
|
|
61
60
|
getAllKeys: () => [...map.keys()],
|
|
62
61
|
};
|
|
63
62
|
}
|
|
64
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Opens the MMKV instance through v4's `createMMKV`.
|
|
66
|
+
*
|
|
67
|
+
* Up to 0.2.0 this called `new MMKV()`, the v3 constructor. In v4 — the only
|
|
68
|
+
* major the peer range allows — `MMKV` is a type, not a value, so the call threw
|
|
69
|
+
* and the store fell back to memory on every device: entitlements read `unknown`
|
|
70
|
+
* after each cold start, and a pending purchase was forgotten after an app kill.
|
|
71
|
+
* Tests could not see it, because the test double still exported a v3 class.
|
|
72
|
+
*/
|
|
65
73
|
function resolveBacking(): { backing: MmkvLike; durable: boolean } {
|
|
66
74
|
try {
|
|
67
75
|
// Required lazily so the package loads where the native module is absent.
|
|
68
|
-
const
|
|
69
|
-
|
|
76
|
+
const { createMMKV } = require('react-native-mmkv') as {
|
|
77
|
+
createMMKV: (config?: { id?: string }) => MmkvLike;
|
|
70
78
|
};
|
|
71
|
-
return { backing:
|
|
79
|
+
return { backing: createMMKV({ id: INSTANCE_ID }), durable: true };
|
|
72
80
|
} catch {
|
|
73
81
|
return { backing: createMemoryBacking(), durable: false };
|
|
74
82
|
}
|
|
@@ -132,7 +140,7 @@ export function createSnapshotStore(
|
|
|
132
140
|
try {
|
|
133
141
|
for (const key of backing.getAllKeys()) {
|
|
134
142
|
if (key.startsWith(`${KEY_PREFIX}:`)) {
|
|
135
|
-
backing.
|
|
143
|
+
backing.remove(key);
|
|
136
144
|
}
|
|
137
145
|
}
|
|
138
146
|
} catch {
|