@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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { OpenRouter } from '@openrouter/sdk';
|
|
2
|
+
import { ChatUsage } from '@openrouter/sdk/models';
|
|
3
|
+
import { F as FoundationApi } from './api-5gAz4-Sb.js';
|
|
4
|
+
import { c as createAiPricing, A as AiUsageContext, a as AiTokenUsage, b as AiTokenQuote } from './ai-usage-Btwg1nD7.js';
|
|
5
|
+
import './screen-D81zCpfi.js';
|
|
6
|
+
import 'zod';
|
|
7
|
+
import 'zod/v4';
|
|
8
|
+
import '@ai-sdk/provider';
|
|
9
|
+
|
|
10
|
+
type OpenRouterUsageRecord = {
|
|
11
|
+
context: AiUsageContext;
|
|
12
|
+
generationId: string | null;
|
|
13
|
+
modelId: string | null;
|
|
14
|
+
usage: ChatUsage | null;
|
|
15
|
+
tokens: AiTokenUsage | null;
|
|
16
|
+
quote: AiTokenQuote | null;
|
|
17
|
+
receipt: Awaited<ReturnType<FoundationApi["mutateBalance"]>> | null;
|
|
18
|
+
phase: "captured" | "quoted" | "settled" | "incomplete";
|
|
19
|
+
};
|
|
20
|
+
type OpenRouterUsageOptions = {
|
|
21
|
+
openRouter: OpenRouter;
|
|
22
|
+
pricing: ReturnType<typeof createAiPricing>;
|
|
23
|
+
client: Pick<FoundationApi, "mutateBalance">;
|
|
24
|
+
context: () => AiUsageContext | Promise<AiUsageContext>;
|
|
25
|
+
persist: (record: OpenRouterUsageRecord) => Promise<void>;
|
|
26
|
+
reasoning: "output" | "separate";
|
|
27
|
+
mapTokens?: (usage: ChatUsage) => AiTokenUsage;
|
|
28
|
+
};
|
|
29
|
+
type OpenRouterTrackOptions = Pick<OpenRouterUsageOptions, "pricing" | "client" | "persist" | "reasoning" | "mapTokens"> & {
|
|
30
|
+
context: AiUsageContext;
|
|
31
|
+
generationId: string;
|
|
32
|
+
/** Actual provider response model, without the openrouter/ provider prefix. */
|
|
33
|
+
modelId: string;
|
|
34
|
+
usage: ChatUsage;
|
|
35
|
+
/** Use the saved quoted record for a retry; never reprice an uncertain settlement. */
|
|
36
|
+
quote?: AiTokenQuote;
|
|
37
|
+
};
|
|
38
|
+
/** Settle a saved current OpenRouter usage response without calling the model again. */
|
|
39
|
+
declare function trackOpenRouterUsage(options: OpenRouterTrackOptions): Promise<OpenRouterUsageRecord>;
|
|
40
|
+
/** Wrap the current OpenRouter chat.send API. Persist raw usage before pricing or settlement. */
|
|
41
|
+
declare function createOpenRouterUsageClient(options: OpenRouterUsageOptions): OpenRouter;
|
|
42
|
+
|
|
43
|
+
export { type OpenRouterTrackOptions, type OpenRouterUsageOptions, type OpenRouterUsageRecord, createOpenRouterUsageClient, trackOpenRouterUsage };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-YQHPEP4X.js";import"./chunk-2HFA34QO.js";import{qa as i}from"./chunk-ACF2FBUB.js";import"./chunk-G56XGVMS.js";import"./chunk-MRN4HP4W.js";import"./chunk-7DXL3LHT.js";import"./chunk-XSVQHPQT.js";import{z as k}from"zod";var c=k.number().int().min(0).max(1e9);function R(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 f(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):R(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 f({...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,f as trackOpenRouterUsage};
|
package/dist/page.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { a as ConfigurationSync } from './configuration-sync-CywDxRkX.js';
|
|
2
|
+
import { J as JsonValue, R as Result } from './growth-rules-B4YYy64R.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import './screen-D81zCpfi.js';
|
|
5
|
+
|
|
6
|
+
/** GET /api/v1/page-experiments/{environmentId}: page experiments and the saved groups they reference only. */
|
|
7
|
+
declare const pageExperimentPayloadSchema: z.ZodObject<{
|
|
8
|
+
environmentId: z.ZodUUID;
|
|
9
|
+
trackExposures: z.ZodBoolean;
|
|
10
|
+
generation: z.ZodString;
|
|
11
|
+
experiments: z.ZodArray<z.ZodObject<{
|
|
12
|
+
name: z.ZodOptional<z.ZodString>;
|
|
13
|
+
phase: z.ZodOptional<z.ZodString>;
|
|
14
|
+
key: z.ZodString;
|
|
15
|
+
filters: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
16
|
+
attribute: z.ZodOptional<z.ZodString>;
|
|
17
|
+
seed: z.ZodString;
|
|
18
|
+
hashVersion: z.ZodLiteral<2>;
|
|
19
|
+
ranges: z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>;
|
|
20
|
+
}, z.core.$strict>>>;
|
|
21
|
+
condition: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodJSONSchema>>;
|
|
22
|
+
force: z.ZodOptional<z.ZodNumber>;
|
|
23
|
+
hashAttribute: z.ZodOptional<z.ZodString>;
|
|
24
|
+
seed: z.ZodOptional<z.ZodString>;
|
|
25
|
+
hashVersion: z.ZodOptional<z.ZodLiteral<2>>;
|
|
26
|
+
ranges: z.ZodOptional<z.ZodArray<z.ZodTuple<[z.ZodNumber, z.ZodNumber], null>>>;
|
|
27
|
+
urlPatterns: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
28
|
+
include: z.ZodBoolean;
|
|
29
|
+
type: z.ZodEnum<{
|
|
30
|
+
regex: "regex";
|
|
31
|
+
simple: "simple";
|
|
32
|
+
}>;
|
|
33
|
+
pattern: z.ZodString;
|
|
34
|
+
}, z.core.$strict>>>;
|
|
35
|
+
persistQueryString: z.ZodOptional<z.ZodBoolean>;
|
|
36
|
+
parentConditions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
37
|
+
id: z.ZodString;
|
|
38
|
+
condition: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
|
|
39
|
+
gate: z.ZodOptional<z.ZodBoolean>;
|
|
40
|
+
}, z.core.$strict>>>;
|
|
41
|
+
weights: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
|
|
42
|
+
coverage: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
fallbackAttribute: z.ZodOptional<z.ZodString>;
|
|
44
|
+
disableStickyBucketing: z.ZodOptional<z.ZodBoolean>;
|
|
45
|
+
bucketVersion: z.ZodOptional<z.ZodNumber>;
|
|
46
|
+
minBucketVersion: z.ZodOptional<z.ZodNumber>;
|
|
47
|
+
meta: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
48
|
+
key: z.ZodOptional<z.ZodString>;
|
|
49
|
+
name: z.ZodOptional<z.ZodString>;
|
|
50
|
+
passthrough: z.ZodOptional<z.ZodBoolean>;
|
|
51
|
+
}, z.core.$strict>>>;
|
|
52
|
+
changeId: z.ZodString;
|
|
53
|
+
variations: z.ZodArray<z.ZodObject<{
|
|
54
|
+
urlRedirect: z.ZodString;
|
|
55
|
+
}, z.core.$strict>>;
|
|
56
|
+
}, z.core.$strict>>;
|
|
57
|
+
savedGroups: z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodObject<{
|
|
58
|
+
type: z.ZodLiteral<"hashed">;
|
|
59
|
+
attributeKey: z.ZodString;
|
|
60
|
+
values: z.ZodArray<z.ZodString>;
|
|
61
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
62
|
+
type: z.ZodLiteral<"list">;
|
|
63
|
+
attributeKey: z.ZodString;
|
|
64
|
+
values: z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>;
|
|
65
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
66
|
+
type: z.ZodLiteral<"condition">;
|
|
67
|
+
condition: z.ZodRecord<z.ZodString, z.ZodJSONSchema>;
|
|
68
|
+
}, z.core.$strict>]>>;
|
|
69
|
+
}, z.core.$strict>;
|
|
70
|
+
type PageExperimentPayload = z.infer<typeof pageExperimentPayloadSchema>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Page experiments for anonymous merchant pages (docs/growthbook-sdk-delivery.md): URL redirect experiments delivered
|
|
74
|
+
* by a public App environment ID. Assignment, URL targeting and query-string overrides use the vendored GrowthBook
|
|
75
|
+
* engine; the exposure is sent before navigating and kept until acknowledged. The environment ID is public and never
|
|
76
|
+
* authenticates anything.
|
|
77
|
+
*/
|
|
78
|
+
type PageExperimentsOptions = {
|
|
79
|
+
configuration?: ConfigurationSync;
|
|
80
|
+
/** The public App environment ID shown in the console. */
|
|
81
|
+
environmentId: string;
|
|
82
|
+
/** Indiana API origin; defaults to https://api.longplayai.com. */
|
|
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 @@
|
|
|
1
|
+
import{d as o,e as r,f as e,g as f}from"./chunk-VMM3GA3L.js";import"./chunk-HOW7NIIR.js";import"./chunk-DSPLGTQI.js";import"./chunk-MRN4HP4W.js";import"./chunk-XSVQHPQT.js";export{f as createPageExperiments,o as mergeQueryStrings,r as pageAttributes,e as readPayload};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as Z}from"./chunk-SSLZKIIU.js";import{M as B}from"./chunk-2HFA34QO.js";import{L as V,i as G,ma as W,oa as X,qa as w}from"./chunk-ACF2FBUB.js";import{a as j}from"./chunk-DSPLGTQI.js";import"./chunk-G56XGVMS.js";import"./chunk-MRN4HP4W.js";import"./chunk-7DXL3LHT.js";import"./chunk-XSVQHPQT.js";async function ee(b,n,y){if(n.status!=="complete")throw new w("INVALID_RESPONSE","A verified completed purchase is required");if(await y.onComplete?.(n),!b||!y.check)return;let R=V.parse(await y.check());if(R.internal_feature_id!==b.internal_feature_id||R.quantity!==b.quantity)throw new w("INVALID_RESPONSE","The access requirement changed");R.allowed&&await y.onAccessGranted?.(R)}async function fe(b,n){let y=n.check?V.parse(await n.check()):null;if(y?.allowed)return await n.onAccessGranted?.(y),{check:y,host:null};let R=Z(b.ownerDocument),r=await n.start(),E=r.view??await r.refresh();if(!E)throw new w("INVALID_RESPONSE","Paywall presentation is missing");let{mountScreenPreview:te}=await import("./screen-renderer-JMHV4GPP.js"),c=b.ownerDocument,_=new AbortController,C=c.createElement("section"),K=c.createElement("div"),D=c.createElement("div"),g=c.createElement("p"),u=c.createElement("button"),M=c.createElement("button"),d=c.createElement("dialog"),i=E.document.screen.strings.defaultLocale.startsWith("zh");C.dataset.indianaPaywall="",C.setAttribute("aria-label",E.document.name),C.style.cssText="width:100%;box-sizing:border-box;background:#fff;color:#172033;font:14px/1.5 system-ui,sans-serif",D.style.cssText="display:flex;gap:12px;padding:12px 20px;align-items:center;flex-wrap:wrap",g.setAttribute("role","status"),M.textContent=i?"\u5173\u95ED":"Close",u.textContent=i?"\u91CD\u8BD5":"Retry",u.hidden=!0,D.append(g,u,M),C.append(K,D);let L=E.document.presentation,O,p=!1,$=!1,f=null,Y=!1,m=()=>!Y&&n.canInteract?.()!==!1,q=!1,a=null,U=null,Q=new Map;async function H(e){if(!(!m()||!e.checkoutUrl||Q.get(e.operationId)===e.checkoutUrl)){Q.set(e.operationId,e.checkoutUrl);try{n.onCheckoutUrl?await n.onCheckoutUrl(e.checkoutUrl):c.defaultView?.location.assign(e.checkoutUrl)}catch(o){throw Q.delete(e.operationId),o}}}let N=e=>{g.textContent=e instanceof Error?e.message:i?"\u8BF7\u6C42\u5931\u8D25":"Request failed",j(n.onError,e),u.hidden=a===null};async function k(e){if(!($||p||!m())){$=!0,u.hidden=!0,g.textContent="";try{await e()}catch(o){p||N(o)}finally{$=!1}}}async function ne(e){await ee(y,e,n)}async function v(e){if(p)return;if(!m()){a=async()=>v(await r.read()),u.hidden=!1;return}if(g.textContent=e.status,e.problem){let t=e.problem;a=T,u.textContent=i?"\u68C0\u67E5\u5E76\u6062\u590D\u539F\u64CD\u4F5C":"Check and recover original purchase",N(new w(t.code,t.reason==="RESULT_UNKNOWN"?i?"\u4ED8\u6B3E\u7ED3\u679C\u5C1A\u672A\u786E\u5B9A\uFF0C\u8BF7\u6062\u590D\u539F\u64CD\u4F5C\uFF0C\u6682\u52FF\u91CD\u590D\u8D2D\u4E70\u3002":"The payment result is unknown. Recover the original purchase before buying again.":t.phase==="fulfillment"?i?"\u4ED8\u6B3E\u5DF2\u8BB0\u5F55\uFF0C\u5546\u6237\u6743\u76CA\u5904\u7406\u9700\u8981\u6838\u5BF9\u3002":"Payment was recorded. Access fulfillment needs attention.":t.code==="CAPABILITY_UNAVAILABLE"?i?"\u5546\u6237\u5C1A\u4E0D\u652F\u6301\u672C\u6B21\u8D2D\u4E70\u6240\u9700\u7684\u5B8C\u6574\u80FD\u529B\uFF0C\u8BF7\u5904\u7406\u540E\u6062\u590D\u539F\u64CD\u4F5C\u3002":"The merchant does not currently support all actions required by this purchase. Recover the original purchase after this is resolved.":t.code==="NOT_CONFIGURED"?i?"\u4ED8\u6B3E\u670D\u52A1\u8FDE\u63A5\u9700\u8981\u5904\u7406\uFF0C\u8BF7\u8054\u7CFB\u5546\u6237\u540E\u6062\u590D\u539F\u64CD\u4F5C\u3002":"The payment connection needs attention. Contact the merchant, then recover the original purchase.":i?"\u539F\u8D2D\u4E70\u64CD\u4F5C\u9700\u8981\u5904\u7406\uFF0C\u8BF7\u67E5\u8BE2\u6216\u6062\u590D\u6B64\u64CD\u4F5C\u3002":"The original purchase needs attention. Check or recover this operation.",void 0,void 0,t));return}if(e.merchantExecution?.type==="purchase"&&e.merchantExecution.paymentStatus==="succeeded"&&!["applied","not_applicable"].includes(e.merchantExecution.fulfillmentStatus)&&(g.textContent=i?"\u4ED8\u6B3E\u5DF2\u8BB0\u5F55\uFF0C\u6B63\u5728\u7B49\u5F85\u5546\u6237\u5B8C\u6210\u5904\u7406\u3002":"Payment was recorded. Waiting for merchant fulfillment."),e.status==="complete"){a=null,await ne(e);return}if(e.status!=="pending"){if(r.canReselect===!1){g.textContent=i?"\u6B63\u5728\u6838\u5BF9\u539F\u8D2D\u4E70\u7ED3\u679C":"Checking the original purchase result",u.textContent=i?"\u67E5\u8BE2\u7ED3\u679C":"Check result",a=async()=>v(await r.read()),u.hidden=!1;return}u.textContent=i?"\u91CD\u8BD5":"Retry",a=null,f=null;return}await H(e);let o=Date.now()+6e5,l=2e3;for(;!p&&Date.now()<o;){if(await new Promise(h=>{let P=()=>{clearTimeout(I),h()},I=setTimeout(()=>{_.signal.removeEventListener("abort",P),h()},l);_.signal.addEventListener("abort",P,{once:!0})}),p)return;if(!m()){a=async()=>v(await r.read()),u.hidden=!1;return}let t=await r.read();if(!t.problem&&t.status==="pending"&&await H(t),t.status!=="pending"||t.problem){await v(t);return}l=Math.min(l*1.5,3e4)}a=async()=>v(await r.read()),u.hidden=!1}async function T(){U?.remove(),U=null,a=T;let e=await r.confirm();if(!m()){a=T,u.hidden=!1;return}if(e.problem){await v(e);return}if(e.status==="pending"){if(e.checkoutUrl)await H(e);else if(e.execution&&e.execution.provider!=="merchant"||e.nativePurchase){if(!n.onPurchase)throw new w("PURCHASE_HANDLER_REQUIRED","Provide the merchant or native purchase handler");await n.onPurchase({operation:e,signal:_.signal})}}a=async()=>v(await r.read()),await v(e)}function re(e){U?.remove();let o=c.createElement("div"),l=c.createElement("button"),t=c.createElement("button"),h=c.createElement("p");h.textContent=e.lines.map(s=>s.productName).join(", ")+" \xB7 "+G(e.charge?.totalMinor??e.catalogAmountMinor-(e.discount?.amountMinor??0),e.currency),l.textContent=i?"\u786E\u8BA4\u8D2D\u4E70":"Confirm purchase",t.textContent=i?"\u53D6\u6D88":"Cancel",l.onclick=()=>{k(T)};let P=async()=>{let s=await r.cancel();p||(o.remove(),U=null,await v(s))};t.onclick=()=>{a=P,k(P)};let I=c.createElement("div");for(let s of e.lines){let J=c.createElement("p");J.textContent=`${s.productName} \xB7 ${G(s.catalogAmountMinor,e.currency)}${W(s.interval,i?"zh-CN":"en",s.intervalCount??1)}`,I.append(J);for(let x of s.prepaid??[]){let F=c.createElement("p"),A=r.view?.capabilities.plans[s.productVersionId]?.prepaid?.find(ae=>ae.internalFeatureId===x.internal_feature_id);F.textContent=`${A?.name??x.internal_feature_id} \xB7 ${x.quantity} ${i?"\u4EFD":"packs"} \xD7 ${x.terms.units}`,A&&(F.textContent+=W(A.reset==="week"||A.reset==="month"||A.reset==="year"?A.reset:"one_time",i?"zh-CN":"en",A.resetCount)),I.append(F)}if(s.trial){let x=c.createElement("p");x.textContent=i?`\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"}`,I.append(x)}}if(!e.charge){let s=c.createElement("p");s.textContent=i?"\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.",I.append(s)}o.append(h,I,l,t),D.prepend(o),U=o,a=null}async function z(){if(!f)return;if(a=z,!q){let o=r.view,l=o?.document.buttons[f.actionId],t=o&&l?.type==="checkout"?B(l.product,f.selection,o.capabilities):null;if(!t)throw new w("PRODUCT_UNAVAILABLE","Choose an available Product in this purchase area");let h=await n.resolveOfferId?.({actionId:f.actionId,productVersionId:t});if(p)return;h!==void 0&&(f.offerId=h),q=!0}if(!m()){u.hidden=!1;return}let e=await r.preview(f);p||re(e)}function S(e="destroy"){p||(p=!0,_.abort(),O?.destroy(),r.destroy(),d.open&&d.close(),d.remove(),C.remove(),R(),e==="dismiss"&&j(n.onDismiss))}M.onclick=()=>{m()&&S("dismiss")},d.addEventListener("cancel",e=>{e.preventDefault(),m()&&S("dismiss")}),u.onclick=()=>{a&&k(a)};try{if(L.type==="inline")b.append(C);else{let e={sm:420,md:640,lg:900};d.style.cssText=`padding:0;border:0;max-width:calc(100vw - 32px);max-height:calc(100dvh - 32px);overflow:auto;width:${"size"in L?e[L.size]+"px":"100vw"}`,d.setAttribute("aria-label",E.document.name),d.append(C),b.append(d),m()&&d.showModal()}O=te(K,{mode:"live",rendererUrl:r.webViewUrl,title:E.document.name,onAction(e,o={}){if(!m())return;let l=r.view,t=l?.document.buttons[e];if(!t)return;if(t.type==="close"){k(async()=>{await n.onAction?.(e),S(n.onAction?"action":"dismiss")});return}if(t.type==="action"){a=async()=>{if(t.action.type==="event"){if(!n.onEvent)throw new w("EVENT_HANDLER_REQUIRED","Provide the event handler");await n.onEvent(t.action.event)}else n.onOpenUrl?await n.onOpenUrl(t.action.url):c.defaultView?.location.assign(t.action.url);await n.onAction?.(e),a=null},k(a);return}if((r.quote||f)&&r.canReselect===!1){a=r.quote?async()=>v(await r.read()):z,k(a);return}let h=l&&B(t.product,o,l.capabilities);if(!l||!h){N(new w("PRODUCT_UNAVAILABLE","Choose an available Product in this purchase area"));return}if(n.onPurchaseIntent){a=null,k(async()=>{let P={actionId:e,productVersionId:h,selection:structuredClone(o),featureQuantities:X(l.document.screen,h,o,l.capabilities)};await n.onPurchaseIntent(P)});return}if(f&&f.actionId!==e){N(new w("COMMAND_PENDING","Finish the current purchase"));return}f||(f={requestId:crypto.randomUUID(),actionId:e,selection:o},q=!1),k(z)},onEscape:()=>{m()&&S("dismiss")},onError:e=>N(new Error(e))}),O.update(E.document.screen,E.document.screen.strings.defaultLocale,{capabilities:E.capabilities,serverInput:{bindings:[],answers:{}}}),await O.ready}catch(e){throw S(),e}return{check:y,host:{setSuspended(e){Y=e,C.hidden=e,e&&d.open?d.close():!e&&!p&&L.type!=="inline"&&!d.open&&d.showModal()},destroy:S,refresh:async()=>{if(!m()){if(r.view)return r.view;throw new w("PRECONDITION_FAILED","Paywall presentation is suspended")}let e=await r.refresh();return p||O?.updateCapabilities(e.capabilities),e}}}}export{fe as showPaywall};
|
package/dist/popup.d.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { b as ScreenAction, c as ScreenNode, d as ScreenDocument } from './screen-D81zCpfi.js';
|
|
2
|
+
import { O as Offer, a as OfferUnavailableReason } from './coupon-fA1QujHt.js';
|
|
3
|
+
import { b as CustomerPageData, a as SurfaceData, c as ScreenParameters, d as ScreenRendererInput, S as ScreenAnswers, C as CustomerDataIntent } from './customer-page-km5f1PJZ.js';
|
|
4
|
+
import { WindowLike } from 'dompurify';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
type PopupEvent = {
|
|
8
|
+
type: "shown";
|
|
9
|
+
} | {
|
|
10
|
+
type: "action";
|
|
11
|
+
actionId: string;
|
|
12
|
+
nodeId?: string;
|
|
13
|
+
name: string;
|
|
14
|
+
properties: ScreenAction["properties"];
|
|
15
|
+
/** Presentation-only choices; validate actual prices and entitlements on the server. */
|
|
16
|
+
selection?: Record<string, string | boolean | number>;
|
|
17
|
+
} | {
|
|
18
|
+
type: "closed";
|
|
19
|
+
reason: "close_button" | "escape" | "action" | "destroy" | "timeout";
|
|
20
|
+
} | {
|
|
21
|
+
/** claim_offer 按钮的领取结果(showRemotePopup 才有;离线 showPopup 没有投放凭证,不能领取)。 */
|
|
22
|
+
type: "offer";
|
|
23
|
+
actionId: string;
|
|
24
|
+
offer: Offer | null;
|
|
25
|
+
reason: OfferUnavailableReason | "error" | null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Render the current ScreenDocument 4 into DOM.
|
|
30
|
+
*
|
|
31
|
+
* 这个函数会被 `rendererRuntime.toString()` 序列化后放进沙箱 iframe(Web)或 WebView(iOS / Android)
|
|
32
|
+
* 里单独执行,所以**必须完全自包含**:不能引用函数外的任何变量、导入或辅助函数(类型除外)。
|
|
33
|
+
* packages/sdk/src/renderer/runtime.test.ts 会在没有模块作用域的环境里执行序列化结果来保证这一点。
|
|
34
|
+
*
|
|
35
|
+
* 安全:文案一律通过 textContent 写入;自定义 HTML 由宿主清洗后放进各自的 Shadow DOM;
|
|
36
|
+
* iframe 的 CSP 只允许执行带 nonce 的本脚本,不能联网,原生图片允许 https 或经过校验的有界内嵌 SVG/JPEG。
|
|
37
|
+
*/
|
|
38
|
+
type RendererMode = "live" | "editor";
|
|
39
|
+
/** Host UI copy for editor controls, independent of the document content locale. */
|
|
40
|
+
type RendererEditorMessages = {
|
|
41
|
+
emptyContainer?: string;
|
|
42
|
+
nodeTypes?: Partial<Record<ScreenNode["type"], string>>;
|
|
43
|
+
};
|
|
44
|
+
type ScreenNodeRect = {
|
|
45
|
+
id: string;
|
|
46
|
+
x: number;
|
|
47
|
+
y: number;
|
|
48
|
+
width: number;
|
|
49
|
+
height: number;
|
|
50
|
+
};
|
|
51
|
+
/** Editor shortcuts typed while focus is inside the canvas; the host owns their meaning. */
|
|
52
|
+
type RendererEditorKey = {
|
|
53
|
+
key: string;
|
|
54
|
+
phase: "down" | "up";
|
|
55
|
+
meta: boolean;
|
|
56
|
+
ctrl: boolean;
|
|
57
|
+
shift: boolean;
|
|
58
|
+
alt: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Preserve authoritative decimal strings and localize their digits without rounding through Number. */
|
|
62
|
+
declare function formatCustomerDecimal(value: string, locale: string): string;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 自定义 HTML 块的内容策略(服务端发布、控制台预览、SDK 渲染共用):
|
|
66
|
+
* 只允许排版类标签和 https 图片;不允许脚本、表单、链接、内联样式和事件属性;
|
|
67
|
+
* CSS 不允许外部资源、@import 和未知语法。按钮通过 data-grow-action 绑定文档里的动作。
|
|
68
|
+
* 结果是幂等的:清洗过的内容再清洗一次不变。
|
|
69
|
+
*/
|
|
70
|
+
declare function sanitizeCustomHtml(html: string, css: string, actionIds: readonly string[], window: WindowLike): {
|
|
71
|
+
html: string;
|
|
72
|
+
css: string;
|
|
73
|
+
};
|
|
74
|
+
type HtmlCodeDiagnostic = {
|
|
75
|
+
language: "html" | "css";
|
|
76
|
+
from: number;
|
|
77
|
+
to: number;
|
|
78
|
+
severity: "warning" | "error";
|
|
79
|
+
message: string;
|
|
80
|
+
};
|
|
81
|
+
/** Authoring diagnostics use the exact same sanitizer as publication and delivery. No relaxed preview policy. */
|
|
82
|
+
declare function inspectCustomHtml(html: string, css: string, actionIds: readonly string[], window: WindowLike): {
|
|
83
|
+
html: string;
|
|
84
|
+
css: string;
|
|
85
|
+
diagnostics: HtmlCodeDiagnostic[];
|
|
86
|
+
valid: boolean;
|
|
87
|
+
};
|
|
88
|
+
/** Strict validation always runs; repeated pure design can reuse HTML/CSS preparation. */
|
|
89
|
+
declare function prepareScreen(input: unknown, window: WindowLike): ScreenDocument;
|
|
90
|
+
|
|
91
|
+
declare const hostedRendererShell: {
|
|
92
|
+
readonly tag: string;
|
|
93
|
+
readonly csp: string;
|
|
94
|
+
readonly html: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/** The bootstrap the shell's CSP hash authorizes. */
|
|
98
|
+
declare function hostedRendererScript(): string;
|
|
99
|
+
|
|
100
|
+
type PopupHandle = {
|
|
101
|
+
destroy: () => void;
|
|
102
|
+
updateCapabilities: (data: SurfaceData) => void;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Passive import. Call explicitly after obtaining an approved configuration.
|
|
106
|
+
* 接受完整文档(离线使用导出的发布版本)或下发的单语言文档;多语言时按 options.locale 与浏览器语言挑选。
|
|
107
|
+
*/
|
|
108
|
+
type PopupOptions = {
|
|
109
|
+
/** Hosted public shell, supplied by the current SDK gateway after authorization. */
|
|
110
|
+
rendererUrl?: string;
|
|
111
|
+
onError?: (error: unknown) => void;
|
|
112
|
+
locale?: string;
|
|
113
|
+
capabilities?: SurfaceData;
|
|
114
|
+
onEvent?: (event: PopupEvent) => void | Promise<void>;
|
|
115
|
+
};
|
|
116
|
+
type PreparedPopupHandle = PopupHandle & {
|
|
117
|
+
/** Resolves after the first rendered response, rather than the bridge handshake. */
|
|
118
|
+
ready: Promise<void>;
|
|
119
|
+
/** Display the prepared iframe in place. A handle is used for one presentation. */
|
|
120
|
+
show(): PopupHandle;
|
|
121
|
+
};
|
|
122
|
+
declare function showPopup(input: ScreenDocument, options?: PopupOptions): PopupHandle;
|
|
123
|
+
/** Explicitly prepare an already-authorized screen before the opening click.
|
|
124
|
+
* Destroy on unmount or identity change. Preparation emits no display or action events. */
|
|
125
|
+
declare function preparePopup(input: ScreenDocument, options?: PopupOptions): PreparedPopupHandle;
|
|
126
|
+
type ScreenPreviewHandle = {
|
|
127
|
+
/** Rejects if the renderer cannot start; this does not grant customer authorization. */
|
|
128
|
+
ready: Promise<void>;
|
|
129
|
+
/** The document of the last update. */
|
|
130
|
+
readonly document: ScreenDocument | null;
|
|
131
|
+
/** 重新渲染(完整文档 + 当前编辑的语言;缺失的文案用默认语言淡显)。 */
|
|
132
|
+
update(doc: ScreenDocument, locale: string,
|
|
133
|
+
/** Current single-screen presentation and server-owned input bindings. */
|
|
134
|
+
options?: {
|
|
135
|
+
parameters?: ScreenParameters;
|
|
136
|
+
capabilities?: SurfaceData;
|
|
137
|
+
customerData?: CustomerPageData;
|
|
138
|
+
serverInput?: ScreenRendererInput;
|
|
139
|
+
selection?: Record<string, string | boolean | number>;
|
|
140
|
+
/** Explicitly restart a live page even when its design and input are unchanged. */
|
|
141
|
+
reset?: boolean;
|
|
142
|
+
}): void;
|
|
143
|
+
updateCustomerData(data: CustomerPageData): void;
|
|
144
|
+
updateCapabilities(data: SurfaceData): void;
|
|
145
|
+
/** Resize the visible viewport without replacing the document or interaction state. */
|
|
146
|
+
resizeViewport(height: number): void;
|
|
147
|
+
/** 编辑模式下的悬停与选中描边。 */
|
|
148
|
+
highlight(hoverId: string | null, selectedId: string | null): void;
|
|
149
|
+
destroy(): void;
|
|
150
|
+
};
|
|
151
|
+
/**
|
|
152
|
+
* 控制台编辑器的画布:同一个渲染器,以「编辑模式」嵌在容器里(不是弹出层)。
|
|
153
|
+
* 点击选中、悬停高亮、双击进入文字编辑都通过回调告诉编辑器;按钮不会执行动作。
|
|
154
|
+
* mode 为 "live" 时按钮可点(翻译页等只读预览里用于查看交互),回调 onAction。
|
|
155
|
+
*/
|
|
156
|
+
declare function mountScreenPreview(container: HTMLElement, options?: {
|
|
157
|
+
mode?: RendererMode;
|
|
158
|
+
rendererUrl?: string;
|
|
159
|
+
title?: string;
|
|
160
|
+
/** Translate editor controls without changing authored text or the content locale. */
|
|
161
|
+
editorMessages?: RendererEditorMessages;
|
|
162
|
+
/** 固定模拟浏览器的高度;全屏铺满,其他展示方式在该视口内滚动。 */
|
|
163
|
+
viewportHeight?: number;
|
|
164
|
+
onHover?: (id: string | null) => void;
|
|
165
|
+
onSelect?: (id: string | null) => void;
|
|
166
|
+
onEdit?: (id: string) => void;
|
|
167
|
+
onAction?: (actionId: string, selection?: Record<string, string | boolean | number>, answers?: ScreenAnswers) => void;
|
|
168
|
+
onSelection?: (selection: Record<string, string | boolean | number>) => void;
|
|
169
|
+
onAnswers?: (answers: ScreenAnswers) => void;
|
|
170
|
+
onRendered?: () => void;
|
|
171
|
+
/** Live previews report the page actually rendered, when the current screen is rendered. */
|
|
172
|
+
onPageChange?: (pageId: string) => void;
|
|
173
|
+
onEscape?: () => void;
|
|
174
|
+
onResize?: (height: number) => void;
|
|
175
|
+
/** Editing-only geometry in iframe viewport coordinates; no DOM access across the sandbox. */
|
|
176
|
+
onLayout?: (nodes: ScreenNodeRect[]) => void;
|
|
177
|
+
/** Editing-only shortcuts typed while focus is inside the canvas. */
|
|
178
|
+
onKey?: (key: RendererEditorKey) => void;
|
|
179
|
+
/** Editing-only pinch or ctrl + wheel zoom; positive deltas zoom out. */
|
|
180
|
+
onZoom?: (delta: number) => void;
|
|
181
|
+
onError?: (message: string) => void;
|
|
182
|
+
onCustomerDataIntent?: (intent: CustomerDataIntent) => void;
|
|
183
|
+
}): ScreenPreviewHandle;
|
|
184
|
+
type CustomerPageHandle = {
|
|
185
|
+
versionId: string;
|
|
186
|
+
refresh(): Promise<void>;
|
|
187
|
+
snapshot(): CustomerPageData;
|
|
188
|
+
destroy(): void;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
export { type CustomerPageHandle, type HtmlCodeDiagnostic, type PopupEvent, type PopupHandle, type PopupOptions, type PreparedPopupHandle, type RendererEditorKey, type RendererEditorMessages, ScreenDocument, type ScreenNodeRect, type ScreenPreviewHandle, formatCustomerDecimal, hostedRendererScript, hostedRendererShell, inspectCustomHtml, mountScreenPreview, preparePopup, prepareScreen, sanitizeCustomHtml, showPopup };
|
package/dist/popup.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{a as o,b as r,c as e,d as f,e as m,g as p,h as t,i as x,j as a}from"./chunk-X6BCUINI.js";import"./chunk-SSLZKIIU.js";import"./chunk-ACF2FBUB.js";import"./chunk-DSPLGTQI.js";import"./chunk-7DXL3LHT.js";import"./chunk-XSVQHPQT.js";export{p as formatCustomerDecimal,r as hostedRendererScript,o as hostedRendererShell,f as inspectCustomHtml,a as mountScreenPreview,x as preparePopup,m as prepareScreen,e as sanitizeCustomHtml,t as showPopup};
|