@tinkerlandai/indiana-sdk 0.10.1 → 0.10.2
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 +7 -0
- package/README.md +28 -28
- package/README.zh-CN.md +22 -22
- package/dist/{chunk-ABNSU3ZG.js → chunk-CWHJKWOD.js} +1 -1
- package/dist/chunk-EARSGITJ.js +1 -0
- package/dist/{chunk-WYZI3TNB.js → chunk-FYGXGMRB.js} +1 -1
- package/dist/chunk-GHJ65ILJ.js +1 -0
- package/dist/chunk-JPV7F5XI.js +1 -0
- package/dist/{chunk-QUWPPZHH.js → chunk-LCLCG2I2.js} +1 -1
- package/dist/{chunk-2Y4G5364.js → chunk-Q64VNSPV.js} +3 -3
- package/dist/{chunk-CCPNMQHZ.js → chunk-V3RAOCVQ.js} +1 -1
- package/dist/{chunk-EDQCH2QS.js → chunk-VZVUZWP2.js} +1 -1
- package/dist/{chunk-AHXYGGAA.js → chunk-XO42OH3M.js} +1 -1
- package/dist/{configuration-sync-CXe9Zcng.d.ts → configuration-sync-CywDxRkX.d.ts} +1 -1
- package/dist/{content-ZRWZ26PF.js → content-QM75T7JO.js} +1 -1
- package/dist/{funnel-local-client-BQsdOjQ7.d.ts → funnel-local-client-DXamAj1-.d.ts} +2 -2
- package/dist/{funnel-resource-client-Zof1WLgA.d.ts → funnel-resource-client-md62M8Rv.d.ts} +2 -2
- package/dist/funnel-runtime-host-BJZUVJAI.js +1 -0
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/native-shell.js +1 -1
- package/dist/openrouter.js +1 -1
- package/dist/page.d.ts +2 -2
- package/dist/page.js +1 -1
- package/dist/{paywall-DR65VN2G.js → paywall-527RPFAX.js} +1 -1
- package/dist/react-native.d.ts +5 -5
- package/dist/react-native.js +1 -1
- package/dist/server.d.ts +5 -5
- package/dist/server.js +1 -1
- package/dist/visual.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-6ZZUNGNE.js +0 -1
- package/dist/chunk-IWWKVNEX.js +0 -1
- package/dist/chunk-Y46ALAKP.js +0 -1
- package/dist/funnel-runtime-host-N7TX3LWI.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.2 — 2026-10-30
|
|
4
|
+
|
|
5
|
+
- Move the default production API to `https://api.longplayai.com` in all six SDKs; use `https://api.dev.longplayai.com` explicitly for the test deployment. Wire protocol remains `0.10`.
|
|
6
|
+
- Move the Go module to `longplayai.com/sdk/go`. Update imports and module requirements; the old module path has no compatibility alias.
|
|
7
|
+
- Update visual diagnostics to trust the new Console origins and regenerate bundled native and browser resources.
|
|
8
|
+
- Dismiss Funnel presentations when their Run ends elsewhere and release the corresponding IAM entry host.
|
|
9
|
+
|
|
3
10
|
Release facts and artifact evidence: [SDK release ledger](../../docs/sdk-releases.md).
|
|
4
11
|
|
|
5
12
|
## 0.10.1 — 2026-10-29
|
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# @tinkerlandai/indiana-sdk
|
|
2
2
|
|
|
3
|
-
This source checkout is SDK 0.10.
|
|
3
|
+
This source checkout is SDK 0.10.2, protocol 0.10. These examples use `https://api.longplayai.com` with a Sandbox App and credentials issued by the production deployment. Sandbox still contains production data; do not reuse credentials from the separate test deployment. Confirm runtime readiness in [environment state](../../docs/development-status.md) before running them.
|
|
4
4
|
|
|
5
5
|
[简体中文](./README.zh-CN.md)
|
|
6
6
|
|
|
7
|
-
The
|
|
7
|
+
The Longplay business SDK groups its capabilities as **Identity, Commerce, experimentation engine, Analytics, Delivery (Offering, Paywall, Page, Popup and Banner), and Funnel**, over shared protocol, transport, configuration, rendering and recovery foundations. These responsibilities follow the existing L0–L4 architecture; language packages and entry points implement different subsets. The browser/native client and merchant-server entries share contracts and core behavior while retaining separate credentials and allowed operations. Indianapolis is the backend runtime they call; it is not the merchant's server SDK.
|
|
8
8
|
|
|
9
9
|
**IAM source scanning, generation, validation and upload belong to the independent build package `@tinkerlandai/indiana-iam`.** This business SDK retains only the runtime release binding used by GB visual changes. See [SDK boundaries](../../docs/sdk-architecture.md) and the [IAM package](../iam/README.md).
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install --save-exact @tinkerlandai/indiana-sdk@0.
|
|
12
|
+
npm install --save-exact @tinkerlandai/indiana-sdk@0.10.2
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
This SDK requires HTTP protocol 0.
|
|
15
|
+
This SDK requires HTTP protocol 0.10. The runtime must serve the same current protocol; publishing a package does not deploy the server. Publication and accepted environment scope are recorded in the [release ledger](../../docs/sdk-releases.md) and [development status](../../docs/development-status.md).
|
|
16
16
|
|
|
17
17
|
The data/session layer handles public JSON, integrity checks and authenticated operations. The presentation host composes that layer with the packaged screen renderer; the renderer receives supplied data and emits action intents. Client display entry points load presentation lazily. The `/server` entry has no screen presentation dependency and uses a scoped Server Key only on your backend. A local SDK guard never replaces runtime authorization or verified payment completion. Console preview supplies simulated data/session results to the same presentation host.
|
|
18
18
|
|
|
@@ -21,7 +21,7 @@ The data/session layer handles public JSON, integrity checks and authenticated o
|
|
|
21
21
|
The classic-script entry reuses the npm browser implementation, with no bundler:
|
|
22
22
|
|
|
23
23
|
```html
|
|
24
|
-
<script src="https://
|
|
24
|
+
<script src="https://longplayai.com/sdk.js"></script>
|
|
25
25
|
<script type="module">
|
|
26
26
|
const {
|
|
27
27
|
createIndiana,
|
|
@@ -32,7 +32,7 @@ The classic-script entry reuses the npm browser implementation, with no bundler:
|
|
|
32
32
|
mountContent,
|
|
33
33
|
} = await Indiana.ready;
|
|
34
34
|
const client = createIndiana({
|
|
35
|
-
baseUrl: "https://api.
|
|
35
|
+
baseUrl: "https://api.longplayai.com",
|
|
36
36
|
scope: { projectId: "<APP_UUID>", environment: "sandbox" },
|
|
37
37
|
});
|
|
38
38
|
// Obtain the complete session pair from your authenticated backend.
|
|
@@ -43,7 +43,7 @@ The classic-script entry reuses the npm browser implementation, with no bundler:
|
|
|
43
43
|
</script>
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Production uses `https://
|
|
46
|
+
Production uses `https://longplayai.com/sdk.js` and its matching API/credentials. `Indiana.ready` provides every root browser export plus `createPageExperiments` and `createVisualExperiments`; replace npm imports below with destructuring those names. Loading activates no sessions, events or experiments. Server Keys remain on your backend; Funnel start/recovery and logout cleanup retain the existing contracts. Duplicate tags share readiness, failures reject the Promise and UI loads lazily. With `async`/`defer`, wait for the loader tag's load event before accessing `Indiana`. CSP must allow the script origin and API connections. Page experiments use the same script entry; explicitly create their clients and call `start()`. See [script distribution](../../docs/sdk-architecture.md#web-script-distribution) and [the complete integration example](https://longplayai.com/docs/javascript#script).
|
|
47
47
|
|
|
48
48
|
## React Native core
|
|
49
49
|
|
|
@@ -51,7 +51,7 @@ Use the independent `@tinkerlandai/indiana-react-native` package. The `@tinkerla
|
|
|
51
51
|
|
|
52
52
|
## Version compatibility and support
|
|
53
53
|
|
|
54
|
-
During `0.x`, only the current protocol is supported. Starting with `1.0`, compatibility is preserved within each stable major version. Once the next stable major is published, the preceding stable major remains supported for at least 12 calendar months, with at least 180 days' public notice before retirement. The later deadline applies. See the [support policy](https://
|
|
54
|
+
During `0.x`, only the current protocol is supported. Starting with `1.0`, compatibility is preserved within each stable major version. Once the next stable major is published, the preceding stable major remains supported for at least 12 calendar months, with at least 180 days' public notice before retirement. The later deadline applies. See the [support policy](https://longplayai.com/docs/release-notes#support-policy).
|
|
55
55
|
|
|
56
56
|
The SDK sends current protocol and SDK version headers and validates the protocol declared by successful responses. Old 0.x protocols are not converted into the current contract. Updating source code does not change an existing npm artifact or deploy a server.
|
|
57
57
|
|
|
@@ -72,17 +72,17 @@ The same package supports JavaScript and TypeScript and includes type declaratio
|
|
|
72
72
|
|
|
73
73
|
For an independent **Page, Popup or Banner resource**, import `createIndiana` and `mountContent` from the root entry, resolve it with `indiana.resolveContent(...)`, then mount the result. `/page` runs GB URL redirect experiments on an existing merchant page; `/visual` applies GB visual changes to that page. `/popup` exposes lower-level screen rendering and preview primitives. Its name does not select or resolve a Popup resource. The public import paths stay unchanged; see [entry ownership](../../docs/sdk-architecture.md#public-entry-points-and-capability-ownership).
|
|
74
74
|
|
|
75
|
-
The separate [Python server SDK](../python/README.md) provides synchronous and asynchronous Customer, holdings, entitlement-check and usage clients against the same current HTTP protocol. It is distributed from `https://
|
|
75
|
+
The separate [Python server SDK](../python/README.md) provides synchronous and asynchronous Customer, holdings, entitlement-check and usage clients against the same current HTTP protocol. It is distributed from `https://longplayai.com/sdk/python` (not PyPI) and is not part of this npm package.
|
|
76
76
|
|
|
77
|
-
The [Go server SDK](../go/README.md) provides the same initial capabilities with typed responses, Context cancellation and goroutine-safe client reuse. It is installed with `go get
|
|
77
|
+
The [Go server SDK](../go/README.md) provides the same initial capabilities with typed responses, Context cancellation and goroutine-safe client reuse. It is installed with `go get longplayai.com/sdk/go` through the default Go module proxy and shares the release version with the other business SDKs.
|
|
78
78
|
|
|
79
79
|
## API origin and test services
|
|
80
80
|
|
|
81
81
|
The canonical repository policy, including production/test domain roles, package tags and test-host configuration, is [SDK API entry and development testing](../../docs/sdk-endpoints-and-testing.md).
|
|
82
82
|
|
|
83
|
-
Omitting `baseUrl` selects `https://api.
|
|
83
|
+
Omitting `baseUrl` selects `https://api.longplayai.com`. This applies to Customer/Server, Funnel, Feature, Paywall, page and visual clients. `createConfigurationSync` also defaults its optional `origin` to that address. Swift `IndianaConfiguration(scope: ...)` and Kotlin `IndianaConfiguration(scope = ...)` uses the same default and shared release version. Explicit `baseURL` / `baseUrl` still overrides it. Funnel and Paywall shells and renderers are packaged locally; their executable code is not downloaded from that API origin.
|
|
84
84
|
|
|
85
|
-
Both `beta` and `latest` versions published with this default use the production API address. Tags select package versions, not deployments. The separate test API at `https://api.dev.
|
|
85
|
+
Both `beta` and `latest` versions published with this default use the production API address. Tags select package versions, not deployments. The separate test API at `https://api.dev.longplayai.com` requires its own credentials and a runtime serving the current protocol 0.10.
|
|
86
86
|
|
|
87
87
|
Use the same API origin for every client and configuration-sync instance in one integration. Sandbox/Live selects business scope inside that deployment, not a different service or database. Never retry against another service automatically after an error or unknown command result. Maintainer test runs require an explicit target and must not fall back to production; follow the [SDK testing guide](../../docs/sdk-selfserve.md#maintainer-api-origin-testing).
|
|
88
88
|
|
|
@@ -143,7 +143,7 @@ await indiana.createGuestCustomer({
|
|
|
143
143
|
});
|
|
144
144
|
```
|
|
145
145
|
|
|
146
|
-
|
|
146
|
+
Longplay keeps the first supplied signal as an environment-scoped hash; later values do not replace it. Missing input is valid and retains per-Customer trial deduplication. `unique_fingerprint` additionally deduplicates the configured Product trial across Customers sharing the signal. Business user IDs do not identify an installation; Customer/Guest/session tokens and request IDs have their own lifecycles and should not be fingerprints.
|
|
147
147
|
|
|
148
148
|
On Web, an independent first-party installation cookie can retain the signal. Clearing it or changing browser storage can produce a new value; sign-out need not reset it. Native hosts persist a random installation signal and define reinstall, restore and reset behavior. This signal is separate from device attestation and authentication. Trial conflicts remain generic to clients; retain `X-Request-Id` for server-side diagnosis.
|
|
149
149
|
|
|
@@ -161,9 +161,9 @@ Each Funnel chooses local or remote evaluation. `createFunnelDecisionClient` sup
|
|
|
161
161
|
|
|
162
162
|
### Bucketing identity across independent consumers
|
|
163
163
|
|
|
164
|
-
Funnel, standalone Features and page experiments evaluate independently. Matching App/environment, experiment definition, attributes, hash identity and Sticky state produce matching results; different refresh times or inputs may produce different variations. A Remote Funnel still evaluates on
|
|
164
|
+
Funnel, standalone Features and page experiments evaluate independently. Matching App/environment, experiment definition, attributes, hash identity and Sticky state produce matching results; different refresh times or inputs may produce different variations. A Remote Funnel still evaluates on Longplay when its referenced code Experiment allows Local definition delivery. Accepted Funnel decisions retain their original results. There is no cross-feature result synchronization.
|
|
165
165
|
|
|
166
|
-
For Customer-based evaluation, `id` means the
|
|
166
|
+
For Customer-based evaluation, `id` means the Longplay Customer UUID (`customer.id`); `externalUserId` means the merchant account ID (`customer.externalUserId`). Choose the experiment's `hashAttribute` explicitly when creating it and preserve that meaning in every consumer. Do not put the merchant account ID into `attributes.id`. Supply local attributes from your trusted backend's Customer data; `identify(customerToken)` authenticates requests but does not populate local bucketing attributes. These attributes are evaluation data, not authorization.
|
|
167
167
|
|
|
168
168
|
`setAttributes()` replaces the complete object, so retain the bucketing fields on every update. Anonymous page/visual experiments instead default `id` and `deviceId` to the shared first-party device cookie; they do not automatically match an account-based experiment. Sticky capabilities differ by adapter: managed Local Run instructions receive server state, while pure local tools require their existing host persistence adapter for durable Sticky. No new shared state is introduced. See the [architecture boundary](../../docs/growthbook-sdk-delivery.md#跨消费方求值边界与分桶身份).
|
|
169
169
|
|
|
@@ -255,7 +255,7 @@ verify the native user before issuing credentials; reset the client and destroy
|
|
|
255
255
|
|
|
256
256
|
The server implements Customer/Guest identity, Entitlement checks, holdings and ledger reads, usage and balances, Coupons/referral codes, Offering/standalone Paywall, and configured Stripe Sandbox attach, payment-method collection, subscription validation/updates/refunds/Trial, and License allocation. Each operation depends on configured payment accounts, execution mode, Product/Price terms, and its implemented scope. An API's presence does not certify every combination.
|
|
257
257
|
|
|
258
|
-
In Stripe environments,
|
|
258
|
+
In Stripe environments, Longplay executes `indiana` mode; `record_only` returns instructions for the merchant backend to execute. Both record financial and Entitlement facts from verified Stripe evidence. Collecting a payment method does not itself charge, create an Invoice, or grant extra Entitlements. Quotes, confirmations, and reads retain their durable operation IDs; a request ID cannot be reused with different input.
|
|
259
259
|
|
|
260
260
|
This beta does not certify complete commercial or production operation. Remaining service gaps include:
|
|
261
261
|
|
|
@@ -274,7 +274,7 @@ The server entry provides AI SDK usage middleware; the OpenRouter entry provides
|
|
|
274
274
|
|
|
275
275
|
Publishing to npm does not deploy Web services, apply database migrations, or release Swift/Kotlin packages. Package installation, types, offline unit tests, and builds are recorded separately from real Auth/RLS, Stripe, native stores, browser checks, and hosted deployment acceptance.
|
|
276
276
|
|
|
277
|
-
See the [SDK release ledger](https://github.com/tinkerlandai/Indiana/blob/main/docs/sdk-releases.md) for versions and immutable publication evidence, and the [developer documentation](https://
|
|
277
|
+
See the [SDK release ledger](https://github.com/tinkerlandai/Indiana/blob/main/docs/sdk-releases.md) for versions and immutable publication evidence, and the [developer documentation](https://longplayai.com/docs?lang=en) for integration guides.
|
|
278
278
|
|
|
279
279
|
## Shared public configuration
|
|
280
280
|
|
|
@@ -287,7 +287,7 @@ import {
|
|
|
287
287
|
} from "@tinkerlandai/indiana-sdk";
|
|
288
288
|
|
|
289
289
|
// Use a Sandbox App and credentials issued by the production deployment.
|
|
290
|
-
const apiUrl = "https://api.
|
|
290
|
+
const apiUrl = "https://api.longplayai.com";
|
|
291
291
|
const scope = { projectId, environment: "sandbox" as const, environmentId };
|
|
292
292
|
const configuration = createConfigurationSync({
|
|
293
293
|
origin: apiUrl,
|
|
@@ -308,13 +308,13 @@ configuration.destroy(); // Host owns the shared lifetime.
|
|
|
308
308
|
|
|
309
309
|
Pass `configuration` to Funnel start/Run helpers or `createPageExperiments` as well. On your backend, `createSynchronizedFunnelLocalEvaluator(configuration, funnelId)` from `/server` uses the same canonical file/GB generation for pure advisory evaluation. Public bytes never authorize business eligibility or commit a Run transition.
|
|
310
310
|
|
|
311
|
-
Initialization has a five-second total wait budget. Active subscriptions share 12–15-second polling, retries and foreground/reconnect refresh. Local control expires at most 60 seconds after origin validation; expiry or observed pause suspends local evaluation/screens. Cold offline initialization cannot restore active permission from storage. Explicit
|
|
311
|
+
Initialization has a five-second total wait budget. Active subscriptions share 12–15-second polling, retries and foreground/reconnect refresh. Local control expires at most 60 seconds after origin validation; expiry or observed pause suspends local evaluation/screens. Cold offline initialization cannot restore active permission from storage. Explicit Longplay Run commands and observations obtain current state from Longplay and bypass CDN initialization and freshness.
|
|
312
312
|
|
|
313
313
|
Optional browser persistence defaults to localStorage and contains only public bytes: 128 entries / 2 MiB / seven days, with a 128-entry / 32 MiB memory cache. Use `storage: null` to disable persistence or supply a `ConfigurationStorage` adapter. Quota errors fall back to memory. Each artifact is limited to 1 MiB, manifests to 512 KiB, and all files are scope/schema/hash checked. Keep credentials, attributes, Sticky records and accepted decisions in their separate existing stores.
|
|
314
314
|
|
|
315
315
|
## Local Funnel execution
|
|
316
316
|
|
|
317
|
-
Local and Remote share the authoritative
|
|
317
|
+
Local and Remote share the authoritative Longplay Run, command/action receipts, business services and Queue recovery. A Local host predicts pure Condition/Feature/Experiment paths for display, then sends a `commit_segment` advance signal. GOP recomputes the accepted path from the Run inputs and approved public GB configuration. Remote evaluates on GOP without client configuration. Independent `evaluateFunnelNode` and advisory `.local()` helpers have no business effects.
|
|
318
318
|
|
|
319
319
|
```ts
|
|
320
320
|
import {
|
|
@@ -343,7 +343,7 @@ host.destroy(); // Detach UI; accepted backend work remains owned by Indiana.
|
|
|
343
343
|
started.controller.reset();
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
-
Merchant backends use the same adapter from `/server`, with their original Server Key as `credential` and the
|
|
346
|
+
Merchant backends use the same adapter from `/server`, with their original Server Key as `credential` and the Longplay Customer UUID as `customerId`. Each Run request proves the original live issuing Key in a server-only header; it is never saved in a pending command or exposed to the renderer. The ordinary `createFunnelRunController` accepts `serverKey` only on a merchant backend. Customer clients continue proving their original Customer identity with `identify(customerToken)`; they cannot override it.
|
|
347
347
|
|
|
348
348
|
A Flow `commit_segment` carries only its retained request ID, parked node/visit/revision, policy revision and configuration generation. Caller steps, results and boundaries are not accepted. The SDK prediction yields to the server response; response-loss recovery preserves the original command identity. Customer Coupon claims still require `offers:resolve` and explicit `selfClaimEnabled`; merchant grants retain `offers:grant`. The independent Coupon issuer owns eligibility, inventory, possession and expiry.
|
|
349
349
|
|
|
@@ -380,20 +380,20 @@ await entries.retry();
|
|
|
380
380
|
entries.destroy();
|
|
381
381
|
```
|
|
382
382
|
|
|
383
|
-
The host observes original button behavior, matches unique render/element guards and attempts automatic entries once per pathname visit. GOP selects the Funnel, checks entry publication, suppresses an existing Customer/Funnel Run and enforces the automatic-entry cooldown. The host retains unknown requests in memory; page reload recovery is not included. An IAM binding supplies no Customer credential or business permission.
|
|
383
|
+
The host observes original button behavior, matches unique render/element guards and attempts automatic entries once per pathname visit. GOP selects the Funnel, checks entry publication, suppresses an existing Customer/Funnel Run and enforces the automatic-entry cooldown. The host retains unknown requests in memory; page reload recovery is not included. When a Run is stopped from Console or expires, the host removes its UI and reports `run_ended`, whether the Run reads `closed` or its next command is refused with `FORBIDDEN`; a later trigger can then start again. An IAM binding supplies no Customer credential or business permission.
|
|
384
384
|
|
|
385
385
|
An opted-in Split (`applyPageExperiment: true`) uses its accepted experiment variation for the merchant page. The entry host automatically shares `appManifestRelease` with `showFunnelRun`; manual hosts pass that option explicitly. `createFunnelPageExperiments({client, appManifestRelease})` is available for hosts without the standard UI. The consumer never rebuckets, reports use against the accepted receipt, and reverts on stop, completion or destruction. A separately installed visual consumer yields ownership of that experiment during the Run. This Web source feature requires matching GOP contracts and the IAM/Funnel database migration; installation alone does not promote runtime support.
|
|
386
386
|
|
|
387
387
|
## Funnel-scoped resource requests
|
|
388
388
|
|
|
389
|
-
Formal Local Runs submit advance signals to
|
|
389
|
+
Formal Local Runs submit advance signals to Longplay; the canonical executor supplies the original Run/node/visit/receipt when it issues a Coupon or performs commerce. An application consuming a pure Funnel result can bind its Customer client explicitly:
|
|
390
390
|
|
|
391
391
|
```ts
|
|
392
392
|
const resources = indiana.forFunnel({ funnelId, versionId });
|
|
393
393
|
await resources.claimCoupon(couponId, savedRequestId);
|
|
394
394
|
```
|
|
395
395
|
|
|
396
|
-
The scoped client automatically carries that fixed Funnel context into Coupon claims, Offering/Paywall resolution and attach quotes.
|
|
396
|
+
The scoped client automatically carries that fixed Funnel context into Coupon claims, Offering/Paywall resolution and attach quotes. Longplay recomputes only the requested Funnel using trusted facts; caller-reported branches are not permission. Independent `indiana.claimCoupon(...)` remains available under Coupon-owned self-claim rules. Reuse the same request ID and input after an unknown response; accepted commands retain their original result after a later Funnel pause.
|
|
397
397
|
|
|
398
398
|
## Standalone Pages, Popups and Banners
|
|
399
399
|
|
|
@@ -413,7 +413,7 @@ export async function showBanner(
|
|
|
413
413
|
onContinue: (intent: ContentActionIntent) => Promise<void>,
|
|
414
414
|
) {
|
|
415
415
|
const indiana = createIndiana({
|
|
416
|
-
baseUrl: "https://api.
|
|
416
|
+
baseUrl: "https://api.longplayai.com",
|
|
417
417
|
scope: { projectId: "<APP_UUID>", environment: "sandbox" },
|
|
418
418
|
});
|
|
419
419
|
indiana.identify(customerToken);
|
|
@@ -463,12 +463,12 @@ Customer sessions contain `customerId`, `sessionId`, `accessToken`, `refreshToke
|
|
|
463
463
|
|
|
464
464
|
## Merchant-recorded billing
|
|
465
465
|
|
|
466
|
-
In `record_only` mode your backend owns payment execution, verifies its results and submits normalized `provider: "merchant"` facts with the Server SDK. Use `purchase.completed`, `purchase.closed`, `subscription.observed`, `subscription.change.completed`, `invoice.paid` or `refund.updated`. Include the original operation/Customer, complete Invoice lines and subscription items as required. Amounts use minor units; `occurredAt` is an ISO observation time. A durable outbox retains the exact event ID and facts on retry. The receipt records merchant provenance with `providerVerified: false`; Customer/Guest clients cannot submit billing facts. Managed mode retains
|
|
466
|
+
In `record_only` mode your backend owns payment execution, verifies its results and submits normalized `provider: "merchant"` facts with the Server SDK. Use `purchase.completed`, `purchase.closed`, `subscription.observed`, `subscription.change.completed`, `invoice.paid` or `refund.updated`. Include the original operation/Customer, complete Invoice lines and subscription items as required. Amounts use minor units; `occurredAt` is an ISO observation time. A durable outbox retains the exact event ID and facts on retry. The receipt records merchant provenance with `providerVerified: false`; Customer/Guest clients cannot submit billing facts. Managed mode retains Longplay-owned provider execution.
|
|
467
467
|
|
|
468
468
|
## Merchant bridge
|
|
469
469
|
|
|
470
470
|
`createMerchantBridge` consumes bridge version 3 at POST `/integrations/indiana`, scoped by App and environment. It uses native login for Customer sessions, presentation, commerce reads and confirmed original operations. Call `reset()` on logout/account switch. The client never automatically retries financial execution or opens Checkout. Protocol 0.10 accepts no older protocol.
|
|
471
471
|
|
|
472
|
-
The Server entry exports `createMerchantBridgeHandler`, `createMerchantBridgeAuthorityResolver`, `signMerchantBridgeMessage` and `verifyMerchantBridgeMessage`. Supply backend signing keys, native authorization/CSRF, implemented capabilities/profiles and typed handlers. Set `resolveOperationAuthority: createMerchantBridgeAuthorityResolver(server)` so every financial/read command obtains fresh
|
|
472
|
+
The Server entry exports `createMerchantBridgeHandler`, `createMerchantBridgeAuthorityResolver`, `signMerchantBridgeMessage` and `verifyMerchantBridgeMessage`. Supply backend signing keys, native authorization/CSRF, implemented capabilities/profiles and typed handlers. Set `resolveOperationAuthority: createMerchantBridgeAuthorityResolver(server)` so every financial/read command obtains fresh Longplay authority from the original operation API. A signed service request still requires confirmation and exact scope/Customer ownership; it cannot issue user sessions.
|
|
473
473
|
|
|
474
|
-
Handlers must durably reserve the original operation before external effects and retain the same provider idempotency key. The SDK's preceding lookup is not a concurrency lock. `operation.read` returns `found` or `not_received` without creating effects. Recover `RESULT_UNKNOWN` through that original operation; never infer permission from a browser quote or `pending`. Console discovery is configuration validation, not commercial completion. See the [merchant specification](https://
|
|
474
|
+
Handlers must durably reserve the original operation before external effects and retain the same provider idempotency key. The SDK's preceding lookup is not a concurrency lock. `operation.read` returns `found` or `not_received` without creating effects. Recover `RESULT_UNKNOWN` through that original operation; never infer permission from a browser quote or `pending`. Console discovery is configuration validation, not commercial completion. See the [merchant specification](https://longplayai.com/docs/merchant-execution) for exact fields and recovery.
|
package/README.zh-CN.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[English](./README.md)
|
|
2
2
|
|
|
3
|
-
当前源码为 SDK 0.10.
|
|
3
|
+
当前源码为 SDK 0.10.2、协议 0.10。本页示例使用 `https://api.longplayai.com` 以及生产部署内签发的 Sandbox App/凭据。Sandbox 仍属于生产数据,不能复用独立测试部署的凭据。运行前核对[环境状态](../../docs/development-status.md),确认服务已就绪。
|
|
4
4
|
|
|
5
5
|
# @tinkerlandai/indiana-sdk
|
|
6
6
|
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
**IAM 的源码扫描、生成、校验和上传属于独立构建包 `@tinkerlandai/indiana-iam`。** 普通业务 SDK 只保留 GB visual 应用页面变更所需的运行时 release 绑定。完整定义见 [SDK 边界](../../docs/sdk-architecture.md) 与 [IAM 构建包](../iam/README.md)。
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npm install --save-exact @tinkerlandai/indiana-sdk@0.
|
|
12
|
+
npm install --save-exact @tinkerlandai/indiana-sdk@0.10.2
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
SDK 0.
|
|
15
|
+
SDK 0.10.2 要求 HTTP 协议 0.10。服务端须使用同一当前协议,不提供旧协议兼容;发布包不会部署服务端。实际发布、标签与环境验收范围见 [SDK 发布总账](../../docs/sdk-releases.md) 和 [开发状态](../../docs/development-status.md)。
|
|
16
16
|
|
|
17
17
|
数据/会话层负责公开 JSON、完整性校验和鉴权操作;展示宿主将会话接口接入打包的屏幕渲染器,渲染器消费已提供的数据并返回动作意图。客户端按需加载展示;`/server` 入口使用商户后端的受限 Server Key,不加载屏幕展示。SDK 本地检查不替代运行时授权或付款核实。后台 Preview 为同一个展示宿主提供模拟会话和数据。
|
|
18
18
|
|
|
@@ -21,7 +21,7 @@ SDK 0.9.5 要求 HTTP 协议 0.9。安装版本须与运行服务匹配;发布
|
|
|
21
21
|
普通 script 标签复用 npm 浏览器实现,无需打包工具:
|
|
22
22
|
|
|
23
23
|
```html
|
|
24
|
-
<script src="https://
|
|
24
|
+
<script src="https://longplayai.com/sdk.js"></script>
|
|
25
25
|
<script type="module">
|
|
26
26
|
const {
|
|
27
27
|
createIndiana,
|
|
@@ -32,7 +32,7 @@ SDK 0.9.5 要求 HTTP 协议 0.9。安装版本须与运行服务匹配;发布
|
|
|
32
32
|
mountContent,
|
|
33
33
|
} = await Indiana.ready;
|
|
34
34
|
const client = createIndiana({
|
|
35
|
-
baseUrl: "https://api.
|
|
35
|
+
baseUrl: "https://api.longplayai.com",
|
|
36
36
|
scope: { projectId: "<APP_UUID>", environment: "sandbox" },
|
|
37
37
|
});
|
|
38
38
|
// 从验证登录后的自有后端获取完整 session 凭据对。
|
|
@@ -43,7 +43,7 @@ SDK 0.9.5 要求 HTTP 协议 0.9。安装版本须与运行服务匹配;发布
|
|
|
43
43
|
</script>
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
生产脚本为 `https://
|
|
46
|
+
生产脚本为 `https://longplayai.com/sdk.js`,配套使用同环境 API 和凭据。`Indiana.ready` 返回根入口的全部浏览器导出,以及 `createPageExperiments` / `createVisualExperiments`;下文 npm import 可替换为从该 Promise 解构同名函数。加载不自动创建会话、发送事件或启动实验。Server Key 留在后端,Funnel 启动恢复与退出清理沿用现有流程。重复标签共用 ready,失败会拒绝 Promise,UI 按需加载。加载标签使用普通 script;添加 async/defer 时先等标签 load 事件,再访问 `Indiana`。CSP 需允许脚本来源和 API 连接。页面实验也使用该统一入口,显式创建客户端并调用 `start()`。详见 [script 分发](../../docs/sdk-architecture.md#web-script-distribution)和[完整接入示例](https://longplayai.com/docs/javascript#script)。
|
|
47
47
|
|
|
48
48
|
## React Native 核心入口
|
|
49
49
|
|
|
@@ -51,9 +51,9 @@ React Native 使用独立包 `@tinkerlandai/indiana-react-native`,原 `@tinker
|
|
|
51
51
|
|
|
52
52
|
## Funnel Local / Remote 统一执行
|
|
53
53
|
|
|
54
|
-
Local / Remote 共用
|
|
54
|
+
Local / Remote 共用 Longplay Run、command/action 回执、业务服务、幂等和恢复。Local SDK 只预测纯条件、Feature/实验路径用于展示,再提交 `commit_segment` 推进信号;GOP 根据 Run 输入和批准的公共 GB 配置重算并接受路径。Remote 在 GOP 计算,不依赖客户端公共配置。
|
|
55
55
|
|
|
56
|
-
`createLocalFunnelClient().start({ requestId, locale, parameters })` 返回 `runToken` 和同一个 `controller`,传给 `showFunnelRun` 即可。原 Server Key 的商户服务端另外指定原
|
|
56
|
+
`createLocalFunnelClient().start({ requestId, locale, parameters })` 返回 `runToken` 和同一个 `controller`,传给 `showFunnelRun` 即可。原 Server Key 的商户服务端另外指定原 Longplay Customer UUID;浏览器使用 Customer Token,不能改写 Customer。恢复必须使用安全保存的原 Run token,不能把旧 Local checkpoint 或未知发券结果转换为一个新请求身份。
|
|
57
57
|
|
|
58
58
|
推进信号尚未提交时,Run 停车并释放 Worker,仍需 Local 宿主返回;信号受理后,后台业务操作通过 GOP 的 Cloud Tasks/Cloud Scheduler 恢复。Local SDK 不直接发券,也没有第二套业务执行器。独立 Coupon API 保留自己的自领政策、权限、资格、库存、幂等和有效期,不要求创建 Funnel。
|
|
59
59
|
|
|
@@ -82,21 +82,21 @@ JavaScript 与 TypeScript 共用本包,声明文件随包提供。内部 `@ind
|
|
|
82
82
|
|
|
83
83
|
展示独立的 **Page、Popup 或 Banner 资源**,从主入口导入 `createIndiana` 和 `mountContent`,先用 `indiana.resolveContent(...)` 解析,再挂载结果。`/page` 用于商户现有页面的 GB 网址跳转实验,`/visual` 用于该页面的 GB 视觉修改;`/popup` 提供底层屏幕渲染与预览工具,不负责选择或解析 Popup 资源。现有公开导入路径保持不变,详见[入口职责](../../docs/sdk-architecture.md#public-entry-points-and-capability-ownership)。
|
|
84
84
|
|
|
85
|
-
独立的 [Python 服务端 SDK](../python/README.md) 使用同一当前 HTTP 协议,提供同步、异步 Customer、持仓/权益检查和用量上报。它从 `https://
|
|
85
|
+
独立的 [Python 服务端 SDK](../python/README.md) 使用同一当前 HTTP 协议,提供同步、异步 Customer、持仓/权益检查和用量上报。它从 `https://longplayai.com/sdk/python` 分发(不在 PyPI),不属于这个 npm 包。
|
|
86
86
|
|
|
87
|
-
[Go 服务端 SDK](../go/README.md) 提供同样的首版能力,使用类型化响应、Context 取消和 goroutine 并发复用客户端。通过 Go 默认模块代理以 `go get
|
|
87
|
+
[Go 服务端 SDK](../go/README.md) 提供同样的首版能力,使用类型化响应、Context 取消和 goroutine 并发复用客户端。通过 Go 默认模块代理以 `go get longplayai.com/sdk/go` 安装,与其他业务 SDK 共用发行版本。
|
|
88
88
|
|
|
89
89
|
## API 默认地址与自测
|
|
90
90
|
|
|
91
91
|
默认地址、四个域名、版本标签和测试配置的持续规范见 [SDK API 入口与开发测试规范](../../docs/sdk-endpoints-and-testing.md)。
|
|
92
92
|
|
|
93
|
-
省略 `baseUrl` 时默认请求 `https://api.
|
|
93
|
+
省略 `baseUrl` 时默认请求 `https://api.longplayai.com`;Customer/Server、Funnel、Feature、Paywall、页面与视觉实验客户端共用此入口。公共配置同步的 `origin` 也可省略并使用同一默认值。Swift `IndianaConfiguration(scope: ...)`、Kotlin `IndianaConfiguration(scope = ...)` 采用同一默认地址与统一发行版本;仍可显式覆盖 `baseURL`/`baseUrl`。Funnel/Paywall 的壳和渲染器随 SDK 本地打包,不从 API origin 下载可执行代码。
|
|
94
94
|
|
|
95
95
|
包含此默认值的版本无论标记为 `beta` 或 `latest`,都使用生产 API 地址。标签选择包版本,不选择服务环境。两套 API 入口经 Vercel 转发到各自 Go Indianapolis,入口和业务就绪分别验收。
|
|
96
96
|
|
|
97
97
|
同一次接入中的所有客户端与配置同步实例使用同一个 API origin。`environment: "sandbox"` 只选择该部署内的业务环境,不选择另一套服务或数据库;错误或未知写入结果不能触发跨环境自动重试。
|
|
98
98
|
|
|
99
|
-
独立测试 API `https://api.dev.
|
|
99
|
+
独立测试 API `https://api.dev.longplayai.com` 必须使用独立测试凭据,并部署当前协议 0.10。维护者测试必须显式指定目标,不能回落生产默认值,见[测试流程](../../docs/sdk-selfserve.md#maintainer-api-origin-testing)。
|
|
100
100
|
|
|
101
101
|
## 从可信后端创建 Customer Session
|
|
102
102
|
|
|
@@ -155,7 +155,7 @@ await indiana.createGuestCustomer({
|
|
|
155
155
|
});
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
Longplay 将首次提供的信号保存为环境内的哈希,后续值不替换它。可以省略输入,保留按 Customer 去重 Trial 的行为;`unique_fingerprint` 进一步在共享信号的 Customer 之间去重所配置的 Product Trial。业务用户 ID 不是安装标识;Customer/Guest/会话 Token 和请求 ID 各有自己的生命周期,不应充当 fingerprint。
|
|
159
159
|
|
|
160
160
|
Web 宿主可以用独立的第一方安装 cookie 保留信号。清除 cookie 或更换浏览器存储可能产生新值;退出登录无需重置它。原生宿主持久保存随机安装信号,并定义重装、恢复和重置行为。该信号独立于设备证明和身份认证。客户端只收到通用的 Trial 冲突信息;保留 `X-Request-Id` 供服务端排查。
|
|
161
161
|
|
|
@@ -173,9 +173,9 @@ Local/Remote 求值策略由每个 Funnel 决定。`createFunnelDecisionClient
|
|
|
173
173
|
|
|
174
174
|
### 独立消费方的分桶身份
|
|
175
175
|
|
|
176
|
-
Funnel、独立 Feature 和页面实验各自求值、各自使用结果。App/环境、实验定义、属性、分桶身份及 Sticky 状态相同时结果应一致;刷新时间或输入不同时允许不同组。Remote Funnel 引用允许 Local 定义投递的代码 Experiment 时,仍由
|
|
176
|
+
Funnel、独立 Feature 和页面实验各自求值、各自使用结果。App/环境、实验定义、属性、分桶身份及 Sticky 状态相同时结果应一致;刷新时间或输入不同时允许不同组。Remote Funnel 引用允许 Local 定义投递的代码 Experiment 时,仍由 Longplay 计算。已接受的 Funnel 决定保留原结果,不做跨功能结果同步。
|
|
177
177
|
|
|
178
|
-
Customer 求值中的 `id` 是
|
|
178
|
+
Customer 求值中的 `id` 是 Longplay Customer UUID(`customer.id`),`externalUserId` 是商户账号 ID(`customer.externalUserId`)。创建实验时明确选择 `hashAttribute`,每个消费方保持相同语义;不要把商户账号 ID 填进 `attributes.id`。本地属性来自可信后端给当前用户的 Customer 数据;`identify(customerToken)` 负责请求认证,不会自动补齐本地分桶属性。属性仅用于求值,不构成授权。
|
|
179
179
|
|
|
180
180
|
`setAttributes()` 替换整个对象,每次更新都保留分桶字段。匿名页面/视觉实验默认的 `id` 和 `deviceId` 来自共用的第一方设备 cookie,不自动与账号实验同组。各适配器的 Sticky 能力有区别:共用 Run 的 Local 指令读取服务端状态,纯本地工具的持久 Sticky 使用已有宿主适配器,不新增共享状态。完整要求见[架构边界](../../docs/growthbook-sdk-delivery.md#跨消费方求值边界与分桶身份)。
|
|
181
181
|
|
|
@@ -248,7 +248,7 @@ Offering 固定 Product Version 的范围与层级。Paywall 固定已发布 Off
|
|
|
248
248
|
|
|
249
249
|
当前服务端实现了 Customer/Guest、Entitlement 检查、持有与账本读取、用量和余额、Coupon/推荐码、Offering/独立 Paywall,以及配置后的 Stripe Sandbox attach、支付方式收集、订阅验证/更新/退款/Trial 和 License 分配路径。每个操作仍受已配置的支付账号、执行模式、Product/Price 条款和当前实现范围约束;接口存在不表示任意组合已验收。
|
|
250
250
|
|
|
251
|
-
Stripe 环境的 `indiana` 模式由
|
|
251
|
+
Stripe 环境的 `indiana` 模式由 Longplay 执行;`record_only` 返回交给商户后端执行的指令。两种模式都从验证后的 Stripe 事实写入财务与权益。支付方式收集本身不产生扣款、Invoice 或额外权益。报价/确认/读取使用各自持久操作 ID;同一请求编号不能更换输入。
|
|
252
252
|
|
|
253
253
|
本 Beta **不认证完整商业功能或生产运行**。当前仍未接通的服务包括:
|
|
254
254
|
|
|
@@ -275,7 +275,7 @@ npm 发布不部署 Web 服务、不应用数据库迁移,也不发布 Swift
|
|
|
275
275
|
|
|
276
276
|
公共配置由匹配运行端发布。公共文件不需要 Customer 凭据;业务操作沿用原凭据和服务端资格检查。完整调用见[英文说明](README.md#shared-public-configuration)。
|
|
277
277
|
|
|
278
|
-
初始化总等待不超过 5 秒;活跃消费者共用 12–15 秒轮询,并在回前台/联网后刷新。Local 控制状态自源站验证起最多有效 60 秒,旧 304 或失败不能续期。暂停/过期后停止新本地判断及客户屏幕;冷启动离线时,仅凭持久缓存不能恢复 active 权限。
|
|
278
|
+
初始化总等待不超过 5 秒;活跃消费者共用 12–15 秒轮询,并在回前台/联网后刷新。Local 控制状态自源站验证起最多有效 60 秒,旧 304 或失败不能续期。暂停/过期后停止新本地判断及客户屏幕;冷启动离线时,仅凭持久缓存不能恢复 active 权限。Longplay Run 的命令与观察直接读取服务端状态,独立于 CDN 控制有效期。
|
|
279
279
|
|
|
280
280
|
内存缓存上限 128 项/32 MiB,浏览器默认 localStorage 的公共文件缓存上限 128 项/2 MiB/7 天。可设置 `storage: null` 或提供 `ConfigurationStorage`;额度不足时退回内存。单文件上限 1 MiB,清单上限 512 KiB,使用前校验作用域、schema 和哈希。共享缓存不存凭据、用户属性、Sticky 或已接受的决策。共享实例由宿主统一 `destroy()`,单个消费者退出不会销毁其他消费者的同步能力。
|
|
281
281
|
|
|
@@ -291,7 +291,7 @@ npm 发布不部署 Web 服务、不应用数据库迁移,也不发布 Swift
|
|
|
291
291
|
|
|
292
292
|
`createIamFunnelEntries({scope, environmentId, baseUrl, appManifestRelease, session: indiana.session, container})` 安装一次后,通过 `start()` 启动监听,即可在 IAM 页面地图配置现有 Funnel Entry 节点的「进入屏幕」和「按钮/链接点击」入口。配置保存到 Funnel 草稿,随 Funnel 一起发布、暂停。`appManifestRelease` 必须来自当前构建的 IAM release descriptor。原页面点击处理继续执行;仅唯一、可验证的页面和元素能够触发。自动入口每次 pathname 访问尝试一次,查询参数、hash 和重新渲染不制造新访问。
|
|
293
293
|
|
|
294
|
-
服务端选择 Funnel,验证发布状态,并按原 Customer/Funnel 检查进行中的 Run 和自动入口冷却时间。未知响应通过 `retry()`
|
|
294
|
+
服务端选择 Funnel,验证发布状态,并按原 Customer/Funnel 检查进行中的 Run 和自动入口冷却时间。未知响应通过 `retry()` 复用内存中的原请求;入口宿主不提供刷新页面后的恢复。Run 被 Console 终止或过期后,无论读取到 `closed` 状态还是后续命令被 `FORBIDDEN` 拒绝,入口宿主都会移除界面并上报 `run_ended`,之后的入口可以重新启动。卸载时调用 `destroy()`。IAM 不提供 Customer 凭据或业务权限。
|
|
295
295
|
|
|
296
296
|
Split 开启 `applyPageExperiment` 后,页面直接使用该 Run 已接受的实验变体,不重新分桶。标准入口宿主自动向 `showFunnelRun` 传入 release;手动宿主传入 `appManifestRelease`,或使用 `createFunnelPageExperiments({client, appManifestRelease})`。停止、完成和销毁会清理效果,实际应用沿用原 receipt 的 GB used 上报。同一实验的独立 visual consumer 在 Funnel 持有分组期间让出控制。这些 Web 源码能力需要配套 GOP 合同和数据库迁移,安装 SDK 不等于运行时已发布。
|
|
297
297
|
|
|
@@ -319,7 +319,7 @@ export async function showBanner(
|
|
|
319
319
|
onContinue: (intent: ContentActionIntent) => Promise<void>,
|
|
320
320
|
) {
|
|
321
321
|
const indiana = createIndiana({
|
|
322
|
-
baseUrl: "https://api.
|
|
322
|
+
baseUrl: "https://api.longplayai.com",
|
|
323
323
|
scope: { projectId: "<APP_UUID>", environment: "sandbox" },
|
|
324
324
|
});
|
|
325
325
|
indiana.identify(customerToken);
|
|
@@ -352,12 +352,12 @@ export async function showBanner(
|
|
|
352
352
|
|
|
353
353
|
## 商户主动记账
|
|
354
354
|
|
|
355
|
-
`record_only` 由商户后端执行并核对支付,再提交 `provider: "merchant"` 的归一化事实。事件包括 `purchase.completed`、`purchase.closed`、`subscription.observed`、`subscription.change.completed`、`invoice.paid`、`refund.updated`,按事件携带原操作、Customer、完整发票行和订阅项目。金额使用最小币种单位,`occurredAt` 为真实观察时间的 ISO 字符串。可靠 outbox 在重试时保留相同事件 ID 和事实。回执以 `providerVerified: false` 标记商户来源;Customer/Guest 无权记账。托管模式继续由
|
|
355
|
+
`record_only` 由商户后端执行并核对支付,再提交 `provider: "merchant"` 的归一化事实。事件包括 `purchase.completed`、`purchase.closed`、`subscription.observed`、`subscription.change.completed`、`invoice.paid`、`refund.updated`,按事件携带原操作、Customer、完整发票行和订阅项目。金额使用最小币种单位,`occurredAt` 为真实观察时间的 ISO 字符串。可靠 outbox 在重试时保留相同事件 ID 和事实。回执以 `providerVerified: false` 标记商户来源;Customer/Guest 无权记账。托管模式继续由 Longplay 执行渠道业务。
|
|
356
356
|
|
|
357
357
|
## 商户桥接
|
|
358
358
|
|
|
359
359
|
`createMerchantBridge` 使用版本 3 单入口 POST `/integrations/indiana`,按 App 和环境固定范围,通过原生登录接入 Customer 会话、展示、商业读取和已确认原操作。退出或换号调用 `reset()`。客户端不自动重试财务执行或打开 Checkout。当前协议为 0.10,不兼容旧协议。
|
|
360
360
|
|
|
361
|
-
Server 入口导出 `createMerchantBridgeHandler`、`createMerchantBridgeAuthorityResolver`、`signMerchantBridgeMessage` 和 `verifyMerchantBridgeMessage`。配置后端签名密钥、原生授权/CSRF、实际已实现的能力与 profiles,并设置 `resolveOperationAuthority: createMerchantBridgeAuthorityResolver(server)`,每次从
|
|
361
|
+
Server 入口导出 `createMerchantBridgeHandler`、`createMerchantBridgeAuthorityResolver`、`signMerchantBridgeMessage` 和 `verifyMerchantBridgeMessage`。配置后端签名密钥、原生授权/CSRF、实际已实现的能力与 profiles,并设置 `resolveOperationAuthority: createMerchantBridgeAuthorityResolver(server)`,每次从 Longplay 原操作 API 读取最新权限。服务签名仍需原确认事实和准确范围、Customer 归属,不能据此签发用户会话。
|
|
362
362
|
|
|
363
|
-
业务处理器必须在调用外部渠道前持久预留原 operationId,并使用稳定渠道幂等键;SDK 的先查询不能代替并发锁。`operation.read` 返回 `found` 或 `not_received`,严格只读。`RESULT_UNKNOWN` 只恢复原操作,不能根据浏览器报价或 pending 状态自行推断执行权限。Console 能力检查不证明商业完成。精确字段和恢复规则见[商户接入规范](https://
|
|
363
|
+
业务处理器必须在调用外部渠道前持久预留原 operationId,并使用稳定渠道幂等键;SDK 的先查询不能代替并发锁。`operation.read` 返回 `found` 或 `not_received`,严格只读。`RESULT_UNKNOWN` 只恢复原操作,不能根据浏览器报价或 pending 状态自行推断执行权限。Console 能力检查不证明商业完成。精确字段和恢复规则见[商户接入规范](https://longplayai.com/zh-CN/docs/merchant-execution)。
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as j,b as N,c as $,d as Y}from"./chunk-
|
|
1
|
+
import{a as j,b as N,c as $,d as Y}from"./chunk-XO42OH3M.js";import{a as D}from"./chunk-DSPLGTQI.js";import{a as O}from"./chunk-GHJ65ILJ.js";import{b as U,e as _,g as C}from"./chunk-3PZ4Y6UD.js";var te=e=>new TextEncoder().encode(JSON.stringify({events:e})).length;function re(e,t){let n=e,r=o=>Number.isInteger(o)&&o>=0;return!!n&&typeof n=="object"&&r(n.inserted)&&r(n.duplicates)&&r(n.skipped)&&Array.isArray(n.rejected)&&n.inserted+n.duplicates+n.skipped+n.rejected.length===t}function V(e){let t=[],n,r=(a,i)=>e.log?.(`${e.label} ${a}`,i);function o(a){n===void 0&&t.length&&(n=setTimeout(()=>{u()},a))}async function s(a){let i;try{i=await e.fetch(e.endpoint,{method:"POST",body:JSON.stringify({events:a}),headers:{"Content-Type":"text/plain"},keepalive:!0,credentials:"omit"})}catch(p){return r("not sent",{error:p}),!1}if(i.status===408||i.status===429||i.status>=500)return r("not sent",{status:i.status}),!1;if(!i.ok)return r("rejected",{status:i.status,events:a.length}),!0;let f;try{f=await i.json()}catch{f=void 0}return re(f,a.length)?(f.rejected.length&&r("rejected",{rejected:f.rejected}),!0):(r("not acknowledged",{status:i.status}),!1)}function u(){clearTimeout(n),n=void 0;let a=[];for(;t.length&&a.length<20&&(!a.length||te([...a,t[0]].map(i=>i.event))<=65536);)a.push(t.shift());return a.length?s(a.map(i=>i.event)).then(i=>{if(i)return o(1e3);let f=a.filter(p=>++p.attempts<5);f.length<a.length&&r("dropped after retries",{events:a.length-f.length}),t.unshift(...f),o(5e3)}):Promise.resolve()}return{push(a){t.push({event:a,attempts:0}),t.length>=20?u():o(1e3)},flush:u}}async function q(e,t,n,r={}){let o=r.message??"Invalid SDK JSON response",s=e.headers.get("content-length");if(!e.ok||!/^application\/json(?:;|$)/i.test(e.headers.get("content-type")??"")||s!==null&&(!/^\d+$/.test(s)||Number(s)>t)||!e.body)throw e.body?.cancel().catch(()=>{}),Error(o);let u=e.body.getReader(),a=[],i=0,f=!1,p=()=>{},g=new Promise((v,h)=>{p=()=>h(Error(o)),n.addEventListener("abort",p,{once:!0}),n.aborted&&p()});try{for(;;){let m=await Promise.race([u.read(),g]);if(m.done){f=!0;break}if(i+=m.value.byteLength,i>t)throw Error(o);a.push(m.value)}let v=new Uint8Array(i),h=0;for(let m of a)v.set(m,h),h+=m.length;return JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(v))}finally{n.removeEventListener("abort",p),f||u.cancel().catch(()=>{}),u.releaseLock()}}async function B(e,t){let n=e?.body;if(!n)return;let r=n.getReader(),o=0,s=!1,u,a=new Promise((p,g)=>{u=setTimeout(()=>g(Error("Exposure response timeout")),2e3)}),i=()=>{},f=new Promise((p,g)=>{i=()=>g(t?.reason),t?.addEventListener("abort",i,{once:!0}),t?.aborted&&i()});try{for(;;){let p=await Promise.race([r.read(),a,f]);if(p.done){s=!0;return}if(o+=p.value.byteLength,o>16384)throw Error("Exposure response budget")}}finally{clearTimeout(u),t?.removeEventListener("abort",i),s||r.cancel().catch(()=>{}),r.releaseLock()}}var fe=2*1024*1024,J=256*1024;async function G(e,t,n,r){let o=()=>{},s=new Promise((u,a)=>{o=()=>a(r.reason),r.addEventListener("abort",o,{once:!0}),r.aborted&&o()});try{let u=e(t,{...n,signal:r});return u.then(a=>{r.aborted&&a.body?.cancel().catch(()=>{})},()=>{}),await Promise.race([u,s])}finally{r.removeEventListener("abort",o)}}async function pe(e,t,n,r=!1){if(new TextEncoder().encode(n).byteLength>65536)throw Error("Exposure request budget");let o=new URL(t);o.searchParams.set("sdkVersion",O.version);let s=new AbortController,u=setTimeout(()=>s.abort(Error("Exposure request timeout")),5e3);try{let a=await G(e,o.href,{method:"POST",headers:{"Content-Type":"text/plain"},body:n,credentials:"omit",keepalive:r,redirect:"error"},s.signal);await B(a,s.signal)}finally{clearTimeout(u)}}function M(e){let t,n=!1;return{async read(r,o){if(n)throw Error("Experiment client stopped");t?.abort(Error("Experiment request superseded"));let s=new AbortController;t=s;let{signal:u}=s,a=setTimeout(()=>s.abort(Error("Experiment request timeout")),5e3);try{let i=await G(e,r,{cache:"no-cache",credentials:"omit",redirect:"error"},u),f=await q(i,o,u);return u.throwIfAborted(),f}finally{clearTimeout(a),t===s&&(t=void 0)}},destroy(){n=!0,t?.abort(Error("Experiment client stopped")),t=void 0}}}/*!
|
|
2
2
|
Portions ported from GrowthBook sdk-js @ ed29827 (GrowthBook.ts auto experiments, util.ts mergeQueryStrings,
|
|
3
3
|
plugins/auto-attributes.ts). Copyright (c) GrowthBook, Inc. MIT License.
|
|
4
4
|
*/function ne(e,t){let n,r;try{n=new URL(e),r=new URL(t)}catch{return t}return n.searchParams.forEach((o,s)=>{r.searchParams.has(s)||r.searchParams.set(s,o)}),r.toString()}function oe(e,t){let n=navigator.userAgent,r=new URLSearchParams(t.search),o={};for(let s of["source","medium","campaign","term","content"]){let u=r.get(`utm_${s}`);u!==null&&(o[`utm${s[0].toUpperCase()}${s.slice(1)}`]=u)}return{id:e,deviceId:e,url:t.href,path:t.pathname,host:t.host,query:t.search,pageTitle:typeof document>"u"?"":document.title,browser:/Edg/.test(n)?"edge":/Chrome/.test(n)?"chrome":/Firefox/.test(n)?"firefox":/Safari/.test(n)?"safari":"unknown",deviceType:/Mobi/.test(n)?"mobile":"desktop",...o}}function ie(e){let t=e;if(!(!!t&&typeof t=="object"&&typeof t.environmentId=="string"&&typeof t.trackExposures=="boolean"&&/^[a-f0-9]{64}$/.test(t.generation??"")&&Array.isArray(t.experiments)&&t.experiments.length<=100&&t.experiments.every(r=>!!r&&!["namespace","status","url","groups"].some(o=>Object.hasOwn(r,o))&&(r.hashVersion===void 0||r.hashVersion===2)&&typeof r.key=="string"&&Array.isArray(r.variations)&&r.variations.length>=2&&r.variations.every(o=>!!o&&typeof o.urlRedirect=="string")&&(r.urlPatterns===void 0||Array.isArray(r.urlPatterns)))&&!!t.savedGroups&&typeof t.savedGroups=="object"&&!Array.isArray(t.savedGroups)))throw new Error("Invalid page experiment payload");return t}function be(e){if(e.configuration&&e.configuration.scope.environmentId!==e.environmentId)throw new Error("Page configuration belongs to another App environment");let t=$(e.baseUrl);if(!/^[0-9a-f-]{36}$/i.test(e.environmentId))throw new Error("environmentId must be the App environment ID");let n=e.fetch??globalThis.fetch.bind(globalThis),r=M(async(c,d)=>{let l=await n(c,d);if(!l.ok)throw l.body?.cancel().catch(()=>{}),new Error(`Page experiments unavailable (${l.status})`);return l}),o=e.log?(c,d)=>D(e.log,c,d):void 0,s=`${t}/api/v1/page-experiments/${e.environmentId}`,u=j(e.deviceIdCookie??"indiana_did"),a=N(),i=null,f={...e.attributes??{}},p="",g=!1,v=!1,h=0,m=0,w=new Map,T=V({endpoint:`${s}/events`,fetch:n,log:o,label:"Exposure"}),P=T.flush,E={trackedExperiments:new Set,log:o,onExperimentViewed:(c,d)=>{e.trackExposures===!1||i?.trackExposures===!1||T.push({eventUuid:crypto.randomUUID(),timestamp:new Date().toISOString(),deviceId:u,...a?{sessionId:a}:{},url:location.href,pageTitle:document.title.slice(0,512),experimentKey:c.key,variationKey:d.key})}},H=()=>{E.url=location.href,E.attributes={...oe(u,location),...f}};function Q(c){if(!e.disableCrossOriginUrlRedirects)return!1;try{let d=new URL(location.href);return c.variations.some(l=>{if(!l.urlRedirect)return!1;let y=new URL(l.urlRedirect);return y.protocol!==d.protocol||y.host!==d.host})}catch{return!0}}function K(c,d){let l=e.configuration?.snapshot?.generation,y=m,z=location.href;p=c;let W=e.navigate??(x=>location.replace(x)),Z=e.navigateDelay??(e.navigate?0:100),ee=e.maxNavigateDelay??1e3;Promise.all([Promise.race([d,new Promise(x=>setTimeout(x,ee))]),new Promise(x=>setTimeout(x,Z))]).then(()=>{!g&&y===m&&location.href===z&&(!e.configuration||e.configuration.fresh&&e.configuration.snapshot?.generation===l)&&W(c)})}function b(){if(!(!i||g||e.configuration&&!e.configuration.fresh)){H();for(let c of i.experiments){if(Q(c)){o?.("Auto experiment blocked",{id:c.key});continue}let d;try{d=C(c,E)}catch(l){o?.("Skipping page experiment",{id:c.key,error:String(l)});continue}if(w.set(c.key,d),!!d.inExperiment){if(d.value.urlRedirect){let l=c.persistQueryString?ne(location.href,d.value.urlRedirect):d.value.urlRedirect;U(l,c.urlPatterns??[])?o?.("Skipping redirect because original URL matches redirect URL",{id:c.key}):l!==p&&K(l,P())}break}}}}let R="",F=()=>{location.href!==R&&(m++,R=location.href,p="",b())},A,S;function X(){A??=Y(F),addEventListener("pagehide",L)}let L=()=>{P()};async function k(){if(g)return!1;let c=++h,d;try{d=e.configuration?await e.configuration.pageExperiments():await r.read(s,J)}catch(y){if(g||c!==h)return!1;throw y}if(g||c!==h)return!1;let l=ie(d);if(l.environmentId.toLowerCase()!==e.environmentId.toLowerCase())throw new Error("Page experiment payload scope changed");return i?.trackExposures===!1&&l.trackExposures&&E.trackedExperiments?.clear(),i=l,E.savedGroups=_(l.savedGroups),!0}function I(){g||(S??=e.configuration?.subscribe(()=>{!g&&e.configuration?.fresh&&k().then(c=>{c&&b()}).catch(c=>o?.("Page configuration unavailable",{error:String(c)}))}),X(),R=location.href)}return{async start(){v||g||(v=!0,await k()&&(I(),b()))},async refresh(){await k()&&(m++,v&&I(),p="",b())},setAttributes(c){m++,p="",f={...c},b()},getResults(){return new Map(w)},get generation(){return i?.generation??null},destroy(){g||(g=!0,h++,r.destroy(),S?.(),A?.(),removeEventListener("pagehide",L),i=null,w.clear(),P())}}}export{fe as a,pe as b,M as c,ne as d,oe as e,ie as f,be as g};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
async function R(e,r,i){let t=new URL(e,r.baseURI),h=r.defaultView?.location.origin,l=/^(?:\/[^/?#]+)*\/\.well-known\/indiana\/releases\/([a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12})\.json$/.exec(t.pathname)?.[1];if(!l||t.origin!==h||t.search||t.hash||t.username||t.password)throw Error("Invalid IAM release URL");let a=AbortSignal.any([i,AbortSignal.timeout(2e3)]),f=await fetch(t,{credentials:"omit",redirect:"error",cache:"force-cache",signal:a}),o=f.headers.get("content-length");if(!f.ok||!/^application\/json(?:;|$)/i.test(f.headers.get("content-type")??"")||o!==null&&(!/^\d+$/.test(o)||Number(o)>512))throw f.body?.cancel().catch(()=>{}),Error("IAM release is unavailable");let d=f.body?.getReader();if(!d)throw Error("IAM release is unavailable");let n=[],b=0,c=()=>{},w=new Promise((u,E)=>{c=()=>E(Error("IAM release timeout")),a.addEventListener("abort",c,{once:!0}),a.aborted&&c()}),p=!1;try{for(;;){let u=await Promise.race([d.read(),w]);if(u.done){p=!0;break}if(b+=u.value.length,b>512)throw Error("IAM release budget");n.push(u.value)}}finally{a.removeEventListener("abort",c),p||d.cancel().catch(()=>{}),d.releaseLock()}let m=new Uint8Array(b),M=0;for(let u of n)m.set(u,M),M+=u.length;let I=new TextDecoder("utf-8",{fatal:!0}).decode(m),s=JSON.parse(I);if(I.trim()!==JSON.stringify(s))throw Error("Noncanonical IAM release descriptor");if(!s||typeof s!="object"||Array.isArray(s)||Object.keys(s).sort().join(",")!=="format,manifestDigest,releaseId,schemaVersion"||Reflect.get(s,"format")!=="indiana-app-manifest-release"||Reflect.get(s,"schemaVersion")!==2||Reflect.get(s,"releaseId")!==l||!/^[a-f0-9]{64}$/.test(String(Reflect.get(s,"manifestDigest"))))throw Error("Invalid IAM release descriptor");return Reflect.get(s,"manifestDigest")}var A=new WeakMap;function x(e,r=document){if(!/^[a-f0-9]{64}$/.test(e))throw new Error("Invalid IAM manifest digest");let i="data-indiana-release",t=r.documentElement,h=()=>new Set((t.getAttribute(i)??"").split(/\s+/).filter(Boolean)),l=A.get(r);if(!l){let n=new Map,b=new MutationObserver(()=>{let c=h(),w=!1;for(let[p,m]of n)m.count&&!c.has(p)&&(c.add(p),w=!0);w&&t.setAttribute(i,[...c].join(" "))});b.observe(t,{attributes:!0,attributeFilter:[i]}),l={releases:n,observer:b},A.set(r,l)}let{releases:a,observer:f}=l,o=a.get(e);if(!o){let n=h();o={count:0,owned:!n.has(e)},a.set(e,o),n.add(e),t.setAttribute(i,[...n].join(" "))}o.count++;let d=!1;return{attributes:{indianaAppManifestRelease:e},destroy(){if(!d&&(d=!0,o.count--,!o.count&&(a.delete(e),a.size||(f.disconnect(),A.delete(r)),o.owned))){let n=h();n.delete(e),n.size?t.setAttribute(i,[...n].join(" ")):t.removeAttribute(i)}}}}var g=new Map,v=new Set,y=()=>{for(let e of[...v])e()},D=e=>g.has(e);function O(e){return v.add(e),()=>{v.delete(e)}}function k(e,r){if(g.has(e)&&g.get(e)!==r)throw new Error("A different Funnel owns this page experiment");return g.set(e,r),y(),()=>{g.get(e)===r&&(g.delete(e),y())}}export{R as a,x as b,D as c,O as d,k as e};
|