@tinkerlandai/indiana-sdk 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  import { OpenRouter } from '@openrouter/sdk';
2
2
  import { ChatUsage } from '@openrouter/sdk/models';
3
- import { c as createAiPricing, F as FoundationApi, a as AiUsageContext, b as AiTokenUsage, d as AiTokenQuote } from './ai-usage-CO0lDavO.js';
3
+ import { c as createAiPricing, F as FoundationApi, a as AiUsageContext, b as AiTokenUsage, d as AiTokenQuote } from './ai-usage-D_mBF5Gp.js';
4
4
  import '@ai-sdk/provider';
5
- import './screen-DfuSw6Tz.js';
5
+ import './screen-Keau_Qa2.js';
6
6
  import 'zod';
7
7
 
8
8
  type OpenRouterUsageRecord = {
@@ -1 +1 @@
1
- import{a as i}from"./chunk-EZFAY4ZL.js";import"./chunk-YOH6DIUN.js";import{z as R}from"zod";var c=R.number().int().min(0).max(1e9);function k(e,o){let a=c.parse(e.promptTokens),p=c.parse(e.completionTokens),s=c.parse(e.promptTokensDetails?.cachedTokens??0),t=c.parse(e.promptTokensDetails?.cacheWriteTokens??0),r=c.parse(e.promptTokensDetails?.audioTokens??0),l=c.parse(e.completionTokensDetails?.audioTokens??0),n=c.parse(e.completionTokensDetails?.reasoningTokens??0);if(c.parse(e.totalTokens)!==a+p||s+t+r>a||n+l>p||(e.promptTokensDetails?.videoTokens??0)>0)throw new i("AI_USAGE_UNAVAILABLE","Token pools must conserve usage; video needs an explicit token mapper");return{input:a-s-t-r,output:p-l-(o==="separate"?n:0),cache_read:s,cache_write:t,input_audio:r,output_audio:l,reasoning:o==="separate"?n:0}}function I(e){if(!Number.isFinite(e)||e<0)throw new i("AI_USAGE_UNAVAILABLE","Provider cost must be a nonnegative USD amount");let o=String(e),a=o.includes("e")?e.toFixed(18).replace(/0+$/,"").replace(/\.$/,""):o;if(!/^(0|[1-9]\d{0,11})(\.\d{1,18})?$/.test(a)||Number(a)!==e)throw new i("AI_USAGE_UNAVAILABLE","Provider cost exceeds the supported precision");return a}async function h(e){if(typeof window<"u")throw new i("SERVER_ONLY","OpenRouter accounting requires your backend");if(!["output","separate"].includes(e.reasoning)||!e.modelId||!e.generationId)throw new i("INVALID_REQUEST","Actual model, generation ID and reasoning pricing are required");let o={context:e.context,generationId:e.generationId,modelId:`openrouter/${e.modelId}`,usage:e.usage,tokens:null,quote:null,receipt:null,phase:"captured"};await e.persist(o);let a=e.mapTokens?e.mapTokens(e.usage):k(e.usage,e.reasoning),p=e.usage.cost==null?void 0:I(e.usage.cost),s=e.quote??await e.pricing.quoteTokens({modelId:o.modelId,tokens:a});if(s.pricing.modelId!==o.modelId||Object.keys(a).some(l=>a[l]!==s.tokens[l]))throw new i("INVALID_REQUEST","Saved quote must match the provider model and token pools");await e.persist({...o,tokens:a,quote:s,phase:"quoted"});let t=await e.pricing.trackTokens(e.client,{...e.context,quote:s,providerCostUsd:p,generationId:e.generationId});if(!t.receipt.accepted)throw new i("AI_BALANCE_REJECTED","OpenRouter usage was saved but the balance request was rejected");let r={...o,tokens:a,quote:s,receipt:t,phase:"settled"};return await e.persist(r),r}function T(e){if(typeof window<"u")throw new i("SERVER_ONLY","OpenRouter accounting requires your backend");if(!["output","separate"].includes(e.reasoning))throw new i("INVALID_REQUEST","Reasoning pricing is required");let o=e.openRouter.chat;async function a(t){if(!t.usage||!t.modelId||!t.generationId)throw new i("AI_USAGE_UNAVAILABLE","OpenRouter did not return final usage and the actual model");await h({...e,context:t.context,generationId:t.generationId,modelId:t.modelId.slice(11),usage:t.usage})}let p=async(t,r)=>{let n={context:await e.context(),generationId:null,modelId:null,usage:null,tokens:null,quote:null,receipt:null,phase:"incomplete"},d;try{d=await o.send(t,r)}catch(g){throw await e.persist(n),g}if(!(d instanceof ReadableStream)){if(n={...n,generationId:d.id,modelId:d.model?`openrouter/${d.model}`:null,usage:d.usage??null},!n.usage||!n.modelId)throw await e.persist(n),new i("AI_USAGE_UNAVAILABLE","OpenRouter omitted final accounting");return await a(n),d}let w=d.getReader(),m=!1;return new ReadableStream({async pull(g){try{let{value:u,done:A}=await w.read();if(A){if(m=!0,!n.usage||!n.modelId)throw await e.persist(n),new i("AI_USAGE_UNAVAILABLE","OpenRouter stream ended without final accounting");await a(n),g.close();return}if(u.error)throw new i("AI_PROVIDER_ERROR",u.error.message);u.usage&&(n={...n,generationId:u.id,modelId:u.model?`openrouter/${u.model}`:null,usage:u.usage}),g.enqueue(u)}catch(u){m||(m=!0,await e.persist(n)),await w.cancel(u),g.error(u)}},async cancel(g){m||(m=!0,await e.persist(n)),await w.cancel(g)}})},s=new Proxy(o,{get(t,r){return r==="send"?p:Reflect.get(t,r,t)}});return new Proxy(e.openRouter,{get(t,r){return r==="chat"?s:Reflect.get(t,r,t)}})}export{T as createOpenRouterUsageClient,h as trackOpenRouterUsage};
1
+ import{a as i}from"./chunk-X5UXOLOU.js";import"./chunk-D37O6FAW.js";import"./chunk-MJG7X7LM.js";import{z as R}from"zod";var c=R.number().int().min(0).max(1e9);function k(e,o){let a=c.parse(e.promptTokens),p=c.parse(e.completionTokens),s=c.parse(e.promptTokensDetails?.cachedTokens??0),t=c.parse(e.promptTokensDetails?.cacheWriteTokens??0),r=c.parse(e.promptTokensDetails?.audioTokens??0),l=c.parse(e.completionTokensDetails?.audioTokens??0),n=c.parse(e.completionTokensDetails?.reasoningTokens??0);if(c.parse(e.totalTokens)!==a+p||s+t+r>a||n+l>p||(e.promptTokensDetails?.videoTokens??0)>0)throw new i("AI_USAGE_UNAVAILABLE","Token pools must conserve usage; video needs an explicit token mapper");return{input:a-s-t-r,output:p-l-(o==="separate"?n:0),cache_read:s,cache_write:t,input_audio:r,output_audio:l,reasoning:o==="separate"?n:0}}function I(e){if(!Number.isFinite(e)||e<0)throw new i("AI_USAGE_UNAVAILABLE","Provider cost must be a nonnegative USD amount");let o=String(e),a=o.includes("e")?e.toFixed(18).replace(/0+$/,"").replace(/\.$/,""):o;if(!/^(0|[1-9]\d{0,11})(\.\d{1,18})?$/.test(a)||Number(a)!==e)throw new i("AI_USAGE_UNAVAILABLE","Provider cost exceeds the supported precision");return a}async function h(e){if(typeof window<"u")throw new i("SERVER_ONLY","OpenRouter accounting requires your backend");if(!["output","separate"].includes(e.reasoning)||!e.modelId||!e.generationId)throw new i("INVALID_REQUEST","Actual model, generation ID and reasoning pricing are required");let o={context:e.context,generationId:e.generationId,modelId:`openrouter/${e.modelId}`,usage:e.usage,tokens:null,quote:null,receipt:null,phase:"captured"};await e.persist(o);let a=e.mapTokens?e.mapTokens(e.usage):k(e.usage,e.reasoning),p=e.usage.cost==null?void 0:I(e.usage.cost),s=e.quote??await e.pricing.quoteTokens({modelId:o.modelId,tokens:a});if(s.pricing.modelId!==o.modelId||Object.keys(a).some(l=>a[l]!==s.tokens[l]))throw new i("INVALID_REQUEST","Saved quote must match the provider model and token pools");await e.persist({...o,tokens:a,quote:s,phase:"quoted"});let t=await e.pricing.trackTokens(e.client,{...e.context,quote:s,providerCostUsd:p,generationId:e.generationId});if(!t.receipt.accepted)throw new i("AI_BALANCE_REJECTED","OpenRouter usage was saved but the balance request was rejected");let r={...o,tokens:a,quote:s,receipt:t,phase:"settled"};return await e.persist(r),r}function T(e){if(typeof window<"u")throw new i("SERVER_ONLY","OpenRouter accounting requires your backend");if(!["output","separate"].includes(e.reasoning))throw new i("INVALID_REQUEST","Reasoning pricing is required");let o=e.openRouter.chat;async function a(t){if(!t.usage||!t.modelId||!t.generationId)throw new i("AI_USAGE_UNAVAILABLE","OpenRouter did not return final usage and the actual model");await h({...e,context:t.context,generationId:t.generationId,modelId:t.modelId.slice(11),usage:t.usage})}let p=async(t,r)=>{let n={context:await e.context(),generationId:null,modelId:null,usage:null,tokens:null,quote:null,receipt:null,phase:"incomplete"},d;try{d=await o.send(t,r)}catch(g){throw await e.persist(n),g}if(!(d instanceof ReadableStream)){if(n={...n,generationId:d.id,modelId:d.model?`openrouter/${d.model}`:null,usage:d.usage??null},!n.usage||!n.modelId)throw await e.persist(n),new i("AI_USAGE_UNAVAILABLE","OpenRouter omitted final accounting");return await a(n),d}let w=d.getReader(),m=!1;return new ReadableStream({async pull(g){try{let{value:u,done:A}=await w.read();if(A){if(m=!0,!n.usage||!n.modelId)throw await e.persist(n),new i("AI_USAGE_UNAVAILABLE","OpenRouter stream ended without final accounting");await a(n),g.close();return}if(u.error)throw new i("AI_PROVIDER_ERROR",u.error.message);u.usage&&(n={...n,generationId:u.id,modelId:u.model?`openrouter/${u.model}`:null,usage:u.usage}),g.enqueue(u)}catch(u){m||(m=!0,await e.persist(n)),await w.cancel(u),g.error(u)}},async cancel(g){m||(m=!0,await e.persist(n)),await w.cancel(g)}})},s=new Proxy(o,{get(t,r){return r==="send"?p:Reflect.get(t,r,t)}});return new Proxy(e.openRouter,{get(t,r){return r==="chat"?s:Reflect.get(t,r,t)}})}export{T as createOpenRouterUsageClient,h as trackOpenRouterUsage};
package/dist/page.d.ts ADDED
@@ -0,0 +1,134 @@
1
+ import { J as JsonValue, R as Result } from './growth-rules-D64pEvg9.js';
2
+ import { z } from 'zod';
3
+
4
+ /** GET /api/v1/page-experiments/{environmentId}: page experiments and the saved groups they reference only. */
5
+ declare const pageExperimentPayloadSchema: z.ZodObject<{
6
+ environmentId: z.ZodUUID;
7
+ generation: z.ZodString;
8
+ experiments: z.ZodArray<z.ZodObject<{
9
+ condition: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
10
+ seed: z.ZodOptional<z.ZodString>;
11
+ hashVersion: z.ZodOptional<z.ZodLiteral<2>>;
12
+ ranges: z.ZodOptional<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
13
+ key: z.ZodString;
14
+ name: z.ZodOptional<z.ZodString>;
15
+ force: z.ZodOptional<z.ZodNumber>;
16
+ urlPatterns: z.ZodOptional<z.ZodArray<z.ZodObject<{
17
+ include: z.ZodBoolean;
18
+ type: z.ZodEnum<{
19
+ regex: "regex";
20
+ simple: "simple";
21
+ }>;
22
+ pattern: z.ZodString;
23
+ }, z.core.$strict>>>;
24
+ persistQueryString: z.ZodOptional<z.ZodBoolean>;
25
+ hashAttribute: z.ZodOptional<z.ZodString>;
26
+ phase: z.ZodOptional<z.ZodString>;
27
+ filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
28
+ attribute: z.ZodOptional<z.ZodString>;
29
+ seed: z.ZodString;
30
+ hashVersion: z.ZodLiteral<2>;
31
+ ranges: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
32
+ }, z.core.$strict>>>;
33
+ coverage: z.ZodOptional<z.ZodNumber>;
34
+ meta: z.ZodOptional<z.ZodArray<z.ZodObject<{
35
+ key: z.ZodOptional<z.ZodString>;
36
+ name: z.ZodOptional<z.ZodString>;
37
+ passthrough: z.ZodOptional<z.ZodBoolean>;
38
+ }, z.core.$strict>>>;
39
+ fallbackAttribute: z.ZodOptional<z.ZodString>;
40
+ disableStickyBucketing: z.ZodOptional<z.ZodBoolean>;
41
+ bucketVersion: z.ZodOptional<z.ZodNumber>;
42
+ minBucketVersion: z.ZodOptional<z.ZodNumber>;
43
+ parentConditions: z.ZodOptional<z.ZodArray<z.ZodObject<{
44
+ id: z.ZodString;
45
+ condition: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
46
+ gate: z.ZodOptional<z.ZodBoolean>;
47
+ }, z.core.$strict>>>;
48
+ weights: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
49
+ changeId: z.ZodString;
50
+ variations: z.ZodArray<z.ZodObject<{
51
+ urlRedirect: z.ZodString;
52
+ }, z.core.$strict>>;
53
+ }, z.core.$strict>>;
54
+ savedGroups: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
55
+ type: z.ZodLiteral<"hashed">;
56
+ attributeKey: z.ZodString;
57
+ values: z.ZodArray<z.ZodString>;
58
+ }, z.core.$strict>, z.ZodObject<{
59
+ type: z.ZodLiteral<"list">;
60
+ attributeKey: z.ZodString;
61
+ values: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
62
+ }, z.core.$strict>, z.ZodObject<{
63
+ type: z.ZodLiteral<"condition">;
64
+ condition: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
65
+ }, z.core.$strict>]>>;
66
+ }, z.core.$strict>;
67
+ type PageExperimentPayload = z.infer<typeof pageExperimentPayloadSchema>;
68
+
69
+ /*!
70
+ Portions ported from GrowthBook sdk-js @ ed29827 (GrowthBook.ts auto experiments, util.ts mergeQueryStrings,
71
+ plugins/auto-attributes.ts). Copyright (c) GrowthBook, Inc. MIT License.
72
+ */
73
+
74
+ /**
75
+ * Page experiments for anonymous merchant pages (docs/growthbook-sdk-delivery.md): URL redirect experiments delivered
76
+ * by a public App environment ID. Assignment, URL targeting and query-string overrides use the vendored GrowthBook
77
+ * engine; the exposure is sent before navigating. The environment ID is public and never authenticates anything.
78
+ */
79
+ type PageExperimentsOptions = {
80
+ /** The public App environment ID shown in the console. */
81
+ environmentId: string;
82
+ /** Indiana origin, for example https://tinker.cash. */
83
+ baseUrl: string;
84
+ /** Extra targeting attributes; they win over the automatic ones. */
85
+ attributes?: Record<string, JsonValue>;
86
+ /** Override navigation (default `location.replace`). */
87
+ navigate?: (url: string) => void;
88
+ /** Minimum wait before navigating; default 100 ms with the default navigation, 0 with a custom one. */
89
+ navigateDelay?: number;
90
+ /** Longest wait for the exposure to be sent before navigating anyway; default 1000 ms. */
91
+ maxNavigateDelay?: number;
92
+ /** Skip redirects whose destination is on another host or protocol. */
93
+ disableCrossOriginUrlRedirects?: boolean;
94
+ /** Set false to evaluate without recording exposures. */
95
+ trackExposures?: boolean;
96
+ /** First-party cookie that keeps the anonymous device id (default `indiana_did`). */
97
+ deviceIdCookie?: string;
98
+ fetch?: typeof globalThis.fetch;
99
+ log?: (message: string, details: Record<string, unknown>) => void;
100
+ };
101
+ type Redirect = {
102
+ urlRedirect: string;
103
+ };
104
+ /** sdk-js util mergeQueryStrings: the current page's query parameters the destination does not set itself. */
105
+ declare function mergeQueryStrings(oldUrl: string, newUrl: string): string;
106
+ /** autoAttributesPlugin getAutoAttributes: id, URL parts, page title, browser, device type and UTM parameters. */
107
+ declare function pageAttributes(id: string, location: Location | URL): {
108
+ id: string;
109
+ deviceId: string;
110
+ url: string;
111
+ path: string;
112
+ host: string;
113
+ query: string;
114
+ pageTitle: string;
115
+ browser: string;
116
+ deviceType: string;
117
+ };
118
+ /** Structural check of the page payload (the full schema lives in @indiana/contracts gb-page.ts). */
119
+ declare function readPayload(value: unknown): PageExperimentPayload;
120
+ declare function createPageExperiments(options: PageExperimentsOptions): {
121
+ /** Loads the payload, runs the page experiments and starts following navigation. */
122
+ start(): Promise<void>;
123
+ /** Reloads the payload and re-runs (for example after the console published a change). */
124
+ refresh(): Promise<void>;
125
+ setAttributes(attributes: Record<string, JsonValue>): void;
126
+ /** The latest assignment per experiment key. */
127
+ getResults(): Map<string, Result<Redirect>>;
128
+ readonly generation: string | null;
129
+ /** Stops following navigation and sends pending exposures. */
130
+ destroy(): void;
131
+ };
132
+ type PageExperiments = ReturnType<typeof createPageExperiments>;
133
+
134
+ export { type PageExperiments, type PageExperimentsOptions, createPageExperiments, mergeQueryStrings, pageAttributes, readPayload };
package/dist/page.js ADDED
@@ -0,0 +1,4 @@
1
+ import{c as U,d as S,f as I}from"./chunk-MJG7X7LM.js";/*!
2
+ Portions ported from GrowthBook sdk-js @ ed29827 (GrowthBook.ts auto experiments, util.ts mergeQueryStrings,
3
+ plugins/auto-attributes.ts). Copyright (c) GrowthBook, Inc. MIT License.
4
+ */function D(t,e){let a,n;try{a=new URL(t),n=new URL(e)}catch{return e}return a.searchParams.forEach((o,c)=>{n.searchParams.has(c)||n.searchParams.set(c,o)}),n.toString()}function G(t){let e=`${t}=`;for(let a of document.cookie.split(";")){let n=a.trim();if(n.startsWith(e))return decodeURIComponent(n.slice(e.length))}return""}function V(t){let e=G(t);return/^[A-Za-z0-9_-]{8,128}$/.test(e)||(e=crypto.randomUUID(),document.cookie=`${t}=${encodeURIComponent(e)}; path=/; max-age=${400*24*3600}; SameSite=Lax`),e}function _(){try{let t=sessionStorage.getItem("indiana_sid");return t||(t=crypto.randomUUID(),sessionStorage.setItem("indiana_sid",t)),t}catch{return}}function j(t,e){let a=navigator.userAgent,n=new URLSearchParams(e.search),o={};for(let c of["source","medium","campaign","term","content"]){let u=n.get(`utm_${c}`);u!==null&&(o[`utm${c[0].toUpperCase()}${c.slice(1)}`]=u)}return{id:t,deviceId:t,url:e.href,path:e.pathname,host:e.host,query:e.search,pageTitle:typeof document>"u"?"":document.title,browser:/Edg/.test(a)?"edge":/Chrome/.test(a)?"chrome":/Firefox/.test(a)?"firefox":/Safari/.test(a)?"safari":"unknown",deviceType:/Mobi/.test(a)?"mobile":"desktop",...o}}function J(t){let e=new URL(t),a=["localhost","127.0.0.1","[::1]"].includes(e.hostname);if(e.protocol!=="https:"&&!(e.protocol==="http:"&&a)||e.username||e.password||e.search||e.hash)throw new Error("baseUrl must be an https origin without credentials");return e.origin}function N(t){let e=t;if(!(!!e&&typeof e=="object"&&typeof e.environmentId=="string"&&/^[a-f0-9]{64}$/.test(e.generation??"")&&Array.isArray(e.experiments)&&e.experiments.length<=100&&e.experiments.every(n=>!!n&&!["namespace","status","url","groups"].some(o=>Object.hasOwn(n,o))&&(n.hashVersion===void 0||n.hashVersion===2)&&typeof n.key=="string"&&Array.isArray(n.variations)&&n.variations.length>=2&&n.variations.every(o=>!!o&&typeof o.urlRedirect=="string")&&(n.urlPatterns===void 0||Array.isArray(n.urlPatterns)))&&!!e.savedGroups&&typeof e.savedGroups=="object"&&!Array.isArray(e.savedGroups)))throw new Error("Invalid page experiment payload");return e}function M(t){let e=J(t.baseUrl);if(!/^[0-9a-f-]{36}$/i.test(t.environmentId))throw new Error("environmentId must be the App environment ID");let a=t.fetch??globalThis.fetch.bind(globalThis),n=`${e}/api/v1/page-experiments/${t.environmentId}`,o=V(t.deviceIdCookie??"indiana_did"),c=_(),u=null,R={...t.attributes??{}},d="",x=!1,w=new Map,P=[],p;function L(r){return r.length?a(`${n}/events`,{method:"POST",body:JSON.stringify({events:r}),headers:{"Content-Type":"text/plain"},keepalive:!0,credentials:"omit"}).then(()=>{}).catch(i=>t.log?.("Exposure not sent",{error:i})):Promise.resolve()}function m(){return clearTimeout(p),p=void 0,L(P.splice(0,20))}let g={trackedExperiments:new Set,log:t.log,onExperimentViewed:(r,i)=>{t.trackExposures!==!1&&(P.push({eventUuid:crypto.randomUUID(),timestamp:new Date().toISOString(),deviceId:o,...c?{sessionId:c}:{},url:location.href,pageTitle:document.title.slice(0,512),experimentKey:r.key,variationKey:i.key}),p||(p=setTimeout(()=>{m()},1e3)))}},T=()=>{g.url=location.href,g.attributes={...j(o,location),...R}};function A(r){if(!t.disableCrossOriginUrlRedirects)return!1;try{let i=new URL(location.href);return r.variations.some(s=>{if(!s.urlRedirect)return!1;let v=new URL(s.urlRedirect);return v.protocol!==i.protocol||v.host!==i.host})}catch{return!0}}function C(r,i){d=r;let s=t.navigate??(l=>location.replace(l)),v=t.navigateDelay??(t.navigate?0:100),O=t.maxNavigateDelay??1e3;Promise.all([Promise.race([i,new Promise(l=>setTimeout(l,O))]),new Promise(l=>setTimeout(l,v))]).then(()=>{x||s(r)})}function h(){if(!(!u||x)){T();for(let r of u.experiments){if(A(r)){t.log?.("Auto experiment blocked",{id:r.key});continue}let i;try{i=I(r,g)}catch(s){t.log?.("Skipping page experiment",{id:r.key,error:String(s)});continue}if(w.set(r.key,i),!!i.inExperiment){if(i.value.urlRedirect){let s=r.persistQueryString?D(location.href,i.value.urlRedirect):i.value.urlRedirect;U(s,r.urlPatterns??[])?t.log?.("Skipping redirect because original URL matches redirect URL",{id:r.key}):s!==d&&C(s,m())}break}}}}let E="",f=()=>{location.href!==E&&(E=location.href,d="",h())},y={push:history.pushState,replace:history.replaceState};function $(){history.pushState=function(...r){y.push.apply(this,r),f()},history.replaceState=function(...r){y.replace.apply(this,r),f()},addEventListener("popstate",f),addEventListener("pagehide",b)}let b=()=>{m()};async function k(){let r=await a(n,{cache:"no-cache",credentials:"omit"});if(!r.ok)throw new Error(`Page experiments unavailable (${r.status})`);let i=N(await r.json());if(i.environmentId.toLowerCase()!==t.environmentId.toLowerCase())throw new Error("Page experiment payload scope changed");u=i,g.savedGroups=S(i.savedGroups)}return{async start(){await k(),$(),E=location.href,h()},async refresh(){await k(),d="",h()},setAttributes(r){R={...r},h()},getResults(){return new Map(w)},get generation(){return u?.generation??null},destroy(){x=!0,history.pushState=y.push,history.replaceState=y.replace,removeEventListener("popstate",f),removeEventListener("pagehide",b),m()}}}export{M as createPageExperiments,D as mergeQueryStrings,j as pageAttributes,N as readPayload};
@@ -0,0 +1 @@
1
+ import{a as H}from"./chunk-6X42DWOM.js";import{a as m}from"./chunk-X5UXOLOU.js";import{Ie as U,Vl as B,hf as V,y as M}from"./chunk-D37O6FAW.js";import"./chunk-MJG7X7LM.js";async function F(y,t,d){if(t.status!=="complete")throw new m("INVALID_RESPONSE","A verified completed purchase is required");if(await d.onComplete?.(t),!y||!d.check)return;let i=U.parse(await d.check());if(i.internal_feature_id!==y.internal_feature_id||i.quantity!==y.quantity)throw new m("INVALID_RESPONSE","The access requirement changed");i.allowed&&await d.onAccessGranted?.(i)}async function re(y,t){let d=t.check?U.parse(await t.check()):null;if(d?.allowed)return await t.onAccessGranted?.(d),{check:d,host:null};let i=await t.start(),p=i.view;if(!p)throw new m("INVALID_RESPONSE","Paywall presentation is missing");let{mountScreenPreview:j}=await import("./popup.js"),n=y.ownerDocument,R=new AbortController,v=n.createElement("section"),$=n.createElement("div"),I=n.createElement("div"),P=n.createElement("p"),E=n.createElement("button"),D=n.createElement("button"),f=n.createElement("dialog"),L=n.activeElement,w=p.document.screen.strings.defaultLocale.startsWith("zh");v.dataset.indianaPaywall="",v.setAttribute("aria-label",p.document.name),v.style.cssText="width:100%;box-sizing:border-box;background:#fff;color:#172033;font:14px/1.5 system-ui,sans-serif",I.style.cssText="display:flex;gap:12px;padding:12px 20px;align-items:center;flex-wrap:wrap",P.setAttribute("role","status"),D.textContent=w?"\u5173\u95ED":"Close",E.textContent=w?"\u91CD\u8BD5":"Retry",E.hidden=!0,I.append(P,E,D),v.append($,I);let N=p.document.presentation,g,u=!1,_=!1,l=null,T=!1,a=null,x=null,S=e=>{P.textContent=e instanceof Error?e.message:w?"\u8BF7\u6C42\u5931\u8D25":"Request failed",H(t.onError,e),E.hidden=a===null};async function b(e){if(!(_||u)){_=!0,E.hidden=!0,P.textContent="";try{await e()}catch(o){u||S(o)}finally{_=!1}}}async function W(e){await F(d,e,t)}async function O(e){if(u)return;if(P.textContent=e.status,e.status==="complete"){a=null,await W(e);return}if(e.status!=="pending"){a=null,l=null;return}let o=Date.now()+6e5,c=2e3;for(;!u&&Date.now()<o;){if(await new Promise(h=>{let A=()=>{clearTimeout(C),h()},C=setTimeout(()=>{R.signal.removeEventListener("abort",A),h()},c);R.signal.addEventListener("abort",A,{once:!0})}),u)return;let r=await i.read();if(r.status!=="pending"){await O(r);return}c=Math.min(c*1.5,3e4)}a=async()=>O(await i.read()),E.hidden=!1}async function z(){x?.remove(),x=null,a=z;let e=await i.confirm();if(e.status==="pending"){if(e.checkoutUrl)t.onCheckoutUrl?await t.onCheckoutUrl(e.checkoutUrl):n.defaultView?.location.assign(e.checkoutUrl);else if(e.execution||e.nativePurchase){if(!t.onPurchase)throw new m("PURCHASE_HANDLER_REQUIRED","Provide the merchant or native purchase handler");await t.onPurchase({operation:e,signal:R.signal})}}a=async()=>O(await i.read()),await O(e)}function J(e){x?.remove();let o=n.createElement("div"),c=n.createElement("button"),r=n.createElement("button"),h=n.createElement("p");h.textContent=e.lines.map(s=>s.productName).join(", ")+" \xB7 "+M(e.charge?.totalMinor??e.catalogAmountMinor-(e.discount?.amountMinor??0),e.currency),c.textContent=w?"\u786E\u8BA4\u8D2D\u4E70":"Confirm purchase",r.textContent=w?"\u53D6\u6D88":"Cancel",c.onclick=()=>{b(z)};let A=async()=>{await i.cancel(),!u&&(l=null,a=null,o.remove(),x=null)};r.onclick=()=>{a=A,b(A)};let C=n.createElement("div");for(let s of e.lines){let q=n.createElement("p");if(q.textContent=`${s.productName} \xB7 ${M(s.catalogAmountMinor,e.currency)}${B(s.interval,w?"zh-CN":"en",s.intervalCount??1)}`,C.append(q),s.trial){let G=n.createElement("p");G.textContent=w?`\u8BD5\u7528 ${s.trial.days} \u5929\uFF1B${s.trial.cardRequired?"\u9700\u8981\u63D0\u4F9B\u4ED8\u6B3E\u65B9\u5F0F":"\u65E0\u9700\u63D0\u4F9B\u4ED8\u6B3E\u65B9\u5F0F"}`:`Trial days: ${s.trial.days}; ${s.trial.cardRequired?"a payment method is required":"no payment method is required"}`,C.append(G)}}if(!e.charge){let s=n.createElement("p");s.textContent=w?"\u76EE\u5F55\u4EF7\u683C\uFF1B\u6700\u7EC8\u91D1\u989D\u4EE5\u7ED3\u8D26\u65F6\u7A0E\u8D39\u548C\u8BD5\u7528\u89C4\u5219\u4E3A\u51C6\u3002":"Catalog price; the final charge depends on checkout taxes and trial terms.",C.append(s)}o.append(h,C,c,r),I.prepend(o),x=o,a=null}async function Q(){if(!l)return;if(a=Q,!T){let o=i.view,c=o?.document.buttons[l.actionId],r=o&&c?.type==="checkout"?V(c.product,l.selection,o.capabilities):null;if(!r)throw new m("PRODUCT_UNAVAILABLE","Choose an available Product in this purchase area");let h=await t.resolveOfferId?.({actionId:l.actionId,productVersionId:r});if(u)return;h!==void 0&&(l.offerId=h),T=!0}let e=await i.preview(l);u||J(e)}function k(e="destroy"){u||(u=!0,R.abort(),g?.destroy(),i.destroy(),f.open&&f.close(),f.remove(),v.remove(),L instanceof HTMLElement&&L.isConnected&&L.focus(),e==="dismiss"&&H(t.onDismiss))}D.onclick=()=>k("dismiss"),f.addEventListener("cancel",e=>{e.preventDefault(),k("dismiss")}),E.onclick=()=>{a&&b(a)};try{if(N.type==="inline")y.append(v);else{let e={sm:420,md:640,lg:900};f.style.cssText=`padding:0;border:0;max-width:calc(100vw - 32px);max-height:calc(100dvh - 32px);overflow:auto;width:${"size"in N?e[N.size]+"px":"100vw"}`,f.setAttribute("aria-label",p.document.name),f.append(v),y.append(f),f.showModal()}g=j($,{mode:"live",rendererUrl:i.webViewUrl,title:p.document.name,onAction(e,o={}){let c=i.view,r=c?.document.buttons[e];if(r){if(r.type==="close"){b(async()=>{await t.onAction?.(e),k(t.onAction?"action":"dismiss")});return}if(r.type==="action"){a=async()=>{if(r.action.type==="event"){if(!t.onEvent)throw new m("EVENT_HANDLER_REQUIRED","Provide the event handler");await t.onEvent(r.action.event)}else t.onOpenUrl?await t.onOpenUrl(r.action.url):n.defaultView?.location.assign(r.action.url);await t.onAction?.(e),a=null},b(a);return}if(!c||!V(r.product,o,c.capabilities)){S(new m("PRODUCT_UNAVAILABLE","Choose an available Product in this purchase area"));return}if(l&&l.actionId!==e){S(new m("COMMAND_PENDING","Finish the current purchase"));return}l||(l={requestId:crypto.randomUUID(),actionId:e,selection:o},T=!1),b(Q)}},onEscape:()=>k("dismiss"),onError:e=>S(new Error(e))}),g.update(p.document.screen,p.document.screen.strings.defaultLocale,{capabilities:p.capabilities,serverInput:{bindings:[],answers:{}}}),await g.ready}catch(e){throw k(),e}return{check:d,host:{destroy:k,refresh:async()=>{let e=await i.refresh();return u||g?.updateCapabilities(e.capabilities),e}}}}export{re as showPaywall};
package/dist/popup.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ScreenAction, b as ScreenNode, c as ScreenDocument, d as SurfaceData } from './screen-DfuSw6Tz.js';
1
+ import { a as ScreenAction, b as ScreenNode, c as ScreenDocument, d as SurfaceData } from './screen-Keau_Qa2.js';
2
2
  import { O as Offer, b as OfferUnavailableReason, c as CustomerPageData, a as CustomerDataIntent } from './customer-page-aZb5Twue.js';
3
3
  import { z } from 'zod';
4
4
  import { WindowLike } from 'dompurify';
@@ -61,6 +61,20 @@ type ScreenNodeRect = {
61
61
  width: number;
62
62
  height: number;
63
63
  };
64
+ /** Editor shortcuts typed while focus is inside the canvas; the host owns their meaning. */
65
+ type RendererEditorKey = {
66
+ key: string;
67
+ phase: "down" | "up";
68
+ meta: boolean;
69
+ ctrl: boolean;
70
+ shift: boolean;
71
+ alt: boolean;
72
+ };
73
+ /**
74
+ * Published screen render: the same deterministic bootstrap plus the page's inert JSON document.
75
+ * Only per-mount routing lives in the URL fragment; per-visitor data arrives through the host bridge.
76
+ */
77
+ declare function hostedScreenScript(): string;
64
78
  /** Public, deterministic shell. Only per-mount routing lives in the URL fragment;
65
79
  * customer credentials, designs and business data arrive through the host bridge.
66
80
  * Keep this bootstrap self-contained, like the renderer and Funnel runtime.
@@ -116,8 +130,13 @@ declare function showPopup(input: ScreenDocument, options?: {
116
130
  type ScreenPreviewHandle = {
117
131
  /** Rejects if the renderer cannot start; this does not grant customer authorization. */
118
132
  ready: Promise<void>;
119
- /** 重新渲染(完整文档 + 当前编辑的语言;缺失的文案用默认语言淡显)。 */
120
- update(doc: ScreenDocument, locale: string,
133
+ /** The rendered document: the published render's own document once it is ready, else the last update. */
134
+ readonly document: ScreenDocument | null;
135
+ /**
136
+ * 重新渲染(完整文档 + 当前编辑的语言;缺失的文案用默认语言淡显)。
137
+ * A published screen render passes null: its document arrives with the frame.
138
+ */
139
+ update(doc: ScreenDocument | null, locale: string,
121
140
  /** Current single-screen presentation and server-owned input bindings. */
122
141
  options?: {
123
142
  parameters?: ScreenParameters;
@@ -128,6 +147,8 @@ type ScreenPreviewHandle = {
128
147
  }): void;
129
148
  updateCustomerData(data: CustomerPageData): void;
130
149
  updateCapabilities(data: SurfaceData): void;
150
+ /** Resize the visible viewport without replacing the document or interaction state. */
151
+ resizeViewport(height: number): void;
131
152
  /** 编辑模式下的悬停与选中描边。 */
132
153
  highlight(hoverId: string | null, selectedId: string | null): void;
133
154
  destroy(): void;
@@ -140,6 +161,8 @@ type ScreenPreviewHandle = {
140
161
  declare function mountScreenPreview(container: HTMLElement, options?: {
141
162
  mode?: RendererMode;
142
163
  rendererUrl?: string;
164
+ /** Public cached render of one published screen; its document comes from the frame. */
165
+ screenUrl?: string;
143
166
  title?: string;
144
167
  /** Translate editor controls without changing authored text or the content locale. */
145
168
  editorMessages?: RendererEditorMessages;
@@ -157,6 +180,10 @@ declare function mountScreenPreview(container: HTMLElement, options?: {
157
180
  onResize?: (height: number) => void;
158
181
  /** Editing-only geometry in iframe viewport coordinates; no DOM access across the sandbox. */
159
182
  onLayout?: (nodes: ScreenNodeRect[]) => void;
183
+ /** Editing-only shortcuts typed while focus is inside the canvas. */
184
+ onKey?: (key: RendererEditorKey) => void;
185
+ /** Editing-only pinch or ctrl + wheel zoom; positive deltas zoom out. */
186
+ onZoom?: (delta: number) => void;
160
187
  onError?: (message: string) => void;
161
188
  onCustomerDataIntent?: (intent: CustomerDataIntent) => void;
162
189
  }): ScreenPreviewHandle;
@@ -167,4 +194,4 @@ type CustomerPageHandle = {
167
194
  destroy(): void;
168
195
  };
169
196
 
170
- export { type CustomerPageHandle, type HtmlCodeDiagnostic, type PopupEvent, type PopupHandle, type RendererEditorMessages, ScreenDocument, type ScreenNodeRect, type ScreenPreviewHandle, formatCustomerDecimal, hostedRendererScript, inspectCustomHtml, mountScreenPreview, prepareScreen, sanitizeCustomHtml, showPopup };
197
+ export { type CustomerPageHandle, type HtmlCodeDiagnostic, type PopupEvent, type PopupHandle, type RendererEditorKey, type RendererEditorMessages, ScreenDocument, type ScreenNodeRect, type ScreenPreviewHandle, formatCustomerDecimal, hostedRendererScript, hostedScreenScript, inspectCustomHtml, mountScreenPreview, prepareScreen, sanitizeCustomHtml, showPopup };