@tinkerlandai/longplay-sdk 0.10.14 → 0.10.15-rc.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.10.15 — 2026-11-04
4
+
5
+ - No API or behavior change from 0.10.14. This is the first release published through a release candidate (`0.10.15-rc.1` on npm `next`) before the stable version. The README describes the `next` and `latest` channels. Wire protocol stays `0.10`.
6
+
3
7
  ## 0.10.14 — 2026-11-03
4
8
 
5
9
  - Send bundled native Paywall commands one at a time, so a host refresh while a store purchase is being observed no longer fails the Paywall after the purchase is paid; Swift and Kotlin hosts receive completion instead of `command_pending`. Wire protocol stays `0.10`.
package/README.md CHANGED
@@ -96,7 +96,7 @@ The canonical repository policy, including production/test domain roles, package
96
96
 
97
97
  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.
98
98
 
99
- 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.
99
+ Release candidates (`next`) and stable releases (`latest`) published with this default both 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.
100
100
 
101
101
  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).
102
102
 
@@ -182,7 +182,10 @@ On the merchant backend, create `createFunnelProcessAdapter` from the `/server`
182
182
  Expose the adapter's operation-specific calls through your backend. Keep Guest proofs, Customer sessions and the Server Key there. A shared `guestKey` reuses an eligible Guest across process handles; one `processKey` retains its original start, bind and recovery intents.
183
183
 
184
184
  ```ts
185
- import { createConfigurationSync, createFunnelProcess } from "@tinkerlandai/longplay-sdk";
185
+ import {
186
+ createConfigurationSync,
187
+ createFunnelProcess,
188
+ } from "@tinkerlandai/longplay-sdk";
186
189
 
187
190
  const configuration = createConfigurationSync({
188
191
  origin: baseUrl,
@@ -195,7 +198,10 @@ const process = createFunnelProcess({
195
198
  onAuthenticationRequired: () => showMerchantSignIn(),
196
199
  });
197
200
  const { controller } = await process.open({
198
- operation: "start", funnelId, locale: "en", parameters: {},
201
+ operation: "start",
202
+ funnelId,
203
+ locale: "en",
204
+ parameters: {},
199
205
  });
200
206
  // After verified merchant login, retain this instance and controller.
201
207
  await process.syncIdentity();
@@ -1,4 +1,4 @@
1
- import{a as $,b as N,c as J,d as q}from"./chunk-J42MHRU7.js";import{a as _}from"./chunk-DSPLGTQI.js";import{a as P}from"./chunk-2ODPDLTX.js";import{bb as D}from"./chunk-KOGWARWA.js";import{s as C,v as O,x as j}from"./chunk-VSJOHWDL.js";async function B(e,t,o,n={}){let r=n.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(r);let u;try{u=await P(e,t,{signal:o,error:()=>Error(r)})}catch(p){throw o.aborted?Error(r):p}return JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(u))}async function G(e,t){if(!e?.body)return;let o=new AbortController,n=()=>o.abort(t?.reason);t?.addEventListener("abort",n,{once:!0}),t?.aborted&&n();let r=setTimeout(()=>o.abort(Error("Exposure response timeout")),2e3);try{return await P(e,16384,{signal:o.signal,error:()=>Error("Exposure response budget")})}finally{clearTimeout(r),t?.removeEventListener("abort",n)}}var be=2*1024*1024,V=256*1024;async function Y(e,t,o,n){let r=()=>{},s=new Promise((u,p)=>{r=()=>p(n.reason),n.addEventListener("abort",r,{once:!0}),n.aborted&&r()});try{let u=e(t,{...o,signal:n});return u.then(p=>{n.aborted&&p.body?.cancel().catch(()=>{})},()=>{}),await Promise.race([u,s])}finally{n.removeEventListener("abort",r)}}async function M(e,t,o,n=!1){if(new TextEncoder().encode(o).byteLength>65536)throw Error("Exposure request budget");let r=new URL(t);r.searchParams.set("sdkVersion",D.version);let s=new AbortController,u=setTimeout(()=>s.abort(Error("Exposure request timeout")),5e3);try{let p=await Y(e,r.href,{method:"POST",headers:{"Content-Type":"text/plain"},body:o,credentials:"omit",keepalive:n,redirect:"error"},s.signal),a=await G(p,s.signal),c;try{c=a&&JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(a))}catch{}return{response:p,receipt:c}}finally{clearTimeout(u)}}function H(e){let t,o=!1;return{async read(n,r){if(o)throw Error("Experiment client stopped");t?.abort(Error("Experiment request superseded"));let s=new AbortController;t=s;let{signal:u}=s,p=setTimeout(()=>s.abort(Error("Experiment request timeout")),5e3);try{let a=await Y(e,n,{cache:"no-cache",credentials:"omit",redirect:"error"},u),c=await B(a,r,u);return u.throwIfAborted(),c}finally{clearTimeout(p),t===s&&(t=void 0)}},destroy(){o=!0,t?.abort(Error("Experiment client stopped")),t=void 0}}}var Q=20,ie=65536,se=5e3,ae=5,K=e=>new TextEncoder().encode(JSON.stringify(e)).length;function ce(e,t){let o=e,n=r=>Number.isSafeInteger(r)&&r>=0;return!!o&&typeof o=="object"&&n(o.inserted)&&n(o.duplicates)&&n(o.skipped)&&Array.isArray(o.rejected)&&o.inserted+o.duplicates+o.skipped+o.rejected.length===t&&o.rejected.every(r=>!!r&&typeof r=="object"&&n(r.index)&&r.index<t&&typeof r.reason=="string")&&new Set(o.rejected.map(r=>r.index)).size===o.rejected.length}function F(e){let t=[],o,n,r=(a,c)=>{try{e.log?.(`${e.label} ${a}`,c)}catch{}};function s(a){o===void 0&&!n&&t.length&&(o=setTimeout(()=>{p()},a))}async function u(a,c){let g,f;try{({response:g,receipt:f}=await M(e.fetch,e.endpoint,JSON.stringify({events:a}),c))}catch(h){return r("not sent",{error:h}),!1}return g.status===408||g.status===429||g.status>=500?(r("not sent",{status:g.status}),!1):g.ok?ce(f,a.length)?(f.rejected.length&&r("rejected",{rejected:f.rejected}),!0):(r("not acknowledged",{status:g.status}),!1):(r("rejected",{status:g.status,events:a.length}),!0)}function p(a=!0){if(n)return n;clearTimeout(o),o=void 0;let c=[],g=K({events:[]});for(;t.length&&c.length<Q;){let m=K(t[0].event)+(c.length?1:0);if(c.length&&g+m>ie)break;c.push(t.shift()),g+=m}if(!c.length)return Promise.resolve();let f=1e3,h=u(c.map(m=>m.event),a).then(m=>{if(m)return;f=se;let y=c.filter(b=>++b.attempts<ae);y.length<c.length&&r("dropped after retries",{events:c.length-y.length}),t.unshift(...y)}).finally(()=>{n===h&&(n=void 0),s(f)});return n=h,h}return{push(a){t.push({event:structuredClone(a),attempts:0}),t.length>=Q?p():s(1e3)},flush:p}}/*!
1
+ import{a as $,b as N,c as J,d as q}from"./chunk-YVEIUICI.js";import{a as _}from"./chunk-DSPLGTQI.js";import{a as P}from"./chunk-RFVSNSDQ.js";import{bb as D}from"./chunk-B7Y6SQ2N.js";import{s as C,v as O,x as j}from"./chunk-VSJOHWDL.js";async function B(e,t,o,n={}){let r=n.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(r);let u;try{u=await P(e,t,{signal:o,error:()=>Error(r)})}catch(p){throw o.aborted?Error(r):p}return JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(u))}async function G(e,t){if(!e?.body)return;let o=new AbortController,n=()=>o.abort(t?.reason);t?.addEventListener("abort",n,{once:!0}),t?.aborted&&n();let r=setTimeout(()=>o.abort(Error("Exposure response timeout")),2e3);try{return await P(e,16384,{signal:o.signal,error:()=>Error("Exposure response budget")})}finally{clearTimeout(r),t?.removeEventListener("abort",n)}}var be=2*1024*1024,V=256*1024;async function Y(e,t,o,n){let r=()=>{},s=new Promise((u,p)=>{r=()=>p(n.reason),n.addEventListener("abort",r,{once:!0}),n.aborted&&r()});try{let u=e(t,{...o,signal:n});return u.then(p=>{n.aborted&&p.body?.cancel().catch(()=>{})},()=>{}),await Promise.race([u,s])}finally{n.removeEventListener("abort",r)}}async function M(e,t,o,n=!1){if(new TextEncoder().encode(o).byteLength>65536)throw Error("Exposure request budget");let r=new URL(t);r.searchParams.set("sdkVersion",D.version);let s=new AbortController,u=setTimeout(()=>s.abort(Error("Exposure request timeout")),5e3);try{let p=await Y(e,r.href,{method:"POST",headers:{"Content-Type":"text/plain"},body:o,credentials:"omit",keepalive:n,redirect:"error"},s.signal),a=await G(p,s.signal),c;try{c=a&&JSON.parse(new TextDecoder("utf-8",{fatal:!0}).decode(a))}catch{}return{response:p,receipt:c}}finally{clearTimeout(u)}}function H(e){let t,o=!1;return{async read(n,r){if(o)throw Error("Experiment client stopped");t?.abort(Error("Experiment request superseded"));let s=new AbortController;t=s;let{signal:u}=s,p=setTimeout(()=>s.abort(Error("Experiment request timeout")),5e3);try{let a=await Y(e,n,{cache:"no-cache",credentials:"omit",redirect:"error"},u),c=await B(a,r,u);return u.throwIfAborted(),c}finally{clearTimeout(p),t===s&&(t=void 0)}},destroy(){o=!0,t?.abort(Error("Experiment client stopped")),t=void 0}}}var Q=20,ie=65536,se=5e3,ae=5,K=e=>new TextEncoder().encode(JSON.stringify(e)).length;function ce(e,t){let o=e,n=r=>Number.isSafeInteger(r)&&r>=0;return!!o&&typeof o=="object"&&n(o.inserted)&&n(o.duplicates)&&n(o.skipped)&&Array.isArray(o.rejected)&&o.inserted+o.duplicates+o.skipped+o.rejected.length===t&&o.rejected.every(r=>!!r&&typeof r=="object"&&n(r.index)&&r.index<t&&typeof r.reason=="string")&&new Set(o.rejected.map(r=>r.index)).size===o.rejected.length}function F(e){let t=[],o,n,r=(a,c)=>{try{e.log?.(`${e.label} ${a}`,c)}catch{}};function s(a){o===void 0&&!n&&t.length&&(o=setTimeout(()=>{p()},a))}async function u(a,c){let g,f;try{({response:g,receipt:f}=await M(e.fetch,e.endpoint,JSON.stringify({events:a}),c))}catch(h){return r("not sent",{error:h}),!1}return g.status===408||g.status===429||g.status>=500?(r("not sent",{status:g.status}),!1):g.ok?ce(f,a.length)?(f.rejected.length&&r("rejected",{rejected:f.rejected}),!0):(r("not acknowledged",{status:g.status}),!1):(r("rejected",{status:g.status,events:a.length}),!0)}function p(a=!0){if(n)return n;clearTimeout(o),o=void 0;let c=[],g=K({events:[]});for(;t.length&&c.length<Q;){let m=K(t[0].event)+(c.length?1:0);if(c.length&&g+m>ie)break;c.push(t.shift()),g+=m}if(!c.length)return Promise.resolve();let f=1e3,h=u(c.map(m=>m.event),a).then(m=>{if(m)return;f=se;let y=c.filter(b=>++b.attempts<ae);y.length<c.length&&r("dropped after retries",{events:c.length-y.length}),t.unshift(...y)}).finally(()=>{n===h&&(n=void 0),s(f)});return n=h,h}return{push(a){t.push({event:structuredClone(a),attempts:0}),t.length>=Q?p():s(1e3)},flush:p}}/*!
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 ue(e,t){let o,n;try{o=new URL(e),n=new URL(t)}catch{return t}return o.searchParams.forEach((r,s)=>{n.searchParams.has(s)||n.searchParams.set(s,r)}),n.toString()}function de(e,t){let o=navigator.userAgent,n=new URLSearchParams(t.search),r={};for(let s of["source","medium","campaign","term","content"]){let u=n.get(`utm_${s}`);u!==null&&(r[`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(o)?"edge":/Chrome/.test(o)?"chrome":/Firefox/.test(o)?"firefox":/Safari/.test(o)?"safari":"unknown",deviceType:/Mobi/.test(o)?"mobile":"desktop",...r}}function le(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(n=>!!n&&!["namespace","status","url","groups"].some(r=>Object.hasOwn(n,r))&&(n.hashVersion===void 0||n.hashVersion===2)&&typeof n.key=="string"&&Array.isArray(n.variations)&&n.variations.length>=2&&n.variations.every(r=>!!r&&typeof r.urlRedirect=="string")&&(n.urlPatterns===void 0||Array.isArray(n.urlPatterns)))&&!!t.savedGroups&&typeof t.savedGroups=="object"&&!Array.isArray(t.savedGroups)))throw new Error("Invalid page experiment payload");return t}function Te(e){if(e.configuration&&e.configuration.scope.environmentId!==e.environmentId)throw new Error("Page configuration belongs to another App environment");let t=J(e.baseUrl);if(!/^[0-9a-f-]{36}$/i.test(e.environmentId))throw new Error("environmentId must be the App environment ID");let o=e.fetch??globalThis.fetch.bind(globalThis),n=H(async(i,d)=>{let l=await o(i,d);if(!l.ok)throw l.body?.cancel().catch(()=>{}),new Error(`Page experiments unavailable (${l.status})`);return l}),r=e.log?(i,d)=>_(e.log,i,d):void 0,s=`${t}/api/v1/page-experiments/${e.environmentId}`,u=$(e.deviceIdCookie??"indiana_did"),p=N(),a=null,c={...e.attributes??{}},g="",f=!1,h=!1,m=0,y=0,b=new Map,S=F({endpoint:`${s}/events`,fetch:o,log:r,label:"Exposure"}),k=S.flush,E={trackedExperiments:new Set,log:r,onExperimentViewed:(i,d)=>{e.trackExposures===!1||a?.trackExposures===!1||S.push({eventUuid:crypto.randomUUID(),timestamp:new Date().toISOString(),deviceId:u,...p?{sessionId:p}:{},url:location.href,pageTitle:document.title.slice(0,512),experimentKey:i.key,variationKey:d.key})}},z=()=>{E.url=location.href,E.attributes={...de(u,location),...c}};function W(i){if(!e.disableCrossOriginUrlRedirects)return!1;try{let d=new URL(location.href);return i.variations.some(l=>{if(!l.urlRedirect)return!1;let v=new URL(l.urlRedirect);return v.protocol!==d.protocol||v.host!==d.host})}catch{return!0}}function X(i,d){let l=e.configuration?.snapshot?.generation,v=y,te=location.href;g=i;let re=e.navigate??(w=>location.replace(w)),ne=e.navigateDelay??(e.navigate?0:100),oe=e.maxNavigateDelay??1e3;Promise.all([Promise.race([d,new Promise(w=>setTimeout(w,oe))]),new Promise(w=>setTimeout(w,ne))]).then(()=>{!f&&v===y&&location.href===te&&(!e.configuration||e.configuration.fresh&&e.configuration.snapshot?.generation===l)&&re(i)})}function x(){if(!(!a||f||e.configuration&&!e.configuration.fresh)){z();for(let i of a.experiments){if(W(i)){r?.("Auto experiment blocked",{id:i.key});continue}let d;try{d=j(i,E)}catch(l){r?.("Skipping page experiment",{id:i.key,error:String(l)});continue}if(b.set(i.key,d),!!d.inExperiment){if(d.value.urlRedirect){let l=i.persistQueryString?ue(location.href,d.value.urlRedirect):d.value.urlRedirect;C(l,i.urlPatterns??[])?r?.("Skipping redirect because original URL matches redirect URL",{id:i.key}):l!==g&&X(l,k())}break}}}}let R="",Z=()=>{location.href!==R&&(y++,R=location.href,g="",x())},T,L;function ee(){T??=q(Z),addEventListener("pagehide",I)}let I=()=>{k()};async function A(){if(f)return!1;let i=++m,d;try{d=e.configuration?await e.configuration.pageExperiments():await n.read(s,V)}catch(v){if(f||i!==m)return!1;throw v}if(f||i!==m)return!1;let l=le(d);if(l.environmentId.toLowerCase()!==e.environmentId.toLowerCase())throw new Error("Page experiment payload scope changed");return a?.trackExposures===!1&&l.trackExposures&&E.trackedExperiments?.clear(),a=l,E.savedGroups=O(l.savedGroups),!0}function U(){f||(L??=e.configuration?.subscribe(()=>{!f&&e.configuration?.fresh&&A().then(i=>{i&&x()}).catch(i=>r?.("Page configuration unavailable",{error:String(i)}))}),ee(),R=location.href)}return{async start(){h||f||(h=!0,await A()&&(U(),x()))},async refresh(){await A()&&(y++,h&&U(),g="",x())},setAttributes(i){y++,g="",c={...i},x()},getResults(){return new Map(b)},get generation(){return a?.generation??null},destroy(){f||(f=!0,m++,n.destroy(),L?.(),T?.(),removeEventListener("pagehide",I),a=null,b.clear(),k())}}}export{be as a,H as b,F as c,ue as d,de as e,le as f,Te as g};