@tinkerlandai/longplay-sdk 0.0.0-stage → 0.10.4
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 +209 -0
- package/README.md +485 -2
- package/README.zh-CN.md +375 -0
- package/THIRD_PARTY_NOTICES.md +101 -0
- package/dist/ai-usage-Btwg1nD7.d.ts +247 -0
- package/dist/api-5gAz4-Sb.d.ts +16897 -0
- package/dist/chunk-2HFA34QO.js +6 -0
- package/dist/chunk-7DXL3LHT.js +1 -0
- package/dist/chunk-ACF2FBUB.js +1 -0
- package/dist/chunk-CLXORAOK.js +1 -0
- package/dist/chunk-DSPLGTQI.js +1 -0
- package/dist/chunk-EARSGITJ.js +1 -0
- package/dist/chunk-G56XGVMS.js +1 -0
- package/dist/chunk-HOW7NIIR.js +1 -0
- package/dist/chunk-JB46ZKGZ.js +1 -0
- package/dist/chunk-KBM7BYQY.js +1 -0
- package/dist/chunk-MRN4HP4W.js +1 -0
- package/dist/chunk-RE6BIFLN.js +2 -0
- package/dist/chunk-SBPTOOYB.js +1 -0
- package/dist/chunk-SSLZKIIU.js +1 -0
- package/dist/chunk-VMM3GA3L.js +4 -0
- package/dist/chunk-VOEFXEPP.js +1 -0
- package/dist/chunk-X6BCUINI.js +173 -0
- package/dist/chunk-XSVQHPQT.js +17 -0
- package/dist/chunk-YQHPEP4X.js +1 -0
- package/dist/chunk-ZPZVSC4J.js +1 -0
- package/dist/configuration-sync-CywDxRkX.d.ts +1698 -0
- package/dist/content-NYMIZRNR.js +1 -0
- package/dist/coupon-fA1QujHt.d.ts +184 -0
- package/dist/customer-page-km5f1PJZ.d.ts +480 -0
- package/dist/funnel-local-client-CVokI0rx.d.ts +46997 -0
- package/dist/funnel-node-evaluation-P5B4fPfJ.d.ts +2054 -0
- package/dist/funnel-resource-client-Cv5OjfAn.d.ts +14525 -0
- package/dist/funnel-runtime-host-UL4DEJDU.js +1 -0
- package/dist/growth-rules-B4YYy64R.d.ts +173 -0
- package/dist/index.d.ts +23545 -0
- package/dist/index.js +1 -0
- package/dist/native-shell.d.ts +4 -0
- package/dist/native-shell.js +1 -0
- package/dist/openrouter.d.ts +43 -0
- package/dist/openrouter.js +1 -0
- package/dist/page.d.ts +134 -0
- package/dist/page.js +1 -0
- package/dist/paywall-EJCNWDHK.js +1 -0
- package/dist/popup.d.ts +191 -0
- package/dist/popup.js +1 -0
- package/dist/react-native.d.ts +36701 -0
- package/dist/react-native.js +1 -0
- package/dist/screen-D81zCpfi.d.ts +3000 -0
- package/dist/screen-renderer-JMHV4GPP.js +1 -0
- package/dist/server.d.ts +7073 -0
- package/dist/server.js +1 -0
- package/dist/visual-dom-DQLPBZ2Z.js +1 -0
- package/dist/visual.d.ts +87 -0
- package/dist/visual.js +1 -0
- package/package.json +90 -3
package/README.md
CHANGED
|
@@ -1,3 +1,486 @@
|
|
|
1
|
-
|
|
1
|
+
<p>
|
|
2
|
+
<a href="https://longplayai.com">
|
|
3
|
+
<img src="https://longplayai.com/brand/indiana-mark.png" alt="Longplay" width="72" height="72" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
# @tinkerlandai/longplay-sdk
|
|
8
|
+
|
|
9
|
+
This source checkout is SDK 0.10.4, 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.
|
|
10
|
+
|
|
11
|
+
[简体中文](./README.zh-CN.md)
|
|
12
|
+
|
|
13
|
+
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.
|
|
14
|
+
|
|
15
|
+
**IAM source scanning, generation, validation and upload belong to the independent build package `@tinkerlandai/longplay-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).
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install --save-exact @tinkerlandai/longplay-sdk@latest
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The first install resolves latest and saves an exact package.json version. Commit the lockfile and use npm ci for later builds. Use the original package identity and exact version from the [release records](../../docs/sdk-releases.md) for historical installations and rollback. New Longplay package names require their own publication; source changes do not publish artifacts.
|
|
22
|
+
|
|
23
|
+
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).
|
|
24
|
+
|
|
25
|
+
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.
|
|
26
|
+
|
|
27
|
+
## Plain HTML / script installation
|
|
28
|
+
|
|
29
|
+
The classic-script entry reuses the npm browser implementation, with no bundler:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
SDK_MODULE_URL="$(curl -fsS https://longplayai.com/sdk/module)" &&
|
|
33
|
+
printf 'export * from "%s";\n' "$SDK_MODULE_URL" > longplay-sdk.js
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
```html
|
|
37
|
+
<script type="module">
|
|
38
|
+
import {
|
|
39
|
+
createLongplay,
|
|
40
|
+
createPaywallSession,
|
|
41
|
+
showPaywall,
|
|
42
|
+
createFunnelRunController,
|
|
43
|
+
showFunnelRun,
|
|
44
|
+
mountContent,
|
|
45
|
+
} from "./longplay-sdk.js";
|
|
46
|
+
const client = createLongplay({
|
|
47
|
+
baseUrl: "https://api.longplayai.com",
|
|
48
|
+
scope: { projectId: "<APP_UUID>", environment: "sandbox" },
|
|
49
|
+
});
|
|
50
|
+
// Obtain the complete session pair from your authenticated backend.
|
|
51
|
+
// client.identify(session);
|
|
52
|
+
// await client.readHoldings();
|
|
53
|
+
// client.track("page_viewed", { page: location.pathname });
|
|
54
|
+
// await client.flush();
|
|
55
|
+
</script>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Commit the generated longplay-sdk.js to lock the immutable module URL; resolve it again only when upgrading. The optional classic loader at `https://longplayai.com/sdk.js` still follows Web releases and uses matching API/credentials. `Longplay.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 `Longplay`. 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).
|
|
59
|
+
|
|
60
|
+
## React Native core
|
|
61
|
+
|
|
62
|
+
Use the independent `@tinkerlandai/longplay-react-native` package. The `@tinkerlandai/longplay-sdk/react-native` subpath shares the same implementation. Both support Customer Token identity, holdings, entitlement checks and usage reporting through your authenticated backend. It requires no DOM or native UI/store bridge. See [mobile SDK installation and examples](../../docs/mobile-sdk.md).
|
|
63
|
+
|
|
64
|
+
## Version compatibility and support
|
|
65
|
+
|
|
66
|
+
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).
|
|
67
|
+
|
|
68
|
+
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.
|
|
69
|
+
|
|
70
|
+
## Public entry points
|
|
71
|
+
|
|
72
|
+
| Import | Purpose |
|
|
73
|
+
| ---------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
74
|
+
| `@tinkerlandai/longplay-sdk` | Browser Customer, Funnel, Feature, and standalone Paywall clients |
|
|
75
|
+
| `@tinkerlandai/longplay-sdk/server` | Trusted Node.js backend, Customer Sessions, business writes, and Funnel decisions |
|
|
76
|
+
| `@tinkerlandai/longplay-sdk/react-native` | Portable Customer core and authenticated-backend usage relay; no DOM presentation |
|
|
77
|
+
| `@tinkerlandai/longplay-sdk/popup` | Standalone screen rendering, sanitization, and previews |
|
|
78
|
+
| `@tinkerlandai/longplay-sdk/openrouter` | Optional OpenRouter usage integration; requires its peer dependency |
|
|
79
|
+
| `@tinkerlandai/longplay-sdk/native-shell` | Static Funnel shell assets for native WebViews |
|
|
80
|
+
| `@tinkerlandai/longplay-sdk/page` | Anonymous website URL experiments using a public App environment ID |
|
|
81
|
+
| `@tinkerlandai/longplay-sdk/visual` | Anonymous visual experiments and exposure collection using a public environment ID |
|
|
82
|
+
|
|
83
|
+
The same package supports JavaScript and TypeScript and includes type declarations. Internal `@indiana/contracts` code is bundled; consumers do not install private workspace packages. Swift and Kotlin use their own package ecosystems and are released separately.
|
|
84
|
+
|
|
85
|
+
For an independent **Page, Popup or Banner resource**, import `createLongplay` and `mountContent` from the root entry, resolve it with `longplay.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).
|
|
86
|
+
|
|
87
|
+
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.
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
## API origin and test services
|
|
92
|
+
|
|
93
|
+
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).
|
|
94
|
+
|
|
95
|
+
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 `LongplayConfiguration(scope: ...)` and Kotlin `LongplayConfiguration(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.
|
|
96
|
+
|
|
97
|
+
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.
|
|
98
|
+
|
|
99
|
+
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).
|
|
100
|
+
|
|
101
|
+
## Create a Customer Session from a trusted backend
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { createLongplayServer } from "@tinkerlandai/longplay-sdk/server";
|
|
105
|
+
|
|
106
|
+
const longplay = createLongplayServer({
|
|
107
|
+
baseUrl: process.env.INDIANA_BASE_URL!,
|
|
108
|
+
apiKey: process.env.INDIANA_SECRET_KEY!,
|
|
109
|
+
scope: {
|
|
110
|
+
projectId: process.env.INDIANA_PROJECT_ID!,
|
|
111
|
+
environment: "sandbox",
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const user = await requireSignedInUser(request);
|
|
115
|
+
const { customer } = await longplay.getOrCreateCustomer({
|
|
116
|
+
externalUserId: user.id,
|
|
117
|
+
});
|
|
118
|
+
const session = await longplay.createCustomerSession(customer.id);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Keep the Secret Key on your trusted backend. A Customer ID supplied by a browser is not proof of login. Give each Customer Token only to the corresponding authenticated user. App, Sandbox/Live environment, Customer identity, permissions, and allowed origins are checked independently. The SDK holds Customer credentials in memory; destroy the previous view and clear credentials when signing out or changing accounts.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { createLongplay } from "@tinkerlandai/longplay-sdk";
|
|
125
|
+
|
|
126
|
+
const longplay = createLongplay({
|
|
127
|
+
baseUrl,
|
|
128
|
+
scope: { projectId, environment: "sandbox" },
|
|
129
|
+
});
|
|
130
|
+
longplay.identify(session);
|
|
131
|
+
const { holdings } = await longplay.readHoldings();
|
|
132
|
+
// On sign-out:
|
|
133
|
+
longplay.reset();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
The SDK refreshes its Access/Refresh pair directly with GOP. Access lasts at most one hour, Refresh at most 24 hours per rotation, within the original 30-day session limit. Successful rotation preserves the Customer identity and pending work; a definitive refresh refusal or expired Refresh Token clears the credentials and requires a new verified login. Business requests do not extend expiry or automatically replay after refresh. Revocation and issuer expiry still apply. See [Customer credentials](../../docs/customer-credentials.md).
|
|
137
|
+
|
|
138
|
+
Customer updates are partial patches and do not use a Customer revision. When a Guest signs in to an existing Customer, the response reports `switched: true`. Resources and history keep their original owner; create a new Session/Funnel for the target Customer. Guest financial facts are not merged.
|
|
139
|
+
|
|
140
|
+
## Optional stable fingerprint
|
|
141
|
+
|
|
142
|
+
`fingerprint` is a host-supplied stable deduplication signal, such as an installation identifier. Initialize and retain it before the first Guest/Customer request, independently of login and credentials. Reuse it across Guest identification, account changes and session renewal. The SDK does not collect or generate it.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
// Trusted backend: retain installationSignal independently of login.
|
|
146
|
+
// verifiedUser.id comes from your application's authenticated session.
|
|
147
|
+
const { customer } = await longplay.getOrCreateCustomer({
|
|
148
|
+
externalUserId: verifiedUser.id,
|
|
149
|
+
fingerprint: installationSignal,
|
|
150
|
+
});
|
|
151
|
+
// Reuse the signal on Guest creation. Retain guestToken separately for retries.
|
|
152
|
+
await longplay.createGuestCustomer({
|
|
153
|
+
guestToken: retainedGuestToken,
|
|
154
|
+
fingerprint: installationSignal,
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
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.
|
|
159
|
+
|
|
160
|
+
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.
|
|
161
|
+
|
|
162
|
+
## Funnel, Feature, and standalone Paywall
|
|
163
|
+
|
|
164
|
+
A trusted backend calls `createFunnelStartClient().start()` with `funnelId`, a retained `requestId`, `customer`, `locale`, and `parameters`. The Customer reference is `{ type: "id", value: customerId }` or `{ type: "externalUserId", value: verifiedUser.id }`. The latter supports creation-only `customerData` and requires `customers:write`; an existing Customer is not updated. The response includes `customerId`, which your backend can use to issue a Customer Session. Historical start-body `customerToken` and decision-body `customerId` inputs are rejected.
|
|
165
|
+
|
|
166
|
+
`createFunnelClient({ customerToken, ...options })` is for clients that already hold a Customer credential. Its `start()` omits `customer` and `customerData` and runs as the credential's Customer. After response loss, retry with the same request ID and input. Put commercial parameters in `parameters.commerce`. Each Run has its own Run Token, which cannot switch Customer identity.
|
|
167
|
+
|
|
168
|
+
`createFunnelDecisionClient.submit/decide` and the server entry's `createFunnelCampaignClient` use the same explicit Customer reference. Campaign `decide()` waits for a `FunnelDecision`; a lookup can return the original accepted decision and its original request ID. Campaign `start()` returns either Run credentials or `{ suppressed: true, reason, retryAt }` when frequency or an active Run blocks a new start, without fabricating Run credentials. Report `shown` only after the Run is actually displayed; page closure or local storage cannot replace a durable presentation fact.
|
|
169
|
+
|
|
170
|
+
In a browser, use `createFunnelRunController` and `showFunnelRun`. The controller handles current-protocol commands, reads, retries, recovery, and subscriptions. Presentation handles provide `refresh()` and `destroy()`. Published nodes delegate business operations to the server. A client assertion or redirect is not payment evidence.
|
|
171
|
+
|
|
172
|
+
Each Funnel chooses local or remote evaluation. `createFunnelDecisionClient` supports `configuration()`, `features()`, submission, and result reads. `createFeatureClient` identifies configuration by `generation`. Downloaded local rules are visible to clients; use remote evaluation and verified business facts for sensitive decisions.
|
|
173
|
+
|
|
174
|
+
### Bucketing identity across independent consumers
|
|
175
|
+
|
|
176
|
+
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.
|
|
177
|
+
|
|
178
|
+
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.
|
|
179
|
+
|
|
180
|
+
`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#跨消费方求值边界与分桶身份).
|
|
181
|
+
|
|
182
|
+
### Server-side local Funnel evaluation
|
|
183
|
+
|
|
184
|
+
For a Local Funnel containing only Entry, Condition, Feature, Split and literal/Feature End or Output nodes, use the explicit local evaluator. Reuse the client/evaluator across requests; create a session for one verified identity and attribute/parameter snapshot:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
const local = createFunnelDecisionClient(options).local();
|
|
188
|
+
const session = local.session({
|
|
189
|
+
attributes: {
|
|
190
|
+
id: customer.id,
|
|
191
|
+
externalUserId: customer.externalUserId,
|
|
192
|
+
country: "US",
|
|
193
|
+
},
|
|
194
|
+
parameters: { placement: "home" },
|
|
195
|
+
});
|
|
196
|
+
const preview = await session.evaluate();
|
|
197
|
+
const used = await session.evaluate({ track: true });
|
|
198
|
+
// used.value is a local calculation, with no Decision/receipt ID.
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
`local_configuration` downloads the scoped dependency projection through the existing backend credential checks. Concurrent loads share one request, definitions are prepared once per Version/policy revision/GB generation, and reuse stops at the supplied expiry or 60 seconds, whichever is earlier. `local.refresh()` forces a fresh read; failed refreshes never fall back to stale rules. `local.reset()` invalidates cached definitions and existing sessions. Create a new session after an identity or context change. Browser execution is refused.
|
|
202
|
+
|
|
203
|
+
Sessions retain Sticky assignments in memory. Supply `stickyBucketAssignmentDocs` and `saveStickyBucketAssignmentDoc` for host persistence; the host must scope storage by App/environment/identity and own any cross-process merge/CAS. Async saves finish before a result is returned. A failed save retains the original assignment and rejects with `STICKY_SAVE_FAILED`; retry `session.flush()` or the evaluation. In-memory state alone provides no cross-device consistency.
|
|
204
|
+
|
|
205
|
+
Evaluation is untracked by default. `{ track: true }` invokes configured `onExperimentViewed`/`onFeatureUsage` callbacks once per native assignment/value in that session after successful evaluation and Sticky persistence. Connect callbacks to the existing event tracker only when the result is actually used. Callback errors reach `onError`; event delivery/retries remain the tracker's responsibility, and evaluation does not manufacture durable exposure or usage receipts.
|
|
206
|
+
|
|
207
|
+
Mixed graphs, resource/Bundle outputs and expired configurations fail explicitly. There is no automatic remote execution. `decide()` retains its server-side idempotency, committed receipts and financial checks; local output never grants a Coupon, purchase, entitlement or campaign frequency promise.
|
|
208
|
+
|
|
209
|
+
### Standalone Feature delivery
|
|
210
|
+
|
|
211
|
+
Explicitly grant a backend Key `features:read` / `events:write` and issue existing Customer/Guest Sessions to browser clients.
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
// Customer data supplied by your trusted backend for the current user.
|
|
215
|
+
const bucketingIdentity = {
|
|
216
|
+
id: customer.id,
|
|
217
|
+
externalUserId: customer.externalUserId,
|
|
218
|
+
};
|
|
219
|
+
const flags = await longplay.features({
|
|
220
|
+
attributes: { ...bucketingIdentity, country: "CA" },
|
|
221
|
+
});
|
|
222
|
+
const enabled = flags.getFeatureValue("new-checkout", false);
|
|
223
|
+
const unsubscribe = flags.subscribe(() =>
|
|
224
|
+
updateView(flags.isOn("new-checkout")),
|
|
225
|
+
);
|
|
226
|
+
flags.setAttributes({ ...bucketingIdentity, country: "US" });
|
|
227
|
+
longplay.track("Checkout Viewed", { source: "pricing" });
|
|
228
|
+
await longplay.flush();
|
|
229
|
+
// On page teardown:
|
|
230
|
+
unsubscribe();
|
|
231
|
+
flags.destroy();
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The selected environment's integration settings control behavior events, experiment exposures and Feature usage independently. New environments enable only exposure collection; existing environments retain all three. `flush()` includes a `skipped` count for intentionally disabled collection. The database rechecks every batch, including direct SDK/server and Funnel ingestion. Running experiment/Bandit dependencies prevent incompatible collection changes. These controls do not delete history or affect commercial usage accounting.
|
|
235
|
+
|
|
236
|
+
Flags default to local evaluation; Owner/Admin can switch each flag to server evaluation. Server-evaluated flags deliver only keys and results, using the caller's fallback until the first result arrives. Attribute changes immediately discard old results. List hashes are not a secrecy boundary. Local Funnels cannot depend on server-evaluated flags; mode switches also check active historical Runs. `identify` / `reset` release the previous identity's Feature instances and event queue.
|
|
237
|
+
|
|
238
|
+
Merchandise targeting resolves a trusted item in the selected App/environment. Register its attributes and maintain the
|
|
239
|
+
item in Console, then pass a reference rather than item metadata. The SDK resolves the initial SKU before returning;
|
|
240
|
+
after switching items, await context refresh before reading the next decision. Item experiments use `merchandiseId`
|
|
241
|
+
(the internal UUID) without a Customer fallback; Customer experiments keep their own bucketing identity.
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
const merchandiseFlags = await longplay.features({
|
|
245
|
+
attributes: { ...bucketingIdentity, merchandise: { id: "sku-outdoor" } },
|
|
246
|
+
});
|
|
247
|
+
merchandiseFlags.setAttributes({
|
|
248
|
+
...bucketingIdentity,
|
|
249
|
+
merchandise: { id: "sku-indoor" },
|
|
250
|
+
});
|
|
251
|
+
await merchandiseFlags.refreshRemote();
|
|
252
|
+
const eligible = merchandiseFlags.isOn("item-promotion");
|
|
253
|
+
longplay.track("Item Viewed", {}, { merchandise: { id: "sku-indoor" } });
|
|
254
|
+
await longplay.flush();
|
|
255
|
+
merchandiseFlags.destroy();
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Offering pins the Product Version scope and hierarchy. Paywall pins a published Offering Version and independently chooses its displayed Product subset, groups, ordering, and default selection. `createPaywallStartClient`, `createPaywallSession`, and `showPaywall` use a standalone Paywall Session. After payment, read Entitlements again. Your backend still authorizes requests and consumes usage idempotently.
|
|
259
|
+
|
|
260
|
+
To render a Paywall while handing purchase selection to your own app, pass `onPurchaseIntent` to `showPaywall`. It receives
|
|
261
|
+
`actionId`, the selected `productVersionId`, a copy of the UI `selection`, and validated `featureQuantities`. That callback
|
|
262
|
+
replaces quote creation for the clicked purchase button; it neither charges nor grants access. Share the identified
|
|
263
|
+
client's `session` with `createPaywallSession` to retain automatic Access/Refresh rotation. Your session endpoint must
|
|
264
|
+
verify the native user before issuing credentials; reset the client and destroy the Paywall when that user changes.
|
|
265
|
+
|
|
266
|
+
## Commercial capabilities and current service boundaries
|
|
267
|
+
|
|
268
|
+
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.
|
|
269
|
+
|
|
270
|
+
In Stripe environments, Longplay executes `longplay` 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.
|
|
271
|
+
|
|
272
|
+
This beta does not certify complete commercial or production operation. Remaining service gaps include:
|
|
273
|
+
|
|
274
|
+
- Subscription batch processing. Conflict queries and retries have services but still require a matching deployment and business prerequisites.
|
|
275
|
+
- Postpaid Billing Authorization, Billing Coupon lists, and billing-adjustment confirmation. AutoTopup consent preparation/confirmation/revocation is implemented independently.
|
|
276
|
+
- Complete delivery for `claimRewardProduct`.
|
|
277
|
+
- Real Apple/Google store acceptance. Independent purchase/restore contracts and partial services do not certify store acceptance. `createNativeReceiptClient().recover()` uses `subscriptions/native/restore`.
|
|
278
|
+
|
|
279
|
+
Implemented commercial operations use the original credential, permission, scope and configured-provider checks in both Sandbox and Live. An unsupported operation returns 501; a provider configuration or business precondition is not a migration allowlist.
|
|
280
|
+
|
|
281
|
+
## AI usage
|
|
282
|
+
|
|
283
|
+
The server entry provides AI SDK usage middleware; the OpenRouter entry provides a wrapper. Your host supplies a persistent idempotency key per invocation, usage/quote storage, and recovery callbacks. Recovery replays the original quoted charge without running the model again. Keep provider keys on your trusted backend.
|
|
284
|
+
|
|
285
|
+
## Publication and verification
|
|
286
|
+
|
|
287
|
+
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.
|
|
288
|
+
|
|
289
|
+
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.
|
|
290
|
+
|
|
291
|
+
## Shared public configuration
|
|
292
|
+
|
|
293
|
+
Use one manager per trusted origin and exact App/environment for Features, canonical Funnel files and enabled page experiments. Use a matching runtime and an explicitly reviewed public configuration publication.
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import {
|
|
297
|
+
createConfigurationSync,
|
|
298
|
+
createLongplay,
|
|
299
|
+
} from "@tinkerlandai/longplay-sdk";
|
|
300
|
+
|
|
301
|
+
// Use a Sandbox App and credentials issued by the production deployment.
|
|
302
|
+
const apiUrl = "https://api.longplayai.com";
|
|
303
|
+
const scope = { projectId, environment: "sandbox" as const, environmentId };
|
|
304
|
+
const configuration = createConfigurationSync({
|
|
305
|
+
origin: apiUrl,
|
|
306
|
+
scope,
|
|
307
|
+
});
|
|
308
|
+
const longplay = createLongplay({
|
|
309
|
+
baseUrl: apiUrl,
|
|
310
|
+
scope: { projectId, environment: "sandbox" },
|
|
311
|
+
configuration,
|
|
312
|
+
});
|
|
313
|
+
const features = await longplay.features();
|
|
314
|
+
const document = await configuration.funnel(funnelId);
|
|
315
|
+
// Other consumers receive this same configuration instance.
|
|
316
|
+
// Supply existing credentials separately for business operations.
|
|
317
|
+
features.destroy();
|
|
318
|
+
configuration.destroy(); // Host owns the shared lifetime.
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
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.
|
|
322
|
+
|
|
323
|
+
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.
|
|
324
|
+
|
|
325
|
+
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.
|
|
326
|
+
|
|
327
|
+
## Local Funnel execution
|
|
328
|
+
|
|
329
|
+
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.
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
import {
|
|
333
|
+
createLocalFunnelClient,
|
|
334
|
+
showFunnelRun,
|
|
335
|
+
} from "@tinkerlandai/longplay-sdk";
|
|
336
|
+
|
|
337
|
+
const flow = await createLocalFunnelClient({
|
|
338
|
+
baseUrl,
|
|
339
|
+
configuration,
|
|
340
|
+
funnelId,
|
|
341
|
+
credential: customerToken,
|
|
342
|
+
});
|
|
343
|
+
const started = await flow.start({
|
|
344
|
+
requestId: savedStartRequestId, // Persist before sending; reuse the exact input after an unknown response.
|
|
345
|
+
locale: "en",
|
|
346
|
+
parameters: { source: "checkout" },
|
|
347
|
+
});
|
|
348
|
+
const host = await showFunnelRun(container, {
|
|
349
|
+
client: started.controller,
|
|
350
|
+
onError: reportError,
|
|
351
|
+
});
|
|
352
|
+
// Retain started.runToken in your host's secure credential storage.
|
|
353
|
+
// Restore the same Run: await flow.restore(originalRunToken).
|
|
354
|
+
host.destroy(); // Detach UI; accepted backend work remains owned by Indiana.
|
|
355
|
+
started.controller.reset();
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
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.
|
|
359
|
+
|
|
360
|
+
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.
|
|
361
|
+
|
|
362
|
+
After an advance signal is accepted, actions, waits, retries and recovery use the same GOP driver and Cloud Tasks / Cloud Scheduler as Remote. A disconnected host does not cancel accepted work. Before the required signal arrives, the Run remains parked. The bounded SDK loop settles up to 32 calculation boundaries per call; longer chains continue on a later read.
|
|
363
|
+
|
|
364
|
+
### Shared UI host
|
|
365
|
+
|
|
366
|
+
Screens, live Customer/Coupon data, shown acknowledgements, Paywall and external-task handling use the same Run controller and UI host. Canonical node/command history uses the common recording limits. Recovery uses authoritative Run, command and business receipts, without an observation-upload API or a second storage authority. There is no second host business state machine, `invoke` adapter, Local checkpoint owner, `browserFunnelStorage` or Local presentation controller.
|
|
367
|
+
|
|
368
|
+
Recovery uses the original Run Token and command identities. Do not turn an unknown business outcome into a new start/request ID. The SDK does not import legacy checkpoints. A mode change never rewrites a Run's accepted policy or commands; resolve that original Run before starting another intent.
|
|
369
|
+
|
|
370
|
+
The native renderer shell uses the same pure JavaScript calculation adapter and native Run-command bridge; credentials remain in the native session. A headless Swift/Kotlin session exposes the pending calculation and does not silently calculate it remotely. Wire schemas and the renderer shell are generated together. Installing the package does not migrate a database or deploy a runtime. Browser, provider, capacity and native-store acceptance remain separate.
|
|
371
|
+
|
|
372
|
+
## Web IAM entries and Funnel page experiments
|
|
373
|
+
|
|
374
|
+
Install the entry host once, then configure screen-entry or button/link triggers on a Funnel's Entry node in the App page map. Save and publish through the existing Funnel lifecycle. Use the merchant build's verified IAM digest and the same Customer session as other SDK clients:
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
import { createIamFunnelEntries } from "@tinkerlandai/longplay-sdk";
|
|
378
|
+
|
|
379
|
+
const entries = createIamFunnelEntries({
|
|
380
|
+
scope: { projectId, environment: "sandbox" },
|
|
381
|
+
environmentId,
|
|
382
|
+
baseUrl,
|
|
383
|
+
appManifestRelease, // digest resolved from this build's IAM release descriptor
|
|
384
|
+
session: longplay.session,
|
|
385
|
+
container,
|
|
386
|
+
onStatus: (status, entryId) => console.log(status, entryId),
|
|
387
|
+
});
|
|
388
|
+
await entries.start();
|
|
389
|
+
// After an uncertain start, reuse the original in-memory request:
|
|
390
|
+
await entries.retry();
|
|
391
|
+
// On host teardown:
|
|
392
|
+
entries.destroy();
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
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.
|
|
396
|
+
|
|
397
|
+
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.
|
|
398
|
+
|
|
399
|
+
## Funnel-scoped resource requests
|
|
400
|
+
|
|
401
|
+
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:
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
const resources = longplay.forFunnel({ funnelId, versionId });
|
|
405
|
+
await resources.claimCoupon(couponId, savedRequestId);
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
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 `longplay.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.
|
|
409
|
+
|
|
410
|
+
## Standalone Pages, Popups and Banners
|
|
411
|
+
|
|
412
|
+
Publish a Banner with no required inputs and a declared `continue` action. Issue a Customer Token with `content:read`, then explicitly resolve and mount it. Use the matching `kind` for Page or Popup.
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
import {
|
|
416
|
+
createLongplay,
|
|
417
|
+
mountContent,
|
|
418
|
+
type ContentActionIntent,
|
|
419
|
+
} from "@tinkerlandai/longplay-sdk";
|
|
420
|
+
|
|
421
|
+
export async function showBanner(
|
|
422
|
+
container: HTMLElement,
|
|
423
|
+
customerToken: string,
|
|
424
|
+
contentId: string,
|
|
425
|
+
onContinue: (intent: ContentActionIntent) => Promise<void>,
|
|
426
|
+
) {
|
|
427
|
+
const longplay = createLongplay({
|
|
428
|
+
baseUrl: "https://api.longplayai.com",
|
|
429
|
+
scope: { projectId: "<APP_UUID>", environment: "sandbox" },
|
|
430
|
+
});
|
|
431
|
+
longplay.identify(customerToken);
|
|
432
|
+
const { content } = await longplay.resolveContent({
|
|
433
|
+
kind: "banner",
|
|
434
|
+
selector: { type: "resource", id: contentId },
|
|
435
|
+
});
|
|
436
|
+
const host = await mountContent(container, {
|
|
437
|
+
content,
|
|
438
|
+
actions: { continue: onContinue },
|
|
439
|
+
onError: console.error,
|
|
440
|
+
});
|
|
441
|
+
return {
|
|
442
|
+
retryAction: host.retryAction,
|
|
443
|
+
destroy() {
|
|
444
|
+
host.destroy();
|
|
445
|
+
longplay.reset();
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Provide every declared action handler. `onContinue` uses `intent.requestId` as its backend idempotency key; `retryAction()` reuses that intent after a failure. Call `destroy()` on unmount, sign-out or account change. The host renders content and emits intents; your backend still authorizes and executes business actions.
|
|
452
|
+
|
|
453
|
+
To remove module loading, document preparation and iframe startup from the opening click, resolve the current Customer's content earlier and prepare it explicitly:
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
import { prepareContent } from "@tinkerlandai/longplay-sdk";
|
|
457
|
+
|
|
458
|
+
const lifetime = new AbortController();
|
|
459
|
+
const popup = await prepareContent(container, {
|
|
460
|
+
content, // Already resolved with kind: "popup" for this Customer.
|
|
461
|
+
actions: { continue: onContinue },
|
|
462
|
+
onError: console.error,
|
|
463
|
+
signal: lifetime.signal,
|
|
464
|
+
});
|
|
465
|
+
openButton.addEventListener("click", () => popup.show(), {
|
|
466
|
+
signal: lifetime.signal,
|
|
467
|
+
});
|
|
468
|
+
// On unmount, sign-out or account/scope change:
|
|
469
|
+
// lifetime.abort();
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
Preparation waits for the first render without opening the dialog or running actions. `show()` reveals the same iframe and is idempotent; destroy and prepare a fresh handle for another presentation. Abort also cancels preparation while it is pending. This does not pre-authorize business operations or guarantee that images have decoded. For low-level ScreenDocument rendering, `/popup` provides `preparePopup(document, options)` with `ready`, `show()` and `destroy()`.
|
|
473
|
+
|
|
474
|
+
Customer sessions contain `customerId`, `sessionId`, `accessToken`, `refreshToken`, `expiresAt` and `refreshExpiresAt`. Return the pair only to the authenticated merchant user with `Cache-Control: no-store`, then call `longplay.identify(session)`. The SDK refreshes directly with GOP and keeps credentials in memory; account reset fences pending refresh. Your backend uses `revokeCustomerSessions({customerId, sessionId?})` for revocation. Share `longplay.session` through the `session` option of independent Paywall/Funnel clients to preserve one refresh lifecycle.
|
|
475
|
+
|
|
476
|
+
## Merchant-recorded billing
|
|
477
|
+
|
|
478
|
+
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.
|
|
479
|
+
|
|
480
|
+
## Merchant bridge
|
|
481
|
+
|
|
482
|
+
`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.
|
|
483
|
+
|
|
484
|
+
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.
|
|
485
|
+
|
|
486
|
+
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.
|