@chipmobilesdk/rn-payment 0.4.0 → 0.5.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/CHANGELOG.md +482 -334
- package/README.md +925 -897
- package/package.json +52 -54
- package/skills/sdk-rn-payment/SKILL.md +175 -174
- package/src/adapter/createStoreProvider.ts +116 -118
- package/src/adapter/iap-adapter.ts +627 -660
- package/src/adapter/port.ts +209 -215
- package/src/config/types.ts +298 -310
- package/src/config/validate.ts +224 -232
- package/src/core/mutex.ts +25 -128
- package/src/core/retry.ts +95 -133
- package/src/core/timers.ts +272 -267
- package/src/diagnostics/errors.ts +124 -117
- package/src/diagnostics/events.ts +282 -266
- package/src/diagnostics/messageKeys.ts +93 -95
- package/src/engine/engine.ts +921 -999
- package/src/engine/finalize.ts +343 -346
- package/src/engine/foreground.ts +29 -0
- package/src/engine/products.ts +259 -259
- package/src/engine/purchase.ts +589 -614
- package/src/engine/subscriptions.ts +146 -157
- package/src/entitlements/aggregate.ts +261 -242
- package/src/entitlements/mapping.ts +67 -67
- package/src/entitlements/policy.ts +121 -129
- package/src/index.ts +5 -9
- package/src/persistence/mmkv-store.ts +158 -158
- package/src/persistence/snapshot.ts +220 -247
- package/src/react/PaymentProvider.tsx +90 -79
- package/src/react/hooks.ts +236 -274
- package/src/testing/conformance.ts +169 -0
- package/src/testing/fakeStore.ts +360 -393
- package/src/testing/index.ts +19 -10
package/src/core/retry.ts
CHANGED
|
@@ -1,133 +1,95 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bounded backoff.
|
|
3
|
-
*
|
|
4
|
-
* "Bounded" is the whole point (FR-020, FR-045): an unbounded reconnect loop
|
|
5
|
-
* against a billing service that is down drains battery and hammers a system
|
|
6
|
-
* service, and an unbounded finalize loop hides a real failure behind noise.
|
|
7
|
-
*
|
|
8
|
-
* Delay computation is pure and separated from waiting so tests can assert the
|
|
9
|
-
* schedule without burning real time.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
type
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (cancelled) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
attemptCount += 1;
|
|
100
|
-
let succeeded = false;
|
|
101
|
-
try {
|
|
102
|
-
succeeded = await attempt(attemptCount);
|
|
103
|
-
} catch {
|
|
104
|
-
// A throwing attempt is a failed attempt, nothing more. Letting it escape
|
|
105
|
-
// would abandon the schedule and, for finalization, silently drop a paid
|
|
106
|
-
// transaction.
|
|
107
|
-
succeeded = false;
|
|
108
|
-
}
|
|
109
|
-
if (cancelled || succeeded) {
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
if (attemptCount >= config.maxAttempts) {
|
|
113
|
-
onExhausted?.(attemptCount);
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
schedule(backoffDelayMs(attemptCount + 1, config, random));
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// Fire the first attempt immediately; the schedule owns everything after it.
|
|
120
|
-
run().catch(() => undefined);
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
cancel(): void {
|
|
124
|
-
cancelled = true;
|
|
125
|
-
if (pending !== undefined) {
|
|
126
|
-
clearTimeoutFn(pending);
|
|
127
|
-
pending = undefined;
|
|
128
|
-
}
|
|
129
|
-
},
|
|
130
|
-
attempts: () => attemptCount,
|
|
131
|
-
exhausted: () => attemptCount >= config.maxAttempts,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Bounded backoff.
|
|
3
|
+
*
|
|
4
|
+
* "Bounded" is the whole point (FR-020, FR-045): an unbounded reconnect loop
|
|
5
|
+
* against a billing service that is down drains battery and hammers a system
|
|
6
|
+
* service, and an unbounded finalize loop hides a real failure behind noise.
|
|
7
|
+
*
|
|
8
|
+
* Delay computation is pure and separated from waiting so tests can assert the
|
|
9
|
+
* schedule without burning real time.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
computeBackoff,
|
|
14
|
+
runWithRetry as runCoreRetry,
|
|
15
|
+
type RetryHandle,
|
|
16
|
+
} from '@chipmobilesdk/rn-core';
|
|
17
|
+
|
|
18
|
+
import type { BackoffConfig } from '../config/types';
|
|
19
|
+
|
|
20
|
+
/** Opaque timer handle: a Node Timeout and a browser numeric id are both valid. */
|
|
21
|
+
type TimerHandle = ReturnType<typeof setTimeout> | number;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Delay before `attempt` (1-based), capped and optionally jittered.
|
|
25
|
+
*
|
|
26
|
+
* Jitter is full jitter over [0, capped]: several devices that lost the billing
|
|
27
|
+
* connection at the same moment should not retry in lockstep.
|
|
28
|
+
*/
|
|
29
|
+
export function backoffDelayMs(
|
|
30
|
+
attempt: number,
|
|
31
|
+
config: Required<BackoffConfig>,
|
|
32
|
+
random: () => number = Math.random,
|
|
33
|
+
): number {
|
|
34
|
+
return computeBackoff(attempt, toSpec(config), random);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toSpec(config: Required<BackoffConfig>) {
|
|
38
|
+
return {
|
|
39
|
+
initialDelayMs: config.initialDelayMs,
|
|
40
|
+
maxDelayMs: config.maxDelayMs,
|
|
41
|
+
factor: config.factor,
|
|
42
|
+
jitter: config.jitter ? ('full' as const) : ('none' as const),
|
|
43
|
+
maxAttempts: config.maxAttempts,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** The full schedule, for asserting a retry policy without waiting for it. */
|
|
48
|
+
export function backoffSchedule(config: Required<BackoffConfig>): number[] {
|
|
49
|
+
const delays: number[] = [];
|
|
50
|
+
for (let attempt = 1; attempt <= config.maxAttempts; attempt += 1) {
|
|
51
|
+
delays.push(backoffDelayMs(attempt, config, () => 1));
|
|
52
|
+
}
|
|
53
|
+
return delays;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type { RetryHandle } from '@chipmobilesdk/rn-core';
|
|
57
|
+
|
|
58
|
+
export interface RetryRunnerOptions {
|
|
59
|
+
config: Required<BackoffConfig>;
|
|
60
|
+
/** Return true when the attempt succeeded and retrying should stop. */
|
|
61
|
+
attempt: (attemptNumber: number) => Promise<boolean>;
|
|
62
|
+
/** Called once the per-session budget is spent without success. */
|
|
63
|
+
onExhausted?: (attempts: number) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Injected so a test can run the whole schedule synchronously instead of
|
|
66
|
+
* waiting minutes of real backoff. The handle is opaque, because a Node
|
|
67
|
+
* `Timeout` and a browser numeric id are both valid here.
|
|
68
|
+
*/
|
|
69
|
+
setTimeoutFn?: (fn: () => void, ms: number) => TimerHandle;
|
|
70
|
+
clearTimeoutFn?: (handle: TimerHandle) => void;
|
|
71
|
+
random?: () => number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Runs `attempt` under the bounded schedule (rn-core's runner).
|
|
76
|
+
*
|
|
77
|
+
* Exhaustion is reported rather than thrown: for finalization, a spent session
|
|
78
|
+
* budget is not a failure but a handoff to the next launch, and the caller is
|
|
79
|
+
* the only one that knows which of the two it is. A throwing attempt is a failed
|
|
80
|
+
* attempt, nothing more: letting it escape would silently drop a paid transaction.
|
|
81
|
+
*/
|
|
82
|
+
export function runWithRetry(options: RetryRunnerOptions): RetryHandle {
|
|
83
|
+
const setTimeoutFn = options.setTimeoutFn ?? setTimeout;
|
|
84
|
+
const clearTimeoutFn = options.clearTimeoutFn ?? clearTimeout;
|
|
85
|
+
return runCoreRetry({
|
|
86
|
+
spec: toSpec(options.config),
|
|
87
|
+
attempt: options.attempt,
|
|
88
|
+
onExhausted: options.onExhausted,
|
|
89
|
+
random: options.random,
|
|
90
|
+
scheduler: {
|
|
91
|
+
setTimeout: (callback, ms) => setTimeoutFn(callback, ms),
|
|
92
|
+
clearTimeout: handle => clearTimeoutFn(handle as TimerHandle),
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
}
|