@wix/himalaya-cli 0.1353.0 → 0.1355.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.
Files changed (25) hide show
  1. package/dist/cli.mjs +88 -27
  2. package/dist/preview-assets.generated.bin +0 -0
  3. package/guides/web-page-native-sdk.md +2 -3
  4. package/guides/web-page-sdk-reference.md +1 -1
  5. package/package.json +2 -2
  6. package/templates/beauty-salon/tier5-src/_shared/hosted-checkout.ts +36 -10
  7. package/templates/branded-lite/tier5-src/_shared/hosted-checkout.ts +36 -10
  8. package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +31 -2
  9. package/templates/community-club/tier5-src/_shared/hosted-checkout.ts +36 -10
  10. package/templates/community-club/tier5-src/himi-events-detail/index.ts +31 -2
  11. package/templates/events-meetups/tier5-src/_shared/hosted-checkout.ts +36 -10
  12. package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +31 -2
  13. package/templates/gym-fitness/tier5-src/_shared/hosted-checkout.ts +36 -10
  14. package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +31 -2
  15. package/templates/nonprofit-cause/tier5-src/_shared/hosted-checkout.ts +36 -10
  16. package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +31 -2
  17. package/templates/podcast-creator/tier5-src/_shared/hosted-checkout.ts +36 -10
  18. package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +31 -2
  19. package/templates/portfolio-creative/tier5-src/_shared/hosted-checkout.ts +36 -10
  20. package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +31 -2
  21. package/templates/restaurant/tier5-src/_shared/hosted-checkout.ts +36 -10
  22. package/templates/restaurant/tier5-src/himi-events-detail/index.ts +31 -2
  23. package/templates/retail-store/tier5-src/_shared/hosted-checkout.ts +36 -10
  24. package/templates/yoga-studio/tier5-src/_shared/hosted-checkout.ts +36 -10
  25. package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +31 -2
package/dist/cli.mjs CHANGED
@@ -7706,6 +7706,7 @@ function resolveTranslation(value, catalog3, sourceCatalog) {
7706
7706
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
7707
7707
  var init_bindings = __esm({
7708
7708
  "../../core/ts/src/bindings.ts"() {
7709
+ "use strict";
7709
7710
  BINDING_SIGILS = ["service", "flag", "item", "t"];
7710
7711
  BINDING_SIGIL_RE = new RegExp(
7711
7712
  `^\\$(?:${BINDING_SIGILS.filter((s2) => s2 !== "t").join("|")})\\b|^\\$t:`
@@ -22017,7 +22018,7 @@ function usageStringCopyFor(permissionCopy) {
22017
22018
  }
22018
22019
  return out2;
22019
22020
  }
22020
- var BRIDGE_CAPABILITY_IDS, KNOWN_CAPABILITY_IDS, CAPABILITY_LITERAL, BRIDGE_MARKERS, NEEDS_BOOT_WIRING, AWAITING_THIRD_PARTY_SIGN_OFF, NOT_DIST_ADDABLE;
22021
+ var BRIDGE_CAPABILITY_IDS, KNOWN_CAPABILITY_IDS, CAPABILITY_LITERAL, BRIDGE_MARKERS, NEEDS_BOOT_WIRING, NOT_DIST_ADDABLE;
22021
22022
  var init_known = __esm({
22022
22023
  "../../core/sdk/capability-catalog/src/known.ts"() {
22023
22024
  init_builtins();
@@ -22059,13 +22060,7 @@ var init_known = __esm({
22059
22060
  // on Android the shell must pick Godot's libc++_shared over the core's (himi-game's godotStl).
22060
22061
  godot: "needs the shell's GameSurface view and, on Android, its libc++_shared pick for the engine, which a build cannot add"
22061
22062
  });
22062
- AWAITING_THIRD_PARTY_SIGN_OFF = Object.freeze({
22063
- calls: "links the community libwebrtc engine (HimalayaCoreCalls / :himalaya-core-calls), whose third-party sign-off for store-shipping apps is still open, so a dist build does not add it to a customer binary"
22064
- });
22065
- NOT_DIST_ADDABLE = Object.freeze({
22066
- ...NEEDS_BOOT_WIRING,
22067
- ...AWAITING_THIRD_PARTY_SIGN_OFF
22068
- });
22063
+ NOT_DIST_ADDABLE = NEEDS_BOOT_WIRING;
22069
22064
  }
22070
22065
  });
22071
22066
 
@@ -35387,6 +35382,69 @@ function resolveServeBase() {
35387
35382
  }
35388
35383
  return (raw ?? "").replace(/\/$/, "");
35389
35384
  }
35385
+ function resolvePlaneControlReadBase() {
35386
+ const explicit = process.env.HIMALAYA_PLAYER_CONTROL_URL;
35387
+ if (explicit) return explicit.replace(/\/$/, "");
35388
+ const override = process.env.HIMALAYA_PLAYER_SERVE_URL;
35389
+ if (override) {
35390
+ const base = resolveServeBase();
35391
+ return sameOrigin(base, LOCAL_ORIGINS.serve) ? LOCAL_ORIGINS.control : base;
35392
+ }
35393
+ let control = "";
35394
+ try {
35395
+ control = resolveTarget().control ?? "";
35396
+ } catch {
35397
+ }
35398
+ const serve = resolveServeBase();
35399
+ return isLocalHostUrl(control) && isLocalHostUrl(serve) && !sameUrlOrigin(control, serve) ? control.replace(/\/$/, "") : serve;
35400
+ }
35401
+ function isLocalHostUrl(url) {
35402
+ let h2;
35403
+ try {
35404
+ h2 = new URL(url).hostname.replace(/^\[|\]$/g, "").toLowerCase();
35405
+ } catch {
35406
+ return false;
35407
+ }
35408
+ if (h2 === "localhost" || h2 === "::1" || h2.endsWith(".local")) return true;
35409
+ const p2 = h2.split(".").map(Number);
35410
+ if (p2.length !== 4 || p2.some((n2) => !Number.isInteger(n2))) return false;
35411
+ return p2[0] === 127 || p2[0] === 10 || p2[0] === 192 && p2[1] === 168 || p2[0] === 169 && p2[1] === 254 || p2[0] === 172 && p2[1] >= 16 && p2[1] <= 31;
35412
+ }
35413
+ function sameUrlOrigin(a2, b2) {
35414
+ try {
35415
+ return new URL(a2).origin === new URL(b2).origin;
35416
+ } catch {
35417
+ return false;
35418
+ }
35419
+ }
35420
+ function isLoopbackUrl(url) {
35421
+ try {
35422
+ return ["127.0.0.1", "localhost", "[::1]"].includes(new URL(url).hostname);
35423
+ } catch {
35424
+ return false;
35425
+ }
35426
+ }
35427
+ function sameOrigin(url, local) {
35428
+ try {
35429
+ const a2 = new URL(url);
35430
+ const b2 = new URL(local);
35431
+ return isLoopbackUrl(url) && a2.port === b2.port && a2.protocol === b2.protocol && (a2.pathname === "/" || a2.pathname === "");
35432
+ } catch {
35433
+ return false;
35434
+ }
35435
+ }
35436
+ function tenantHostPlaneRead(url, headers2, hostApp, method = "GET") {
35437
+ if (!hostApp || !isTenantHost(hostApp) || method !== "GET" && method !== "HEAD") return null;
35438
+ const [path2, query = ""] = url.split("?");
35439
+ if (path2 === "/v1/site/member-app/resolve") return resolvePlaneControlReadBase() || null;
35440
+ if (path2 === "/v1/release/current") {
35441
+ const app = new URLSearchParams(query).get("app")?.trim();
35442
+ if (!app || app === hostApp || HOSTED_TENANTS[hostApp].includes(app)) return null;
35443
+ if (targetTenant(url, headers2, hostApp) !== null) return null;
35444
+ return resolveServeBase() || null;
35445
+ }
35446
+ return null;
35447
+ }
35390
35448
  function targetTenant(url, headers2, hostApp) {
35391
35449
  const hdr = headers2["x-himalaya-app"];
35392
35450
  const headerApp = Array.isArray(hdr) ? hdr[0] : hdr;
@@ -35454,6 +35512,7 @@ var PLAYER_APP_ID, HOSTED_TENANTS, TENANT_PREFIX, LOOPBACK_PEERS, STRIP_RESPONSE
35454
35512
  var init_player_proxy = __esm({
35455
35513
  "../../core/dev-server/src/player-proxy.ts"() {
35456
35514
  init_config();
35515
+ init_serve_origins();
35457
35516
  PLAYER_APP_ID = "himi-player";
35458
35517
  HOSTED_TENANTS = {
35459
35518
  "himi-spaces-lite": ["himi-branded-lite"]
@@ -132468,6 +132527,8 @@ function createDevServer(opts = {}) {
132468
132527
  forwardAs: clientHost ? { host: clientHost, prefix: `${TENANT_PREFIX}${encodeURIComponent(tenant)}` } : void 0
132469
132528
  });
132470
132529
  }
132530
+ const planeRead = tenantHostPlaneRead(url, req.headers, state.appName, req.method);
132531
+ if (planeRead) return await proxyToServe(planeRead, req, res);
132471
132532
  }
132472
132533
  if (url.split("?")[0] === "/v1/app-icon" && req.method === "GET") {
132473
132534
  const iconApp = new URLSearchParams(url.split("?")[1] ?? "").get("app") ?? "";
@@ -156718,25 +156779,25 @@ var init_preview_assets_generated = __esm({
156718
156779
  "../../packages/serve/src/preview-assets.generated.ts"() {
156719
156780
  init_preview_assets_format();
156720
156781
  PREVIEW_ASSET_MANIFEST = [
156721
- { key: "assets/index-BDNhcRLx.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1271913, sha256: "b78f7b3400ea25a2066e24c6b771199b819a833b624499d221edbc5f1b01a18b" },
156722
- { key: "assets/pdf-mXRF2YFr.js", contentType: "application/javascript; charset=utf-8", offset: 1271921, length: 488199, sha256: "3ecd61c959307bee0f74d6d9a3c6ab94dbf7b7305d6d67e2f0167dbf3bc6034a" },
156723
- { key: "assets/pdf.worker.min-BQa_JDET.js", contentType: "application/javascript; charset=utf-8", offset: 1760120, length: 73, sha256: "41bcfda4f3c8c0a81c785ea87c69afc8e7d0ed8c333324aee94c273d881bb6aa" },
156724
- { key: "assets/pdf.worker.min-BmVo14Nb.mjs", contentType: "application/javascript; charset=utf-8", offset: 1760193, length: 1317034, sha256: "a33cfe728c584fdba4fcc1fd54bcdc2f9f2f13889ddbb5b2bd1d0f8cbe49b84e" },
156725
- { key: "assets/pdfDocument-BZIqh5sA.js", contentType: "application/javascript; charset=utf-8", offset: 3077227, length: 2858, sha256: "32abe2da16ae5c22ce831a4a75bdb6e6121dc99fdae72be8ec22038d6982dd81" },
156726
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 3080085, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
156727
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 3084695, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
156728
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 3573279, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
156729
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 3664203, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
156730
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 3755187, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
156731
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 3846167, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
156732
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 3937039, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
156733
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 4028019, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
156734
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 4136655, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
156735
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 4245739, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
156736
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 4354787, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
156737
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 4462487, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
156738
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 4550595, length: 4947, sha256: "475486545202c2973900224ffffa48ffbd7313e49b559cbcf9be678b068bff82" },
156739
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 4555542, length: 17512, sha256: "c5c8612a8b3f5a1828d0c6593106036a180eea11da80232f5181548a50679137" }
156782
+ { key: "assets/index-CfDtWTd9.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1271649, sha256: "b09aff3c4ca8b6d279140f389c9c2c85988dd12e78414ac3e1584f72456ee566" },
156783
+ { key: "assets/pdf-CVazw9Ub.js", contentType: "application/javascript; charset=utf-8", offset: 1271657, length: 488199, sha256: "e42ce9b09b54fe70642b68c38e7209b4a357177f277ef8c40faa3262f1853cde" },
156784
+ { key: "assets/pdf.worker.min-BQa_JDET.js", contentType: "application/javascript; charset=utf-8", offset: 1759856, length: 73, sha256: "41bcfda4f3c8c0a81c785ea87c69afc8e7d0ed8c333324aee94c273d881bb6aa" },
156785
+ { key: "assets/pdf.worker.min-BmVo14Nb.mjs", contentType: "application/javascript; charset=utf-8", offset: 1759929, length: 1317034, sha256: "a33cfe728c584fdba4fcc1fd54bcdc2f9f2f13889ddbb5b2bd1d0f8cbe49b84e" },
156786
+ { key: "assets/pdfDocument-HGrmnRP0.js", contentType: "application/javascript; charset=utf-8", offset: 3076963, length: 2858, sha256: "abf488e7017dd90516a2ada53ec9f0f9bf9402173c8f5b7483917c4d60b03411" },
156787
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 3079821, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
156788
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 3084431, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
156789
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 3573015, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
156790
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 3663939, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
156791
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 3754923, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
156792
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 3845903, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
156793
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 3936775, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
156794
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 4027755, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
156795
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 4136391, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
156796
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 4245475, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
156797
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 4354523, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
156798
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 4462223, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
156799
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 4550331, length: 4947, sha256: "d9b156d2824937902486f47a6250a5589f6e33f224eef7bd512506612779159e" },
156800
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 4555278, length: 17512, sha256: "c5c8612a8b3f5a1828d0c6593106036a180eea11da80232f5181548a50679137" }
156740
156801
  ];
156741
156802
  }
156742
156803
  });
Binary file
@@ -39,10 +39,9 @@ Your page can only use capabilities the **app owner** turned on for your site. I
39
39
  `capabilities`, allowing **every** method the capability's page lists — except
40
40
  `presigned_upload.putFile`, which reads a device file and is allowed only when `files` is enabled
41
41
  too. So enabling a capability is enough; the owner does not list methods.
42
- - **What a Brandy app cannot enable:** `app_info`, `calls`, `godot`, `himi_apps`, `live_activity`,
42
+ - **What a Brandy app cannot enable:** `app_info`, `godot`, `himi_apps`, `live_activity`,
43
43
  `local_database`, `release_control`, `system_intents`, `ui` and `wix_platform` — most need wiring
44
- a website app's shell does not have, and `calls` waits on a third-party sign-off for its WebRTC
45
- engine, so plan your page without them. `base44` works only when the
44
+ a website app's shell does not have, so plan your page without them. `base44` works only when the
46
45
  app also has a Base44 binding. Every other capability in the reference can be enabled; other Himi
47
46
  apps (not Brandy) may offer these too.
48
47
  - Enabling a capability changes the app's native permissions, so the owner ships a **new app
@@ -20,7 +20,7 @@ the SDK sources, so it matches the shipped SDKs.
20
20
  - [`biometrics`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fbiometrics?format=md) — Biometric authentication capability. 2 methods.
21
21
  - [`browser_auth`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fbrowser_auth?format=md) — Native browser SSO. 1 method.
22
22
  - [`calendar`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcalendar?format=md) — Device calendar. 6 methods.
23
- - [`calls`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcalls?format=md) — Real-time 1:1 audio calls (Stage 1). 16 methods · not in Brandy.
23
+ - [`calls`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcalls?format=md) — Real-time 1:1 audio calls (Stage 1). 16 methods.
24
24
  - [`camera`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcamera?format=md) — Camera capability. 3 methods.
25
25
  - [`clipboard`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fclipboard?format=md) — System clipboard. 3 methods.
26
26
  - [`contacts`](https://apps.wix.com/_api/himi-serve/authoring/resource/web-sdk%2Fcontacts?format=md) — Device address book. 4 methods.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1353.0",
3
+ "version": "0.1355.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -71,5 +71,5 @@
71
71
  "artifactId": "himalaya-cli"
72
72
  }
73
73
  },
74
- "falconPackageHash": "1a0d58e6899a9865208dd609c52f853493400202cbdc1a198968c548"
74
+ "falconPackageHash": "640ab0608d97ba66325d1a681f0affc03e5c7f6fbb0ec1996c5e34dc"
75
75
  }
@@ -1,8 +1,12 @@
1
- // Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
2
- // pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
3
- // instance, so the HOST opens the site's checkout signed in as that member. The worker names only
4
- // its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
5
- // the site's `createSessionCookie` → checkout. The session token never reaches this worker.
1
+ // Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
2
+ // lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
3
+ // checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
4
+ // checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
5
+ // The session token never reaches this worker.
6
+ //
7
+ // Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
8
+ // worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
9
+ // plans page the same way, signed in — where the member picks and pays on the site.
6
10
 
7
11
  import { dispatch } from "@himalaya/dispatch";
8
12
  import { hostSiteFromInput } from "./branded-lite-hosted.js";
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
13
17
  * URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
14
18
  export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
15
19
  export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
20
+ /** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
21
+ export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
22
+
23
+ /** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
24
+ * from Wix itself and decides whether the member may be signed in there. */
25
+ export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
16
26
 
17
27
  /** Whether this worker runs inside a host that opened a site for it. */
18
28
  export function isHostedWorker(): boolean {
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
20
30
  }
21
31
 
22
32
  type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
33
+ type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
23
34
 
24
- /** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
25
- * lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
26
- * opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
27
- export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
35
+ /** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
36
+ * that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
37
+ function hostOpener(action: string, params: Record<string, string>): HostOpener {
28
38
  return async (stillMayCharge) => {
29
39
  try {
30
- const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
40
+ const prepared = await dispatch(action, params) as HostAnswer;
31
41
  const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
32
42
  if (typeof ticket !== "string" || !ticket) return false;
33
43
  // An unopened ticket simply expires in the host: nothing to cancel.
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
40
50
  }
41
51
  };
42
52
  }
53
+
54
+ /** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
55
+ * lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
56
+ * opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
57
+ export function hostedCheckoutOpener(checkoutId: string): HostOpener {
58
+ return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
59
+ }
60
+
61
+ /** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
62
+ * (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
63
+ export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
64
+ const params: Record<string, string> = page.page === "event"
65
+ ? { page: "event", eventId: page.eventId }
66
+ : { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
67
+ return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
68
+ }
@@ -1,8 +1,12 @@
1
- // Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
2
- // pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
3
- // instance, so the HOST opens the site's checkout signed in as that member. The worker names only
4
- // its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
5
- // the site's `createSessionCookie` → checkout. The session token never reaches this worker.
1
+ // Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
2
+ // lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
3
+ // checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
4
+ // checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
5
+ // The session token never reaches this worker.
6
+ //
7
+ // Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
8
+ // worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
9
+ // plans page the same way, signed in — where the member picks and pays on the site.
6
10
 
7
11
  import { dispatch } from "@himalaya/dispatch";
8
12
  import { hostSiteFromInput } from "./branded-lite-hosted.js";
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
13
17
  * URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
14
18
  export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
15
19
  export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
20
+ /** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
21
+ export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
22
+
23
+ /** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
24
+ * from Wix itself and decides whether the member may be signed in there. */
25
+ export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
16
26
 
17
27
  /** Whether this worker runs inside a host that opened a site for it. */
18
28
  export function isHostedWorker(): boolean {
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
20
30
  }
21
31
 
22
32
  type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
33
+ type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
23
34
 
24
- /** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
25
- * lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
26
- * opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
27
- export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
35
+ /** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
36
+ * that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
37
+ function hostOpener(action: string, params: Record<string, string>): HostOpener {
28
38
  return async (stillMayCharge) => {
29
39
  try {
30
- const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
40
+ const prepared = await dispatch(action, params) as HostAnswer;
31
41
  const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
32
42
  if (typeof ticket !== "string" || !ticket) return false;
33
43
  // An unopened ticket simply expires in the host: nothing to cancel.
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
40
50
  }
41
51
  };
42
52
  }
53
+
54
+ /** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
55
+ * lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
56
+ * opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
57
+ export function hostedCheckoutOpener(checkoutId: string): HostOpener {
58
+ return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
59
+ }
60
+
61
+ /** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
62
+ * (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
63
+ export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
64
+ const params: Record<string, string> = page.page === "event"
65
+ ? { page: "event", eventId: page.eventId }
66
+ : { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
67
+ return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
68
+ }
@@ -35,7 +35,10 @@ import {
35
35
  type EventRow,
36
36
  } from "../_shared/himi-events";
37
37
  import { initials } from "../_shared/himi-text.js";
38
- import { isHostedWorker } from "../_shared/hosted-checkout.js";
38
+ import { hostedSitePageOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
39
+
40
+ /** A hosted signed-in event-page open is in flight (see openRegistration). */
41
+ let hostedOpenInFlight = false;
39
42
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
40
43
  import { openRichLink } from "../_shared/rich-content-link.js";
41
44
  import { richBody } from "../_shared/rich-body.js";
@@ -579,7 +582,8 @@ const svc = defineService<S, {
579
582
  // `checkoutReservation` needs a buyer + guest form this screen does not collect, and the
580
583
  // redirect session needs a headless OAuth client a hosted app does not have. Skip both —
581
584
  // the native chain would also leave a ticket reservation held for nothing — and open the
582
- // event's own page, where the member registers on the site.
585
+ // event's own page, where the member registers on the site: signed in by the host where it can
586
+ // (`himi.host.memberSitePage`), else as a visitor.
583
587
  if (s.registrationKind === "tickets" && s.id && !isHostedWorker()) {
584
588
  let reservationId = "";
585
589
  try {
@@ -658,6 +662,31 @@ const svc = defineService<S, {
658
662
  // fall through to registrationUrl
659
663
  }
660
664
  }
665
+ // Hosted: the HOST opens the event's own page signed in as the lent member — ids only; it
666
+ // reads the page URL from Wix and refuses (a custom domain → a visitor page; no published Events
667
+ // page → nothing) on its own. Nothing here can change between its prepare and its open (the event
668
+ // is fixed and the page is where the member chooses and pays), so the guard has nothing to re-read.
669
+ // A host that refused or failed leaves the visitor page below.
670
+ if (isHostedWorker() && s.id && (s.registrationKind === "tickets" || s.registrationKind === "rsvp")) {
671
+ // One signed-in open at a time: the host keeps a single one-time ticket, so a second tap would
672
+ // replace the first tap's ticket and drop that tap into the signed-OUT visitor page below.
673
+ if (hostedOpenInFlight) return s;
674
+ hostedOpenInFlight = true;
675
+ let opened: boolean | "withdrawn";
676
+ try {
677
+ opened = await hostedSitePageOpener({ page: "event", eventId: s.id })(async () => true);
678
+ } finally {
679
+ hostedOpenInFlight = false;
680
+ }
681
+ if (opened === true) {
682
+ return derive({
683
+ ...s,
684
+ actionMessage: s.registrationKind === "tickets"
685
+ ? copyParam("event.checkoutOpened", "Complete checkout in the browser to get your tickets.")
686
+ : "",
687
+ });
688
+ }
689
+ }
661
690
  if (!s.registrationUrl) {
662
691
  return derive({ ...s, actionMessage: copyParam("event.registrationOnPage", "Registration for this event happens on the event page.") });
663
692
  }
@@ -1,8 +1,12 @@
1
- // Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
2
- // pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
3
- // instance, so the HOST opens the site's checkout signed in as that member. The worker names only
4
- // its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
5
- // the site's `createSessionCookie` → checkout. The session token never reaches this worker.
1
+ // Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
2
+ // lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
3
+ // checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
4
+ // checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
5
+ // The session token never reaches this worker.
6
+ //
7
+ // Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
8
+ // worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
9
+ // plans page the same way, signed in — where the member picks and pays on the site.
6
10
 
7
11
  import { dispatch } from "@himalaya/dispatch";
8
12
  import { hostSiteFromInput } from "./branded-lite-hosted.js";
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
13
17
  * URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
14
18
  export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
15
19
  export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
20
+ /** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
21
+ export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
22
+
23
+ /** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
24
+ * from Wix itself and decides whether the member may be signed in there. */
25
+ export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
16
26
 
17
27
  /** Whether this worker runs inside a host that opened a site for it. */
18
28
  export function isHostedWorker(): boolean {
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
20
30
  }
21
31
 
22
32
  type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
33
+ type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
23
34
 
24
- /** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
25
- * lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
26
- * opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
27
- export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
35
+ /** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
36
+ * that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
37
+ function hostOpener(action: string, params: Record<string, string>): HostOpener {
28
38
  return async (stillMayCharge) => {
29
39
  try {
30
- const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
40
+ const prepared = await dispatch(action, params) as HostAnswer;
31
41
  const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
32
42
  if (typeof ticket !== "string" || !ticket) return false;
33
43
  // An unopened ticket simply expires in the host: nothing to cancel.
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
40
50
  }
41
51
  };
42
52
  }
53
+
54
+ /** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
55
+ * lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
56
+ * opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
57
+ export function hostedCheckoutOpener(checkoutId: string): HostOpener {
58
+ return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
59
+ }
60
+
61
+ /** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
62
+ * (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
63
+ export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
64
+ const params: Record<string, string> = page.page === "event"
65
+ ? { page: "event", eventId: page.eventId }
66
+ : { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
67
+ return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
68
+ }
@@ -35,7 +35,10 @@ import {
35
35
  type EventRow,
36
36
  } from "../_shared/himi-events";
37
37
  import { initials } from "../_shared/himi-text.js";
38
- import { isHostedWorker } from "../_shared/hosted-checkout.js";
38
+ import { hostedSitePageOpener, isHostedWorker } from "../_shared/hosted-checkout.js";
39
+
40
+ /** A hosted signed-in event-page open is in flight (see openRegistration). */
41
+ let hostedOpenInFlight = false;
39
42
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
40
43
  import { openRichLink } from "../_shared/rich-content-link.js";
41
44
  import { richBody } from "../_shared/rich-body.js";
@@ -579,7 +582,8 @@ const svc = defineService<S, {
579
582
  // `checkoutReservation` needs a buyer + guest form this screen does not collect, and the
580
583
  // redirect session needs a headless OAuth client a hosted app does not have. Skip both —
581
584
  // the native chain would also leave a ticket reservation held for nothing — and open the
582
- // event's own page, where the member registers on the site.
585
+ // event's own page, where the member registers on the site: signed in by the host where it can
586
+ // (`himi.host.memberSitePage`), else as a visitor.
583
587
  if (s.registrationKind === "tickets" && s.id && !isHostedWorker()) {
584
588
  let reservationId = "";
585
589
  try {
@@ -658,6 +662,31 @@ const svc = defineService<S, {
658
662
  // fall through to registrationUrl
659
663
  }
660
664
  }
665
+ // Hosted: the HOST opens the event's own page signed in as the lent member — ids only; it
666
+ // reads the page URL from Wix and refuses (a custom domain → a visitor page; no published Events
667
+ // page → nothing) on its own. Nothing here can change between its prepare and its open (the event
668
+ // is fixed and the page is where the member chooses and pays), so the guard has nothing to re-read.
669
+ // A host that refused or failed leaves the visitor page below.
670
+ if (isHostedWorker() && s.id && (s.registrationKind === "tickets" || s.registrationKind === "rsvp")) {
671
+ // One signed-in open at a time: the host keeps a single one-time ticket, so a second tap would
672
+ // replace the first tap's ticket and drop that tap into the signed-OUT visitor page below.
673
+ if (hostedOpenInFlight) return s;
674
+ hostedOpenInFlight = true;
675
+ let opened: boolean | "withdrawn";
676
+ try {
677
+ opened = await hostedSitePageOpener({ page: "event", eventId: s.id })(async () => true);
678
+ } finally {
679
+ hostedOpenInFlight = false;
680
+ }
681
+ if (opened === true) {
682
+ return derive({
683
+ ...s,
684
+ actionMessage: s.registrationKind === "tickets"
685
+ ? copyParam("event.checkoutOpened", "Complete checkout in the browser to get your tickets.")
686
+ : "",
687
+ });
688
+ }
689
+ }
661
690
  if (!s.registrationUrl) {
662
691
  return derive({ ...s, actionMessage: copyParam("event.registrationOnPage", "Registration for this event happens on the event page.") });
663
692
  }
@@ -1,8 +1,12 @@
1
- // Hosted checkout (#4089 B3). A Branded Lite hosted in a host shell (Spaces)
2
- // pays as the member the host lent it: Headless `createRedirectSession` does not take a Member
3
- // instance, so the HOST opens the site's checkout signed in as that member. The worker names only
4
- // its ecom checkout; the host reads Wix's checkout URL, mints a one-time member session and opens
5
- // the site's `createSessionCookie` → checkout. The session token never reaches this worker.
1
+ // Hosted checkout. A Branded Lite hosted in a host shell (Spaces) pays as the member the host
2
+ // lent it: Headless `createRedirectSession` does not take a Member instance, so the HOST opens the site's
3
+ // checkout signed in as that member. The worker names only its ecom checkout; the host reads Wix's
4
+ // checkout URL, mints a one-time member session and opens the site's `createSessionCookie` → checkout.
5
+ // The session token never reaches this worker.
6
+ //
7
+ // Classic Wix Events tickets and classic Pricing Plans have no ecom checkout: for those the
8
+ // worker names the page by id (`himi.host.memberSitePage`) and the host opens the site's own event or
9
+ // plans page the same way, signed in — where the member picks and pays on the site.
6
10
 
7
11
  import { dispatch } from "@himalaya/dispatch";
8
12
  import { hostSiteFromInput } from "./branded-lite-hosted.js";
@@ -13,6 +17,12 @@ import { hostSiteFromInput } from "./branded-lite-hosted.js";
13
17
  * URL (which carries the member's session token) stays in the host; only the ticket crosses here. */
14
18
  export const HOSTED_CHECKOUT_ACTION = "himi.host.memberCheckout";
15
19
  export const HOSTED_CHECKOUT_OPEN_ACTION = "himi.host.memberCheckout.open";
20
+ /** Prepares a site PAGE (not an ecom checkout) the same way; opened with [HOSTED_CHECKOUT_OPEN_ACTION]. */
21
+ export const HOSTED_SITE_PAGE_ACTION = "himi.host.memberSitePage";
22
+
23
+ /** A site page a hosted worker may ask its host for — ids only, never a URL: the host reads the URL
24
+ * from Wix itself and decides whether the member may be signed in there. */
25
+ export type HostedSitePage = { page: "event"; eventId: string } | { page: "plans"; planId?: string };
16
26
 
17
27
  /** Whether this worker runs inside a host that opened a site for it. */
18
28
  export function isHostedWorker(): boolean {
@@ -20,14 +30,14 @@ export function isHostedWorker(): boolean {
20
30
  }
21
31
 
22
32
  type HostAnswer = { opened?: unknown; ticket?: unknown; payload?: { opened?: unknown; ticket?: unknown } } | null;
33
+ type HostOpener = (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn">;
23
34
 
24
- /** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
25
- * lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
26
- * opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
27
- export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () => Promise<boolean>) => Promise<boolean | "withdrawn"> {
35
+ /** Prepare with [action] [params] → re-read the guard → open by ticket. A host without the verbs, or one
36
+ * that refused (Wix named no page, an unsafe URL, no account session), → false, never a throw. */
37
+ function hostOpener(action: string, params: Record<string, string>): HostOpener {
28
38
  return async (stillMayCharge) => {
29
39
  try {
30
- const prepared = await dispatch(HOSTED_CHECKOUT_ACTION, { checkoutId }) as HostAnswer;
40
+ const prepared = await dispatch(action, params) as HostAnswer;
31
41
  const ticket = prepared?.ticket ?? prepared?.payload?.ticket;
32
42
  if (typeof ticket !== "string" || !ticket) return false;
33
43
  // An unopened ticket simply expires in the host: nothing to cancel.
@@ -40,3 +50,19 @@ export function hostedCheckoutOpener(checkoutId: string): (stillMayCharge: () =>
40
50
  }
41
51
  };
42
52
  }
53
+
54
+ /** `payCashierOrder`'s `openCashier` for a hosted app: the host prepares [checkoutId]'s checkout as the
55
+ * lent member, the caller's guard is re-read, and only then does the host open it. Resolves whether it
56
+ * opened ("withdrawn" when the guard said no); a host without the verbs (or that refused) → false. */
57
+ export function hostedCheckoutOpener(checkoutId: string): HostOpener {
58
+ return hostOpener(HOSTED_CHECKOUT_ACTION, { checkoutId });
59
+ }
60
+
61
+ /** The site's own [page] opened by the host, signed in as the lent member where the host deems it safe
62
+ * (a visitor otherwise) — the same two steps and answers as [hostedCheckoutOpener]. */
63
+ export function hostedSitePageOpener(page: HostedSitePage): HostOpener {
64
+ const params: Record<string, string> = page.page === "event"
65
+ ? { page: "event", eventId: page.eventId }
66
+ : { page: "plans", ...(page.planId ? { planId: page.planId } : {}) };
67
+ return hostOpener(HOSTED_SITE_PAGE_ACTION, params);
68
+ }