@mpgd/adapter-capacitor 0.7.5 → 0.8.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/README.md +13 -9
- package/dist/app-store-server.d.ts +12 -12
- package/dist/app-store-server.js +10 -119
- package/dist/providers.d.ts +6 -1
- package/dist/providers.js +7 -0
- package/package.json +7 -12
package/README.md
CHANGED
|
@@ -66,15 +66,19 @@ SDK works on a physical device or that a store/ad setup is release-ready.
|
|
|
66
66
|
|
|
67
67
|
## App Store server-side transaction signatures
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
`
|
|
72
|
-
`@mpgd/
|
|
73
|
-
|
|
74
|
-
the
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
The server-side verifier moved to `@mpgd/game-services/app-store-server`
|
|
70
|
+
(see the [game-services README](../../packages/game-services/README.md)):
|
|
71
|
+
install the optional `@apple/app-store-server-library@3.1.0` peer next to
|
|
72
|
+
`@mpgd/game-services` and import `createAppleSignedTransactionVerifier` from
|
|
73
|
+
there. `@mpgd/adapter-capacitor/app-store-server` is deprecated: it re-exports
|
|
74
|
+
the same function and options type for one release and is removed in the next
|
|
75
|
+
major version. Supply DER-encoded Apple root certificates from the
|
|
76
|
+
[Apple PKI](https://www.apple.com/certificateauthority/), the game's bundle
|
|
77
|
+
ID, environment, and production App Apple ID. Use the result as
|
|
78
|
+
`signedTransactionVerifier` in `createAppStoreGameServicesEvidenceVerifier`.
|
|
79
|
+
Keep App Store API credentials, trust roots, and this verifier on the backend,
|
|
80
|
+
not in the Capacitor game; the adapter itself no longer peers on Apple's
|
|
81
|
+
library.
|
|
78
82
|
|
|
79
83
|
The server fetches the transaction from Apple's API, verifies its JWS, checks
|
|
80
84
|
the product, bundle, environment, account token and transaction ID, then
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
export
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Moved to `@mpgd/game-services/app-store-server` in A4 of the
|
|
3
|
+
* commerce v2 plan: server-side App Store verification does not belong in the
|
|
4
|
+
* client adapter. This subpath re-exports the same symbols for one release and
|
|
5
|
+
* will be removed in the next major version. Install
|
|
6
|
+
* `@apple/app-store-server-library` next to `@mpgd/game-services`.
|
|
7
|
+
*/
|
|
8
|
+
import { createAppleSignedTransactionVerifier as createAppleSignedTransactionVerifierFromGameServices, type CreateAppleSignedTransactionVerifierOptions as GameServicesAppleVerifierOptions } from '@mpgd/game-services/app-store-server';
|
|
9
|
+
/** @deprecated Import `createAppleSignedTransactionVerifier` from `@mpgd/game-services/app-store-server`. */
|
|
10
|
+
export declare const createAppleSignedTransactionVerifier: typeof createAppleSignedTransactionVerifierFromGameServices;
|
|
11
|
+
/** @deprecated Import `CreateAppleSignedTransactionVerifierOptions` from `@mpgd/game-services/app-store-server`. */
|
|
12
|
+
export type CreateAppleSignedTransactionVerifierOptions = GameServicesAppleVerifierOptions;
|
package/dist/app-store-server.js
CHANGED
|
@@ -1,119 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (options.environment !== 'Production' && options.environment !== 'Sandbox') {
|
|
12
|
-
throw new TypeError('App Store environment must be Production or Sandbox.');
|
|
13
|
-
}
|
|
14
|
-
if (options.bundleId.trim() === '') {
|
|
15
|
-
throw new TypeError('App Store bundle ID is required.');
|
|
16
|
-
}
|
|
17
|
-
if (options.environment === 'Production'
|
|
18
|
-
&& (!Number.isSafeInteger(options.appAppleId) || (options.appAppleId ?? 0) <= 0)) {
|
|
19
|
-
throw new TypeError('Production App Store verification requires appAppleId.');
|
|
20
|
-
}
|
|
21
|
-
if (options.environment === 'Production' && options.enableOnlineChecks === false) {
|
|
22
|
-
throw new TypeError('Production App Store verification requires online certificate checks.');
|
|
23
|
-
}
|
|
24
|
-
const verifier = new SignedDataVerifier(options.rootCertificates.map((certificate) => Buffer.from(certificate)), options.enableOnlineChecks ?? true, options.environment === 'Production' ? Environment.PRODUCTION : Environment.SANDBOX, options.bundleId, options.appAppleId);
|
|
25
|
-
return {
|
|
26
|
-
async verifyAndDecode(input) {
|
|
27
|
-
if (input.environment !== options.environment || input.bundleId !== options.bundleId) {
|
|
28
|
-
return { status: 'rejected', reason: 'APP_STORE_VERIFIER_SCOPE_MISMATCH' };
|
|
29
|
-
}
|
|
30
|
-
if (typeof input.signedTransaction !== 'string'
|
|
31
|
-
|| input.signedTransaction.length === 0
|
|
32
|
-
|| input.signedTransaction.length > 512 * 1024) {
|
|
33
|
-
return { status: 'rejected', reason: 'APP_STORE_SIGNED_TRANSACTION_INVALID' };
|
|
34
|
-
}
|
|
35
|
-
input.signal.throwIfAborted();
|
|
36
|
-
let decoded;
|
|
37
|
-
try {
|
|
38
|
-
decoded = await waitForVerification(verifier.verifyAndDecodeTransaction(input.signedTransaction), input.signal);
|
|
39
|
-
input.signal.throwIfAborted();
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
if (input.signal.aborted) {
|
|
43
|
-
throw input.signal.reason;
|
|
44
|
-
}
|
|
45
|
-
if (error instanceof VerificationException) {
|
|
46
|
-
if (error.status === VerificationStatus.RETRYABLE_VERIFICATION_FAILURE) {
|
|
47
|
-
throw new AppStoreDependencyUnavailableError('Apple signature checks are temporarily unavailable.', error);
|
|
48
|
-
}
|
|
49
|
-
return { status: 'rejected', reason: 'APP_STORE_SIGNATURE_INVALID' };
|
|
50
|
-
}
|
|
51
|
-
// Only Apple's explicit retryable status is a transient dependency
|
|
52
|
-
// failure. Unexpected adapter/library faults must reach the caller.
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
const payload = decodeOneTimeTransaction(decoded);
|
|
56
|
-
return payload === undefined
|
|
57
|
-
? { status: 'rejected', reason: 'APP_STORE_SIGNED_TRANSACTION_INVALID' }
|
|
58
|
-
: { status: 'verified', payload };
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
async function waitForVerification(operation, signal) {
|
|
63
|
-
signal.throwIfAborted();
|
|
64
|
-
let onAbort;
|
|
65
|
-
const aborted = new Promise((_resolve, reject) => {
|
|
66
|
-
onAbort = () => reject(signal.reason ?? new Error('Verification was aborted.'));
|
|
67
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
68
|
-
if (signal.aborted) {
|
|
69
|
-
onAbort();
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
try {
|
|
73
|
-
return await Promise.race([operation, aborted]);
|
|
74
|
-
}
|
|
75
|
-
finally {
|
|
76
|
-
if (onAbort !== undefined) {
|
|
77
|
-
signal.removeEventListener('abort', onAbort);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
function decodeOneTimeTransaction(decoded) {
|
|
82
|
-
if (!nonEmpty(decoded.transactionId)
|
|
83
|
-
|| !nonEmpty(decoded.originalTransactionId)
|
|
84
|
-
|| !nonEmpty(decoded.bundleId)
|
|
85
|
-
|| !nonEmpty(decoded.productId)
|
|
86
|
-
|| !validTimestamp(decoded.purchaseDate)
|
|
87
|
-
|| !validTimestamp(decoded.signedDate)
|
|
88
|
-
|| (decoded.environment !== 'Production' && decoded.environment !== 'Sandbox')
|
|
89
|
-
|| (decoded.type !== 'Consumable' && decoded.type !== 'Non-Consumable')
|
|
90
|
-
|| (decoded.quantity !== undefined
|
|
91
|
-
&& (!Number.isSafeInteger(decoded.quantity) || decoded.quantity <= 0))
|
|
92
|
-
|| (decoded.appAccountToken !== undefined && !nonEmpty(decoded.appAccountToken))
|
|
93
|
-
|| (decoded.revocationDate !== undefined && !validTimestamp(decoded.revocationDate))
|
|
94
|
-
|| (decoded.expiresDate !== undefined && !validTimestamp(decoded.expiresDate))
|
|
95
|
-
|| (decoded.isUpgraded !== undefined && typeof decoded.isUpgraded !== 'boolean')) {
|
|
96
|
-
return undefined;
|
|
97
|
-
}
|
|
98
|
-
return {
|
|
99
|
-
transactionId: decoded.transactionId,
|
|
100
|
-
originalTransactionId: decoded.originalTransactionId,
|
|
101
|
-
bundleId: decoded.bundleId,
|
|
102
|
-
productId: decoded.productId,
|
|
103
|
-
purchaseDate: decoded.purchaseDate,
|
|
104
|
-
signedDate: decoded.signedDate,
|
|
105
|
-
environment: decoded.environment,
|
|
106
|
-
type: decoded.type,
|
|
107
|
-
...(decoded.quantity === undefined ? {} : { quantity: decoded.quantity }),
|
|
108
|
-
...(decoded.appAccountToken === undefined ? {} : { appAccountToken: decoded.appAccountToken }),
|
|
109
|
-
...(decoded.revocationDate === undefined ? {} : { revocationDate: decoded.revocationDate }),
|
|
110
|
-
...(decoded.expiresDate === undefined ? {} : { expiresDate: decoded.expiresDate }),
|
|
111
|
-
...(decoded.isUpgraded === undefined ? {} : { isUpgraded: decoded.isUpgraded }),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function nonEmpty(value) {
|
|
115
|
-
return typeof value === 'string' && value.trim() !== '';
|
|
116
|
-
}
|
|
117
|
-
function validTimestamp(value) {
|
|
118
|
-
return typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
119
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated Moved to `@mpgd/game-services/app-store-server` in A4 of the
|
|
3
|
+
* commerce v2 plan: server-side App Store verification does not belong in the
|
|
4
|
+
* client adapter. This subpath re-exports the same symbols for one release and
|
|
5
|
+
* will be removed in the next major version. Install
|
|
6
|
+
* `@apple/app-store-server-library` next to `@mpgd/game-services`.
|
|
7
|
+
*/
|
|
8
|
+
import { createAppleSignedTransactionVerifier as createAppleSignedTransactionVerifierFromGameServices, } from '@mpgd/game-services/app-store-server';
|
|
9
|
+
/** @deprecated Import `createAppleSignedTransactionVerifier` from `@mpgd/game-services/app-store-server`. */
|
|
10
|
+
export const createAppleSignedTransactionVerifier = createAppleSignedTransactionVerifierFromGameServices;
|
package/dist/providers.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BridgeEvent, type BridgeMethod, type BridgeRequest, type BridgeResponse } from '@mpgd/bridge';
|
|
2
2
|
import type { AdProvider } from '@mpgd/platform/ads';
|
|
3
3
|
import { type PlatformProviderAvailability, type PlatformProviderFeature, type ProductType } from '@mpgd/platform';
|
|
4
4
|
export interface NativeBridge {
|
|
5
5
|
request(input: BridgeRequest): Promise<BridgeResponse>;
|
|
6
|
+
/**
|
|
7
|
+
* Optional commerce v2 event delivery (`{ protocol: 'mpgd.commerce.v2', sequence, event }`).
|
|
8
|
+
* B1 wires it for store providers; the base gateway works without it.
|
|
9
|
+
*/
|
|
10
|
+
subscribe?(listener: (event: BridgeEvent) => void): () => void;
|
|
6
11
|
}
|
|
7
12
|
/**
|
|
8
13
|
* An optional, separately installed provider handles only its declared bridge
|
package/dist/providers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isBridgeCommerceV2Method, } from '@mpgd/bridge';
|
|
1
2
|
import { isAdMobClientRewardEvidence } from '@mpgd/game-services/admob-client-reward';
|
|
2
3
|
import { PlatformOperationError, } from '@mpgd/platform';
|
|
3
4
|
const featureMethods = {
|
|
@@ -53,6 +54,12 @@ export function createCapacitorProviderRegistry(input) {
|
|
|
53
54
|
throw new Error(`Capacitor provider ${provider.id} repeats a bridge method.`);
|
|
54
55
|
}
|
|
55
56
|
for (const method of methods) {
|
|
57
|
+
if (isBridgeCommerceV2Method(method)) {
|
|
58
|
+
// v1 providers own the v1 commerce methods only; a v2 provider arrives
|
|
59
|
+
// with B1 and is registered through its own surface.
|
|
60
|
+
throw new Error(`Capacitor provider ${provider.id} cannot declare commerce v2 method ${method}; `
|
|
61
|
+
+ 'commerce v2 is unavailable through the provider registry.');
|
|
62
|
+
}
|
|
56
63
|
if (!providerMethodSet.has(method)) {
|
|
57
64
|
throw new Error(`Capacitor provider ${provider.id} cannot replace base method ${method}.`);
|
|
58
65
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mpgd/adapter-capacitor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Capacitor platform adapter for mpgd Android and iOS shells.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -43,33 +43,28 @@
|
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"@capacitor/app": "8.1.1",
|
|
46
|
-
"@mpgd/capacitor-game-services": "0.6.
|
|
47
|
-
"@mpgd/bridge": "0.
|
|
48
|
-
"@mpgd/platform": "0.
|
|
49
|
-
"@mpgd/game-services": "0.
|
|
50
|
-
"@mpgd/target-config": "0.21.
|
|
46
|
+
"@mpgd/capacitor-game-services": "0.6.2",
|
|
47
|
+
"@mpgd/bridge": "0.12.0",
|
|
48
|
+
"@mpgd/platform": "0.19.0",
|
|
49
|
+
"@mpgd/game-services": "0.20.0",
|
|
50
|
+
"@mpgd/target-config": "0.21.2"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@apple/app-store-server-library": "^3.1.0",
|
|
54
53
|
"@capacitor-community/admob": "^8.1.0",
|
|
55
54
|
"@capacitor/core": "^8.5.1"
|
|
56
55
|
},
|
|
57
56
|
"peerDependenciesMeta": {
|
|
58
|
-
"@apple/app-store-server-library": {
|
|
59
|
-
"optional": true
|
|
60
|
-
},
|
|
61
57
|
"@capacitor-community/admob": {
|
|
62
58
|
"optional": true
|
|
63
59
|
}
|
|
64
60
|
},
|
|
65
61
|
"devDependencies": {
|
|
66
|
-
"@apple/app-store-server-library": "3.1.0",
|
|
67
62
|
"@capacitor-community/admob": "8.1.0",
|
|
68
63
|
"@capacitor/core": "8.5.2",
|
|
69
64
|
"ttsc": "0.30.4",
|
|
70
65
|
"typescript": "7.0.2",
|
|
71
66
|
"vitest": "^4.0.0",
|
|
72
|
-
"@mpgd/game-runtime": "0.6.
|
|
67
|
+
"@mpgd/game-runtime": "0.6.2"
|
|
73
68
|
},
|
|
74
69
|
"main": "./dist/index.js",
|
|
75
70
|
"types": "./dist/index.d.ts",
|