@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.
Files changed (56) hide show
  1. package/CHANGELOG.md +209 -0
  2. package/README.md +485 -2
  3. package/README.zh-CN.md +375 -0
  4. package/THIRD_PARTY_NOTICES.md +101 -0
  5. package/dist/ai-usage-Btwg1nD7.d.ts +247 -0
  6. package/dist/api-5gAz4-Sb.d.ts +16897 -0
  7. package/dist/chunk-2HFA34QO.js +6 -0
  8. package/dist/chunk-7DXL3LHT.js +1 -0
  9. package/dist/chunk-ACF2FBUB.js +1 -0
  10. package/dist/chunk-CLXORAOK.js +1 -0
  11. package/dist/chunk-DSPLGTQI.js +1 -0
  12. package/dist/chunk-EARSGITJ.js +1 -0
  13. package/dist/chunk-G56XGVMS.js +1 -0
  14. package/dist/chunk-HOW7NIIR.js +1 -0
  15. package/dist/chunk-JB46ZKGZ.js +1 -0
  16. package/dist/chunk-KBM7BYQY.js +1 -0
  17. package/dist/chunk-MRN4HP4W.js +1 -0
  18. package/dist/chunk-RE6BIFLN.js +2 -0
  19. package/dist/chunk-SBPTOOYB.js +1 -0
  20. package/dist/chunk-SSLZKIIU.js +1 -0
  21. package/dist/chunk-VMM3GA3L.js +4 -0
  22. package/dist/chunk-VOEFXEPP.js +1 -0
  23. package/dist/chunk-X6BCUINI.js +173 -0
  24. package/dist/chunk-XSVQHPQT.js +17 -0
  25. package/dist/chunk-YQHPEP4X.js +1 -0
  26. package/dist/chunk-ZPZVSC4J.js +1 -0
  27. package/dist/configuration-sync-CywDxRkX.d.ts +1698 -0
  28. package/dist/content-NYMIZRNR.js +1 -0
  29. package/dist/coupon-fA1QujHt.d.ts +184 -0
  30. package/dist/customer-page-km5f1PJZ.d.ts +480 -0
  31. package/dist/funnel-local-client-CVokI0rx.d.ts +46997 -0
  32. package/dist/funnel-node-evaluation-P5B4fPfJ.d.ts +2054 -0
  33. package/dist/funnel-resource-client-Cv5OjfAn.d.ts +14525 -0
  34. package/dist/funnel-runtime-host-UL4DEJDU.js +1 -0
  35. package/dist/growth-rules-B4YYy64R.d.ts +173 -0
  36. package/dist/index.d.ts +23545 -0
  37. package/dist/index.js +1 -0
  38. package/dist/native-shell.d.ts +4 -0
  39. package/dist/native-shell.js +1 -0
  40. package/dist/openrouter.d.ts +43 -0
  41. package/dist/openrouter.js +1 -0
  42. package/dist/page.d.ts +134 -0
  43. package/dist/page.js +1 -0
  44. package/dist/paywall-EJCNWDHK.js +1 -0
  45. package/dist/popup.d.ts +191 -0
  46. package/dist/popup.js +1 -0
  47. package/dist/react-native.d.ts +36701 -0
  48. package/dist/react-native.js +1 -0
  49. package/dist/screen-D81zCpfi.d.ts +3000 -0
  50. package/dist/screen-renderer-JMHV4GPP.js +1 -0
  51. package/dist/server.d.ts +7073 -0
  52. package/dist/server.js +1 -0
  53. package/dist/visual-dom-DQLPBZ2Z.js +1 -0
  54. package/dist/visual.d.ts +87 -0
  55. package/dist/visual.js +1 -0
  56. package/package.json +90 -3
@@ -0,0 +1,375 @@
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>
6
+
7
+ [English](./README.md)
8
+
9
+ 当前源码为 SDK 0.10.4、协议 0.10。本页示例使用 `https://api.longplayai.com` 以及生产部署内签发的 Sandbox App/凭据。Sandbox 仍属于生产数据,不能复用独立测试部署的凭据。运行前核对[环境状态](../../docs/development-status.md),确认服务已就绪。
10
+
11
+ # @tinkerlandai/longplay-sdk
12
+
13
+ 业务 SDK 按职责分为 **身份、商业、实验引擎、Analytics、内容交付(Offering、Paywall、Page、Popup、Banner)和 Funnel 六组能力**,共用协议、传输、配置、渲染与恢复等基础设施。分类沿用系统的 L0–L4 归属,各语言包和入口支持的子集不同。客户端与商户服务端共用契约和核心行为,凭据及可执行操作分开。Indianapolis 是它们调用的 SDK 运行服务,不是商户安装的服务端 SDK。
14
+
15
+ **IAM 的源码扫描、生成、校验和上传属于独立构建包 `@tinkerlandai/longplay-iam`。** 普通业务 SDK 只保留 GB visual 应用页面变更所需的运行时 release 绑定。完整定义见 [SDK 边界](../../docs/sdk-architecture.md) 与 [IAM 构建包](../iam/README.md)。
16
+
17
+ ```sh
18
+ npm install --save-exact @tinkerlandai/longplay-sdk@latest
19
+ ```
20
+
21
+ 首次安装解析最新稳定版,--save-exact 将结果固定在 package.json。提交锁文件,后续构建使用 npm ci;历史安装或回滚请沿用[发布记录](../../docs/sdk-releases.md)中的原包名和精确版本。Longplay 新包名需要单独发布,源码修改不代表已经发布。
22
+
23
+ SDK 0.10.4 要求 HTTP 协议 0.10。服务端须使用同一当前协议,不提供旧协议兼容;发布包不会部署服务端。实际发布、标签与环境验收范围见 [SDK 发布总账](../../docs/sdk-releases.md) 和 [开发状态](../../docs/development-status.md)。
24
+
25
+ 数据/会话层负责公开 JSON、完整性校验和鉴权操作;展示宿主将会话接口接入打包的屏幕渲染器,渲染器消费已提供的数据并返回动作意图。客户端按需加载展示;`/server` 入口使用商户后端的受限 Server Key,不加载屏幕展示。SDK 本地检查不替代运行时授权或付款核实。后台 Preview 为同一个展示宿主提供模拟会话和数据。
26
+
27
+ ## 普通 HTML / script 接入
28
+
29
+ 普通 script 标签复用 npm 浏览器实现,无需打包工具:
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
+ // 从验证登录后的自有后端获取完整 session 凭据对。
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
+ 提交生成的 longplay-sdk.js,即可锁定不可变模块地址;仅在升级时重新解析。经典脚本 `https://longplayai.com/sdk.js` 仍随 Web 发布更新,并配套使用同环境 API 和凭据。`Longplay.ready` 返回根入口的全部浏览器导出,以及 `createPageExperiments` / `createVisualExperiments`;下文 npm import 可替换为从该 Promise 解构同名函数。加载不自动创建会话、发送事件或启动实验。Server Key 留在后端,Funnel 启动恢复与退出清理沿用现有流程。重复标签共用 ready,失败会拒绝 Promise,UI 按需加载。加载标签使用普通 script;添加 async/defer 时先等标签 load 事件,再访问 `Longplay`。CSP 需允许脚本来源和 API 连接。页面实验也使用该统一入口,显式创建客户端并调用 `start()`。详见 [script 分发](../../docs/sdk-architecture.md#web-script-distribution)和[完整接入示例](https://longplayai.com/docs/javascript#script)。
59
+
60
+ ## React Native 核心入口
61
+
62
+ React Native 使用独立包 `@tinkerlandai/longplay-react-native`,原 `@tinkerlandai/longplay-sdk/react-native` 子入口共用同一实现。两者均支持 Customer Token 身份接入、权益读取和经业务后端转发的用量上报。此入口无需 DOM,不包含原生 UI 或内购桥接。参见[移动端 SDK 安装与示例](../../docs/mobile-sdk.md)。
63
+
64
+ ## Funnel Local / Remote 统一执行
65
+
66
+ Local / Remote 共用 Longplay Run、command/action 回执、业务服务、幂等和恢复。Local SDK 只预测纯条件、Feature/实验路径用于展示,再提交 `commit_segment` 推进信号;GOP 根据 Run 输入和批准的公共 GB 配置重算并接受路径。Remote 在 GOP 计算,不依赖客户端公共配置。
67
+
68
+ `createLocalFunnelClient().start({ requestId, locale, parameters })` 返回 `runToken` 和同一个 `controller`,传给 `showFunnelRun` 即可。原 Server Key 的商户服务端另外指定原 Longplay Customer UUID;浏览器使用 Customer Token,不能改写 Customer。恢复必须使用安全保存的原 Run token,不能把旧 Local checkpoint 或未知发券结果转换为一个新请求身份。
69
+
70
+ 推进信号尚未提交时,Run 停车并释放 Worker,仍需 Local 宿主返回;信号受理后,后台业务操作通过 GOP 的 Cloud Tasks/Cloud Scheduler 恢复。Local SDK 不直接发券,也没有第二套业务执行器。独立 Coupon API 保留自己的自领政策、权限、资格、库存、幂等和有效期,不要求创建 Funnel。
71
+
72
+ Flow `commit_segment` 只包含固定 requestId、停车位置 nodeId/visit/revision、policyRevision 和 generation,不接受调用方步骤、计算结果或边界。SDK 的展示预测服从服务器受理结果;回包丢失使用原命令身份恢复。原生渲染壳复用同一纯求值器与 Run bridge;无界面 Swift/Kotlin 返回待本地推进状态,不自动改成 Remote。详见[Local 执行](README.md#local-funnel-execution)。
73
+
74
+ ## 版本兼容与支持
75
+
76
+ 当前 0.x 仅支持当前协议;1.0 起同一主版本内保持兼容。新稳定主版本发布后,旧稳定主版本至少继续支持 12 个日历月,停止支持提前至少 180 天公开通知,实际日期取两者中较晚者。详见 [版本与支持政策](../../docs/sdk-versioning-and-support.md) 和公开开发文档 `/docs/release-notes#support-policy`。
77
+
78
+ SDK 发送当前协议及 SDK 版本请求头,并校验成功响应的协议声明;旧 0.x 协议不会自动转换成当前协议。源码更新不会修改已有 npm 包,也不会自动部署服务端。
79
+
80
+ ## 公开入口
81
+
82
+ | 导入入口 | 用途 |
83
+ | ---------------------------------------- | ----------------------------------------------------------- |
84
+ | `@tinkerlandai/longplay-sdk` | 浏览器 Customer、Funnel、Feature 与独立 Paywall 客户端 |
85
+ | `@tinkerlandai/longplay-sdk/server` | 可信 Node.js 后端、Customer Session、业务写入和 Funnel 决策 |
86
+ | `@tinkerlandai/longplay-sdk/react-native` | Customer 核心和经认证业务后端转发的用量上报,不含 DOM 展示 |
87
+ | `@tinkerlandai/longplay-sdk/popup` | 独立屏幕渲染、清洗和预览 |
88
+ | `@tinkerlandai/longplay-sdk/openrouter` | 可选 OpenRouter 用量集成,需要安装对应 peer dependency |
89
+ | `@tinkerlandai/longplay-sdk/native-shell` | 原生 WebView 的静态 Funnel shell 资源;不代表商店购买验收 |
90
+ | `@tinkerlandai/longplay-sdk/page` | 匿名商户页面的网址实验;使用公开的 App environment ID |
91
+ | `@tinkerlandai/longplay-sdk/visual` | 匿名商户页面的视觉实验及曝光采集;使用公开的环境 ID |
92
+
93
+ JavaScript 与 TypeScript 共用本包,声明文件随包提供。内部 `@indiana/contracts` 在构建时内联,接入方无需安装私有包。Swift 和 Kotlin 使用各自的包管理生态与独立产物。
94
+
95
+ 展示独立的 **Page、Popup 或 Banner 资源**,从主入口导入 `createLongplay` 和 `mountContent`,先用 `longplay.resolveContent(...)` 解析,再挂载结果。`/page` 用于商户现有页面的 GB 网址跳转实验,`/visual` 用于该页面的 GB 视觉修改;`/popup` 提供底层屏幕渲染与预览工具,不负责选择或解析 Popup 资源。现有公开导入路径保持不变,详见[入口职责](../../docs/sdk-architecture.md#public-entry-points-and-capability-ownership)。
96
+
97
+ 独立的 [Python 服务端 SDK](../python/README.md) 使用同一当前 HTTP 协议,提供同步、异步 Customer、持仓/权益检查和用量上报。它从 `https://longplayai.com/sdk/python` 分发(不在 PyPI),不属于这个 npm 包。
98
+
99
+ [Go 服务端 SDK](../go/README.md) 提供同样的首版能力,使用类型化响应、Context 取消和 goroutine 并发复用客户端。通过 Go 默认模块代理以 `go get longplayai.com/sdk/go` 安装,与其他业务 SDK 共用发行版本。
100
+
101
+ ## API 默认地址与自测
102
+
103
+ 默认地址、四个域名、版本标签和测试配置的持续规范见 [SDK API 入口与开发测试规范](../../docs/sdk-endpoints-and-testing.md)。
104
+
105
+ 省略 `baseUrl` 时默认请求 `https://api.longplayai.com`;Customer/Server、Funnel、Feature、Paywall、页面与视觉实验客户端共用此入口。公共配置同步的 `origin` 也可省略并使用同一默认值。Swift `LongplayConfiguration(scope: ...)`、Kotlin `LongplayConfiguration(scope = ...)` 采用同一默认地址与统一发行版本;仍可显式覆盖 `baseURL`/`baseUrl`。Funnel/Paywall 的壳和渲染器随 SDK 本地打包,不从 API origin 下载可执行代码。
106
+
107
+ 包含此默认值的版本无论标记为 `beta` 或 `latest`,都使用生产 API 地址。标签选择包版本,不选择服务环境。两套 API 入口经 Vercel 转发到各自 Go Indianapolis,入口和业务就绪分别验收。
108
+
109
+ 同一次接入中的所有客户端与配置同步实例使用同一个 API origin。`environment: "sandbox"` 只选择该部署内的业务环境,不选择另一套服务或数据库;错误或未知写入结果不能触发跨环境自动重试。
110
+
111
+ 独立测试 API `https://api.dev.longplayai.com` 必须使用独立测试凭据,并部署当前协议 0.10。维护者测试必须显式指定目标,不能回落生产默认值,见[测试流程](../../docs/sdk-selfserve.md#maintainer-api-origin-testing)。
112
+
113
+ ## 从可信后端创建 Customer Session
114
+
115
+ ```ts
116
+ import { createLongplayServer } from "@tinkerlandai/longplay-sdk/server";
117
+
118
+ const longplay = createLongplayServer({
119
+ baseUrl: process.env.INDIANA_BASE_URL!,
120
+ apiKey: process.env.INDIANA_SECRET_KEY!,
121
+ scope: {
122
+ projectId: process.env.INDIANA_PROJECT_ID!,
123
+ environment: "sandbox",
124
+ },
125
+ });
126
+ const user = await requireSignedInUser(request);
127
+ const { customer } = await longplay.getOrCreateCustomer({
128
+ externalUserId: user.id,
129
+ });
130
+ const session = await longplay.createCustomerSession(customer.id);
131
+ ```
132
+
133
+ Secret Key 仅保存在可信后端。浏览器传入的 Customer ID 不能作为登录证明;将完整 Access/Refresh 凭据对交给对应的已登录用户。App、Sandbox/Live、Customer、权限和允许来源分别校验。SDK 在内存中持有客户凭据;退出或切换身份时销毁旧视图并清理凭据。
134
+
135
+ ```ts
136
+ import { createLongplay } from "@tinkerlandai/longplay-sdk";
137
+
138
+ const longplay = createLongplay({
139
+ baseUrl,
140
+ scope: { projectId, environment: "sandbox" },
141
+ });
142
+ longplay.identify(session);
143
+ const { holdings } = await longplay.readHoldings();
144
+ // 退出账号时:
145
+ longplay.reset();
146
+ ```
147
+
148
+ SDK 通过 Refresh Token 直接向 GOP 轮换凭据;Access 最长 1 小时,Refresh 每次轮换最长 24 小时,原会话最长 30 天。成功轮换保留当前 Customer 身份和待处理业务;刷新被明确拒绝或 Refresh Token 过期时清除凭据,需要重新验证登录。普通业务请求不延期,刷新不会自动重放业务写入。撤销和 Key 到期继续生效。见[Customer 凭据](../../docs/customer-credentials.md)。
149
+
150
+ Customer 使用原始业务字段;资料更新是部分 patch,没有 Customer revision。Guest 登录到已有 Customer 时返回 `switched: true`,资源和历史保留原归属,接入方应为目标 Customer 创建新 Session/Funnel;不合并旧 Guest 的财务事实。
151
+
152
+ ## 可选的稳定 fingerprint
153
+
154
+ `fingerprint` 是宿主提供的稳定去重信号,例如安装标识。首次 Guest/Customer 请求之前初始化并保留它,独立于登录和凭据;Guest 识别、账号切换和会话续期共用同一信号。SDK 不采集或生成它。
155
+
156
+ ```ts
157
+ // 可信后端:installationSignal 由宿主独立保留;
158
+ // verifiedUser.id 来自宿主已验证的登录会话。
159
+ const { customer } = await longplay.getOrCreateCustomer({
160
+ externalUserId: verifiedUser.id,
161
+ fingerprint: installationSignal,
162
+ });
163
+ // Guest 创建使用同一安装信号;创建重试另行保留并复用 guestToken。
164
+ await longplay.createGuestCustomer({
165
+ guestToken: retainedGuestToken,
166
+ fingerprint: installationSignal,
167
+ });
168
+ ```
169
+
170
+ Longplay 将首次提供的信号保存为环境内的哈希,后续值不替换它。可以省略输入,保留按 Customer 去重 Trial 的行为;`unique_fingerprint` 进一步在共享信号的 Customer 之间去重所配置的 Product Trial。业务用户 ID 不是安装标识;Customer/Guest/会话 Token 和请求 ID 各有自己的生命周期,不应充当 fingerprint。
171
+
172
+ Web 宿主可以用独立的第一方安装 cookie 保留信号。清除 cookie 或更换浏览器存储可能产生新值;退出登录无需重置它。原生宿主持久保存随机安装信号,并定义重装、恢复和重置行为。该信号独立于设备证明和身份认证。客户端只收到通用的 Trial 冲突信息;保留 `X-Request-Id` 供服务端排查。
173
+
174
+ ## Funnel、Feature 与独立 Paywall
175
+
176
+ 可信后端通过 `createFunnelStartClient().start()` 提交 `funnelId`、持久保留的 `requestId`、`customer`、`locale` 和 `parameters`。`customer` 为 `{ type: "id", value: customerId }` 或 `{ type: "externalUserId", value: verifiedUser.id }`。后者可以同时提供仅用于新建的 `customerData`,需要 `customers:write` 权限;已有 Customer 不因此更新。服务端响应返回 `customerId`,后端再为它签发 Customer Session。旧的 start-body `customerToken` 和 decision `customerId` 明确拒绝。
177
+
178
+ `createFunnelClient({ customerToken, ...options })` 用于已持有 Customer 凭据的客户端;其 `start()` 不指定 `customer` 或 `customerData`,始终以凭据所属 Customer 运行。响应丢失时保留原编号和输入重试,商业参数放在 `parameters.commerce`。运行有独立的 Run Token,不能据此切换 Customer。
179
+
180
+ `createFunnelDecisionClient` 的 `submit/decide` 与 `/server` 的 `createFunnelCampaignClient` 同样使用明确的 `customer` 引用。Campaign `decide()` 等待返回 `FunnelDecision`;查询可能返回原已接受的决策及其原 request ID。Campaign `start()` 返回 Run 凭据,或在频率/进行中 Run 阻止新启动时返回 `{ suppressed: true, reason, retryAt }`,不伪造 Run 凭据。Run 实际展示后才提交 `shown`,不能用页面关闭或本地存储代替持久的展示事实。
181
+
182
+ 浏览器使用 `createFunnelRunController` 与 `showFunnelRun`。控制器负责当前协议的命令、读取、重试、恢复和订阅;展示句柄提供 `refresh()` 和 `destroy()`。业务操作按当前发布节点委派给服务端,客户端声明或重定向不构成付款证明。
183
+
184
+ Local/Remote 求值策略由每个 Funnel 决定。`createFunnelDecisionClient` 支持当前 `configuration()`、`features()`、提交和结果读取;`createFeatureClient` 以 `generation` 标识配置。Local 下载的规则对客户端可见;敏感决策应使用 Remote 和可信业务事实。
185
+
186
+ ### 独立消费方的分桶身份
187
+
188
+ Funnel、独立 Feature 和页面实验各自求值、各自使用结果。App/环境、实验定义、属性、分桶身份及 Sticky 状态相同时结果应一致;刷新时间或输入不同时允许不同组。Remote Funnel 引用允许 Local 定义投递的代码 Experiment 时,仍由 Longplay 计算。已接受的 Funnel 决定保留原结果,不做跨功能结果同步。
189
+
190
+ Customer 求值中的 `id` 是 Longplay Customer UUID(`customer.id`),`externalUserId` 是商户账号 ID(`customer.externalUserId`)。创建实验时明确选择 `hashAttribute`,每个消费方保持相同语义;不要把商户账号 ID 填进 `attributes.id`。本地属性来自可信后端给当前用户的 Customer 数据;`identify(customerToken)` 负责请求认证,不会自动补齐本地分桶属性。属性仅用于求值,不构成授权。
191
+
192
+ `setAttributes()` 替换整个对象,每次更新都保留分桶字段。匿名页面/视觉实验默认的 `id` 和 `deviceId` 来自共用的第一方设备 cookie,不自动与账号实验同组。各适配器的 Sticky 能力有区别:共用 Run 的 Local 指令读取服务端状态,纯本地工具的持久 Sticky 使用已有宿主适配器,不新增共享状态。完整要求见[架构边界](../../docs/growthbook-sdk-delivery.md#跨消费方求值边界与分桶身份)。
193
+
194
+ ### 服务端 SDK 本地 Funnel 求值
195
+
196
+ 纯 Local Funnel 支持 Entry、Condition、Feature、Split,以及普通值或 Feature 值的 End/Output。复用 client/evaluator,每个可信身份及属性、参数快照创建独立计算会话:
197
+
198
+ ```ts
199
+ const local = createFunnelDecisionClient(options).local();
200
+ const session = local.session({
201
+ attributes: {
202
+ id: customer.id,
203
+ externalUserId: customer.externalUserId,
204
+ country: "US",
205
+ },
206
+ parameters: { placement: "home" },
207
+ });
208
+ const preview = await session.evaluate();
209
+ const used = await session.evaluate({ track: true });
210
+ // used.value 是本地计算结果,没有 Decision/receipt ID。
211
+ ```
212
+
213
+ `local_configuration` 通过现有后端凭据检查下发依赖投影。并发加载共用一次请求,同一 Version/策略 revision/GB generation 复用准备好的规则;缓存到配置过期或 60 秒即停止,以较早者为准。`local.refresh()` 强制更新,失败后不使用旧规则;`local.reset()` 清空配置并使已有会话失效。身份或上下文改变时创建新会话,浏览器执行会被拒绝。
214
+
215
+ 会话在内存中保留 Sticky。持久化通过 `stickyBucketAssignmentDocs` 和 `saveStickyBucketAssignmentDoc` 接入调用方存储,按 App/环境/身份隔离,并由存储适配器保证跨进程 merge/CAS。异步保存完成后才返回结果;失败保留原分配并抛出 `STICKY_SAVE_FAILED`,可重试 `session.flush()` 或求值。仅有内存状态不保证跨设备一致。
216
+
217
+ 默认求值不触发曝光。仅在确实使用结果时传 `{ track: true }`,成功计算并保存 Sticky 后才调用已配置的 `onExperimentViewed`/`onFeatureUsage`,按本会话内的原生分配/值去重。回调接入已有事件 tracker,异常通过 `onError` 报告;事件送达与重试由 tracker 负责,不生成持久的曝光或使用回执。
218
+
219
+ 含商业节点的混合图、资源/Bundle 输出、过期配置明确拒绝,不自动执行远程 `decide()`。远程幂等、已提交回执和财务检查保持原义;本地结果不能代替发券、付款、权益或全局频控。这是需要配套 HTTP 实现的源码能力,本次没有发布 SDK。
220
+
221
+ ### 独立 Feature
222
+
223
+ 后端 Key 显式授予 `features:read` / `events:write`,通过现有 Customer/Guest Session 交给浏览器,不使用新的凭证种类。
224
+
225
+ ```ts
226
+ // 可信后端给当前用户的 Customer 数据。
227
+ const bucketingIdentity = {
228
+ id: customer.id,
229
+ externalUserId: customer.externalUserId,
230
+ };
231
+ const flags = await longplay.features({
232
+ attributes: { ...bucketingIdentity, country: "CA" },
233
+ });
234
+ const enabled = flags.getFeatureValue("new-checkout", false);
235
+ const unsubscribe = flags.subscribe(() =>
236
+ updateView(flags.isOn("new-checkout")),
237
+ );
238
+ flags.setAttributes({ ...bucketingIdentity, country: "US" });
239
+ longplay.track("Checkout Viewed", { source: "pricing" });
240
+ await longplay.flush();
241
+ // 页面卸载时:
242
+ unsubscribe();
243
+ flags.destroy();
244
+ ```
245
+
246
+ 每个 flag 默认本地计算,Owner/Admin 可切换为服务器计算。服务器 flag 只下发键与结果,首次结果到达前使用调用方默认值;属性变化立即丢弃旧结果。名单哈希不等于保密,敏感规则应使用服务器计算。本地 Funnel 不得依赖服务器 flag,切换时也会校验仍在运行的历史版本。`identify` / `reset` 会释放旧身份的功能实例和事件队列。
247
+
248
+ 商品定向使用当前 App/环境的可信商品主档。在 Console 登记属性并维护商品后,传入 `merchandise: { id: "sku-outdoor" }` 引用;SDK 在返回 Feature 客户端之前解析商品。切换商品后,先调用 `setAttributes({ ...bucketingIdentity, merchandise: { id: "sku-indoor" } })`,再等待 `refreshRemote()` 后读取决策。商品实验使用服务端解析的 `merchandiseId`(内部 UUID),不能回退到人的分桶标识。请求中的商品属性不能覆盖主档。
249
+
250
+ 自定义商品事件可调用 `longplay.track("Item Viewed", {}, { merchandise: { id: "sku-indoor" } })`,再 `flush()`。Customer 身份、商品引用和实验分桶单位分别记录;事件不构成订单或收入回执。
251
+
252
+ Offering 固定 Product Version 的范围与层级。Paywall 固定已发布 Offering Version,并独立选择显示的 Product 子集、分组、顺序和默认项。`createPaywallStartClient`、`createPaywallSession`、`showPaywall` 使用独立 Paywall Session,不创建占位 Funnel。付款完成后重新查询权益,业务后端仍须重新鉴权并幂等扣减用量。
253
+
254
+ 仅渲染并将购买选择交给宿主时,向 `showPaywall` 传入 `onPurchaseIntent`。回调接收 `actionId`、选中的
255
+ `productVersionId`、UI `selection` 副本和已校验的 `featureQuantities`;这条路径不创建报价、扣款或发放权益。
256
+ 将已识别客户端的 `session` 传给 `createPaywallSession`,以共用 Access/Refresh 自动轮换。宿主会话接口必须先验证
257
+ 当前原生用户再签发凭据;退出或切换账号时销毁 Paywall 并重置客户端。
258
+
259
+ ## Web 商业能力与当前服务边界
260
+
261
+ 当前服务端实现了 Customer/Guest、Entitlement 检查、持有与账本读取、用量和余额、Coupon/推荐码、Offering/独立 Paywall,以及配置后的 Stripe Sandbox attach、支付方式收集、订阅验证/更新/退款/Trial 和 License 分配路径。每个操作仍受已配置的支付账号、执行模式、Product/Price 条款和当前实现范围约束;接口存在不表示任意组合已验收。
262
+
263
+ Stripe 环境的 `longplay` 模式由 Longplay 执行;`record_only` 返回交给商户后端执行的指令。两种模式都从验证后的 Stripe 事实写入财务与权益。支付方式收集本身不产生扣款、Invoice 或额外权益。报价/确认/读取使用各自持久操作 ID;同一请求编号不能更换输入。
264
+
265
+ 本 Beta **不认证完整商业功能或生产运行**。当前仍未接通的服务包括:
266
+
267
+ - 订阅批处理。冲突查询/重试已有对应服务,但仍需匹配部署及业务前置条件。
268
+ - Postpaid Billing Authorization、Billing Coupon 列表和账单调整确认。当前 AutoTopup consent 的准备/确认/撤销已有服务,不能据此推定其他授权类型可用。
269
+ - `claimRewardProduct` 的完整交付。
270
+ - Apple/Google 真实商店验收。当前独立购买/恢复的协议及部分服务实现不代表商店已验收;`createNativeReceiptClient().recover()` 使用 `subscriptions/native/restore`,不再发送旧 Funnel 原生恢复命令。
271
+
272
+ 已实现商业能力在 Sandbox 与 Live 均沿用原凭据、权限、作用域和真实 Provider 配置检查。未实现操作返回 501;缺少配置或业务前置条件不等于迁移期白名单。
273
+
274
+ ## AI 用量
275
+
276
+ `/server` 提供 AI SDK 用量中间件;`/openrouter` 提供 OpenRouter 包装器。宿主必须提供每次调用的持久幂等键、usage/quote 存储及恢复回调。恢复时重放原报价的扣款,不重新运行模型。密钥始终留在可信后端。
277
+
278
+ ## 发布与验收
279
+
280
+ npm 发布不部署 Web 服务、不应用数据库迁移,也不发布 Swift/Kotlin 包。安装消费、类型、离线单元和构建检查,与真实 Auth/RLS、Stripe、原生商店、浏览器和线上部署分别记录;局部通过不代表整套产品验收。
281
+
282
+ 版本、冻结产物哈希和 registry 回读见 [发布总账](../../docs/sdk-releases.md);当前架构与实现边界见 [开发状态](../../docs/development-status.md)。
283
+
284
+ ## 共享公共配置
285
+
286
+ 源码新增 `createConfigurationSync({ origin, scope })`;`scope` 必须明确包含 `projectId`、`environment` 和 `environmentId`。将同一个实例作为 `configuration` 传给 `createLongplay`、Funnel start/Run controller 和 `createPageExperiments`,共用清单、文件缓存、并发请求和更新通知。`/server` 导出的 `createSynchronizedFunnelLocalEvaluator(configuration, funnelId)` 使用同一份规范 Funnel 文件与 GB 代次进行纯判断,保留仅可信服务端执行的边界。
287
+
288
+ 公共配置由匹配运行端发布。公共文件不需要 Customer 凭据;业务操作沿用原凭据和服务端资格检查。完整调用见[英文说明](README.md#shared-public-configuration)。
289
+
290
+ 初始化总等待不超过 5 秒;活跃消费者共用 12–15 秒轮询,并在回前台/联网后刷新。Local 控制状态自源站验证起最多有效 60 秒,旧 304 或失败不能续期。暂停/过期后停止新本地判断及客户屏幕;冷启动离线时,仅凭持久缓存不能恢复 active 权限。Longplay Run 的命令与观察直接读取服务端状态,独立于 CDN 控制有效期。
291
+
292
+ 内存缓存上限 128 项/32 MiB,浏览器默认 localStorage 的公共文件缓存上限 128 项/2 MiB/7 天。可设置 `storage: null` 或提供 `ConfigurationStorage`;额度不足时退回内存。单文件上限 1 MiB,清单上限 512 KiB,使用前校验作用域、schema 和哈希。共享缓存不存凭据、用户属性、Sticky 或已接受的决策。共享实例由宿主统一 `destroy()`,单个消费者退出不会销毁其他消费者的同步能力。
293
+
294
+ ## 共享 Funnel UI 宿主
295
+
296
+ `showFunnelRun(container, { client: started.controller })` 在 Local / Remote 都接收同一个服务器 Run controller。屏幕、Customer / Product / Coupon 数据、shown 回执、Paywall、外部任务和未知结果重试共用后端路径。宿主销毁只卸载 UI;停止一个 Run 需要显式提交 Close。Console 私有预览仍是无真实业务效果的模拟。
297
+
298
+ 步骤回执和执行上限共用同一 Contract 与服务器记录;恢复使用原 Run、命令及业务回执。Local 不使用本地 checkpoint 或直接 Coupon 调用持有流程业务义务。独立 `claimCoupon`/`grantOffer` 继续由 Coupon 校验资格、额度、权限和有效期。
299
+
300
+ 完整调用和恢复要求见[统一执行说明](README.md#local-funnel-execution)。
301
+
302
+ ## Web IAM 入口与 Funnel 页面实验
303
+
304
+ `createIamFunnelEntries({scope, environmentId, baseUrl, appManifestRelease, session: longplay.session, container})` 安装一次后,通过 `start()` 启动监听,即可在 IAM 页面地图配置现有 Funnel Entry 节点的「进入屏幕」和「按钮/链接点击」入口。配置保存到 Funnel 草稿,随 Funnel 一起发布、暂停。`appManifestRelease` 必须来自当前构建的 IAM release descriptor。原页面点击处理继续执行;仅唯一、可验证的页面和元素能够触发。自动入口每次 pathname 访问尝试一次,查询参数、hash 和重新渲染不制造新访问。
305
+
306
+ 服务端选择 Funnel,验证发布状态,并按原 Customer/Funnel 检查进行中的 Run 和自动入口冷却时间。未知响应通过 `retry()` 复用内存中的原请求;入口宿主不提供刷新页面后的恢复。Run 被 Console 终止或过期后,无论读取到 `closed` 状态还是后续命令被 `FORBIDDEN` 拒绝,入口宿主都会移除界面并上报 `run_ended`,之后的入口可以重新启动。卸载时调用 `destroy()`。IAM 不提供 Customer 凭据或业务权限。
307
+
308
+ Split 开启 `applyPageExperiment` 后,页面直接使用该 Run 已接受的实验变体,不重新分桶。标准入口宿主自动向 `showFunnelRun` 传入 release;手动宿主传入 `appManifestRelease`,或使用 `createFunnelPageExperiments({client, appManifestRelease})`。停止、完成和销毁会清理效果,实际应用沿用原 receipt 的 GB used 上报。同一实验的独立 visual consumer 在 Funnel 持有分组期间让出控制。这些 Web 源码能力需要配套 GOP 合同和数据库迁移,安装 SDK 不等于运行时已发布。
309
+
310
+ ## Funnel 资源上下文
311
+
312
+ 正式 Local Run 由服务端执行原业务动作,自动携带原 Run、node、visit 和 receipt。消费纯 Funnel 结果时,使用 `longplay.forFunnel({ funnelId, versionId })` 获取固定上下文的客户端;其领取、Offering/Paywall 解析和购买报价自动携带这一个 Funnel。服务端以可信事实复算指定流程,不扫描其他 Funnel。
313
+
314
+ 不带上下文的 `longplay.claimCoupon(...)` 仍按 Coupon 自身规则独立领取。未知结果使用原 requestId 和原输入重试;已接受命令在 Funnel 后续暂停时仍返回原结果。
315
+
316
+ ## 独立 Page、Popup 与 Banner
317
+
318
+ 先发布一个没有必填输入、声明 `continue` 动作的 Banner。Customer Token 需要 `content:read` 权限,再显式读取和挂载。Page/Popup 使用相应 `kind`。
319
+
320
+ ```ts
321
+ import {
322
+ createLongplay,
323
+ mountContent,
324
+ type ContentActionIntent,
325
+ } from "@tinkerlandai/longplay-sdk";
326
+
327
+ export async function showBanner(
328
+ container: HTMLElement,
329
+ customerToken: string,
330
+ contentId: string,
331
+ onContinue: (intent: ContentActionIntent) => Promise<void>,
332
+ ) {
333
+ const longplay = createLongplay({
334
+ baseUrl: "https://api.longplayai.com",
335
+ scope: { projectId: "<APP_UUID>", environment: "sandbox" },
336
+ });
337
+ longplay.identify(customerToken);
338
+ const { content } = await longplay.resolveContent({
339
+ kind: "banner",
340
+ selector: { type: "resource", id: contentId },
341
+ });
342
+ const host = await mountContent(container, {
343
+ content,
344
+ actions: { continue: onContinue },
345
+ onError: console.error,
346
+ });
347
+ return {
348
+ retryAction: host.retryAction,
349
+ destroy() {
350
+ host.destroy();
351
+ longplay.reset();
352
+ },
353
+ };
354
+ }
355
+ ```
356
+
357
+ 每个声明动作都需要同名 handler。`onContinue` 用 `intent.requestId` 作为业务后端的幂等键;失败后 `retryAction()` 重用原意图。卸载、退出或换号时调用 `destroy()`。宿主只渲染和发出意图,业务后端仍负责鉴权与执行。
358
+
359
+ 需要加快点击打开时,可提前为当前 Customer 读取内容,再调用根入口的 `prepareContent(container, options)`,参数与 `mountContent` 相同。它提前加载展示模块、校验文档并启动 iframe,等首次渲染后返回句柄;点击时同步调用 `handle.show()`,直接显示同一个 iframe。准备期间不打开弹窗、不抢焦点、不执行动作。句柄用于一次展示,重复 `show()` 不重复挂载;下一次展示需重新准备。
360
+
361
+ 可传入 `signal: abortController.signal`,在卸载、退出或切换 Customer/App/环境时 `abortController.abort()`,连同仍在等待的准备一起取消并释放资源。预渲染不代替业务鉴权,也不保证图片已完成解码。直接持有已授权 ScreenDocument 的低层接入可使用 `/popup` 的 `preparePopup(document, options)`;句柄立即返回,提供 `ready`、`show()` 和 `destroy()`。
362
+
363
+ 客户会话返回 `customerId`、`sessionId`、`accessToken`、`refreshToken`、`expiresAt`、`refreshExpiresAt`。后端只向当前已验证用户返回这组数据,并设置 `Cache-Control: no-store`;客户端调用 `longplay.identify(session)`。SDK 在内存中保管凭据,直接向 GOP 刷新,切换账号或 reset 后不接纳旧刷新响应。撤销由后端调用 `revokeCustomerSessions({customerId, sessionId?})`。独立 Paywall/Funnel 客户端可通过 `session: longplay.session` 共用刷新生命周期。
364
+
365
+ ## 商户主动记账
366
+
367
+ `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 执行渠道业务。
368
+
369
+ ## 商户桥接
370
+
371
+ `createMerchantBridge` 使用版本 3 单入口 POST `/integrations/indiana`,按 App 和环境固定范围,通过原生登录接入 Customer 会话、展示、商业读取和已确认原操作。退出或换号调用 `reset()`。客户端不自动重试财务执行或打开 Checkout。当前协议为 0.10,不兼容旧协议。
372
+
373
+ Server 入口导出 `createMerchantBridgeHandler`、`createMerchantBridgeAuthorityResolver`、`signMerchantBridgeMessage` 和 `verifyMerchantBridgeMessage`。配置后端签名密钥、原生授权/CSRF、实际已实现的能力与 profiles,并设置 `resolveOperationAuthority: createMerchantBridgeAuthorityResolver(server)`,每次从 Longplay 原操作 API 读取最新权限。服务签名仍需原确认事实和准确范围、Customer 归属,不能据此签发用户会话。
374
+
375
+ 业务处理器必须在调用外部渠道前持久预留原 operationId,并使用稳定渠道幂等键;SDK 的先查询不能代替并发锁。`operation.read` 返回 `found` 或 `not_received`,严格只读。`RESULT_UNKNOWN` 只恢复原操作,不能根据浏览器报价或 pending 状态自行推断执行权限。Console 能力检查不证明商业完成。精确字段和恢复规则见[商户接入规范](https://longplayai.com/zh-CN/docs/merchant-execution)。
@@ -0,0 +1,101 @@
1
+ # GrowthBook core
2
+
3
+ MIT core port from growthbook/growthbook at ed29827a453ae6b9eba181bdf4e9280d4b670817. No enterprise-licensed code is included.
4
+
5
+ Copyright (c) 2025 GrowthBook, Inc.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
20
+
21
+ # @noble/hashes 2.4.0
22
+
23
+ SHA-256 for hashed saved-group membership is bundled from `@noble/hashes` 2.4.0.
24
+ Source: https://github.com/paulmillr/noble-hashes
25
+
26
+ The MIT License (MIT)
27
+
28
+ Copyright (c) 2022 Paul Miller (https://paulmillr.com)
29
+
30
+ Permission is hereby granted, free of charge, to any person obtaining a copy
31
+ of this software and associated documentation files (the “Software”), to deal
32
+ in the Software without restriction, including without limitation the rights
33
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
34
+ copies of the Software, and to permit persons to whom the Software is
35
+ furnished to do so, subject to the following conditions:
36
+
37
+ The above copyright notice and this permission notice shall be included in
38
+ all copies or substantial portions of the Software.
39
+
40
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
41
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
42
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
43
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
44
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
45
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
46
+ THE SOFTWARE.
47
+
48
+ # dom-mutator 0.7.0
49
+
50
+ GrowthBook declarative DOM mutations use the original `dom-mutator` package.
51
+ Source: https://github.com/growthbook/dom-mutator
52
+
53
+ MIT License
54
+
55
+ Copyright (c) 2021 Jeremy Dorn
56
+
57
+ Permission is hereby granted, free of charge, to any person obtaining a copy
58
+ of this software and associated documentation files (the "Software"), to deal
59
+ in the Software without restriction, including without limitation the rights
60
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
61
+ copies of the Software, and to permit persons to whom the Software is
62
+ furnished to do so, subject to the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be included in all
65
+ copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
68
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
69
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
70
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
71
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
72
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
73
+ SOFTWARE.
74
+
75
+ # whatwg-url-minimum 0.2.0
76
+
77
+ Bundled URL implementation from https://github.com/expo/whatwg-url-minimum.
78
+
79
+ MIT License
80
+
81
+ Copyright (c) Phil Pluckthun,
82
+ Copyright (c) 650 Industries, Inc. (aka Expo),
83
+ Copyright (c) Sebastian Mayr
84
+
85
+ Permission is hereby granted, free of charge, to any person obtaining a copy
86
+ of this software and associated documentation files (the "Software"), to deal
87
+ in the Software without restriction, including without limitation the rights
88
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
89
+ copies of the Software, and to permit persons to whom the Software is
90
+ furnished to do so, subject to the following conditions:
91
+
92
+ The above copyright notice and this permission notice shall be included in all
93
+ copies or substantial portions of the Software.
94
+
95
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
96
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
97
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
98
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
99
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
100
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
101
+ SOFTWARE.