@wix/himalaya-cli 0.1451.0 → 0.1453.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.
@@ -4743,6 +4743,12 @@
4743
4743
  "source": "core/sdk/webview/src/index.ts",
4744
4744
  "description": "Tier-5 typed binding for the page ↔ worker message channel of a worker-backed webview screen (Part 2 / RFC 0025 composition).",
4745
4745
  "api": [
4746
+ {
4747
+ "name": "\"./dispatcher.js\"",
4748
+ "kind": "reexport",
4749
+ "jsdoc": "",
4750
+ "decl": "export {\n PAGE_HANDSHAKE_CAPABILITY,\n PAGE_HANDSHAKE_METHOD,\n type ExposedPageCapability,\n type PageCapabilityRequest,\n type PageCapabilityResponse,\n type PageConsoleEvent,\n type PageCtx,\n type PageLifecycleEvent,\n type PageRequestHandler,\n type PageStream,\n} from \"./dispatcher.js\";"
4751
+ },
4746
4752
  {
4747
4753
  "name": "postToPage",
4748
4754
  "kind": "function",
@@ -4752,43 +4758,43 @@
4752
4758
  {
4753
4759
  "name": "onPageMessage",
4754
4760
  "kind": "function",
4755
- "jsdoc": "/**\n * Register a handler for messages the page posts via `window.HimalayaWorker.post(msg)`.\n * The latest registered handler wins. Call this once during the bundle's setup.\n */",
4756
- "decl": "export function onPageMessage(cb: (msg: unknown) => void): void;"
4761
+ "jsdoc": "/**\n * Register a handler for messages the page posts via `window.HimalayaWorker.post(msg)` that no\n * responder (`exposeCapabilitiesToPage`, `onPageRequest`) claims. The latest registered handler\n * wins. `instanceId` is the embedded WebView the host says the message came from (`\"\"` for the\n * full-screen page). Call this once during the bundle's setup.\n */",
4762
+ "decl": "export function onPageMessage(cb: (msg: unknown, instanceId: string) => void): void;"
4757
4763
  },
4758
4764
  {
4759
- "name": "PageCapabilityRequest",
4760
- "kind": "interface",
4761
- "jsdoc": "",
4762
- "decl": "export interface PageCapabilityRequest {\n v: 1;\n id: string;\n capability: string;\n method: string;\n args: unknown[];\n}"
4765
+ "name": "onPageRequest",
4766
+ "kind": "function",
4767
+ "jsdoc": "/**\n * Answer page requests `call(namespace, method, args)` from `@wix/himalaya/webview-page` (or\n * `himalaya.<namespace>.<method>(…)`) with worker code. A handler gets `(args, ctx)` and returns a value or a promise; a throw answers\n * `ok: false`. To stream, return `openPageStream(ctx.instanceId, ctx.requestId)`.\n *\n * Throws when `namespace` is `\"himalaya\"`, already registered, already exposed, or not\n * `/^[a-z][a-z0-9_]{0,63}$/`. A method the namespace lacks answers `code: \"unknown_method\"`.\n */",
4768
+ "decl": "export function onPageRequest(namespace: string, handlers: Readonly<Record<string, PageRequestHandler>>): void;"
4763
4769
  },
4764
4770
  {
4765
- "name": "PageCapabilityResponse",
4766
- "kind": "interface",
4767
- "jsdoc": "",
4768
- "decl": "export interface PageCapabilityResponse {\n v: 1;\n id: string;\n ok: boolean;\n result?: unknown;\n error?: { message: string; code?: string };\n}"
4771
+ "name": "emitToPage",
4772
+ "kind": "function",
4773
+ "jsdoc": "/**\n * Push an event to one page instance, or `\"*\"` for every instance that has sent a message or\n * mounted and not unmounted. `event` is `\"<namespace>.<event>\"`, split on the first dot; the page\n * receives it through `himalaya.<namespace>.on(\"<event>\", cb)`.\n */",
4774
+ "decl": "export function emitToPage(instanceId: string, event: `${string}.${string}`, data: unknown): void;"
4769
4775
  },
4770
4776
  {
4771
- "name": "PAGE_HANDSHAKE_CAPABILITY",
4772
- "kind": "const",
4773
- "jsdoc": "/** `call(\"himalaya\", \"capabilities\")` answers `{ v: 1, capabilities: { <id>: [<method>…] } }`:\n * exactly the allowlist this worker was given. Reserved, so no real capability can take it. */",
4774
- "decl": "export const PAGE_HANDSHAKE_CAPABILITY = \"himalaya\";"
4777
+ "name": "openPageStream",
4778
+ "kind": "function",
4779
+ "jsdoc": "/**\n * Open a stream answer for an in-flight `onPageRequest` call. Return the handle from the handler;\n * the page then iterates the chunks. After `end`, `error`, or a page cancel, `next` is a no-op.\n */",
4780
+ "decl": "export function openPageStream(instanceId: string, requestId: string): PageStream;"
4775
4781
  },
4776
4782
  {
4777
- "name": "PAGE_HANDSHAKE_METHOD",
4778
- "kind": "const",
4779
- "jsdoc": "",
4780
- "decl": "export const PAGE_HANDSHAKE_METHOD = \"capabilities\";"
4783
+ "name": "onPageLifecycle",
4784
+ "kind": "function",
4785
+ "jsdoc": "/**\n * Observe host lifecycle envelopes (`mounted | loaded | active | inactive | crashed | reloaded |\n * navBlocked | unmounted`). They arrive only on the host's lifecycle primitive, never from a page.\n * On a runtime without instance support, nothing is delivered. Returns an unsubscribe function.\n */",
4786
+ "decl": "export function onPageLifecycle(cb: (e: PageLifecycleEvent) => void): () => void;"
4781
4787
  },
4782
4788
  {
4783
- "name": "ExposedPageCapability",
4784
- "kind": "interface",
4785
- "jsdoc": "",
4786
- "decl": "export interface ExposedPageCapability {\n module: Record<string, unknown>;\n methods: readonly string[];\n}"
4789
+ "name": "onPageConsole",
4790
+ "kind": "function",
4791
+ "jsdoc": "/**\n * Observe `console_forwarding` output of the embedded pages. UNTRUSTED page data: a page can forge\n * these, so never treat them as host events. Returns an unsubscribe function.\n */",
4792
+ "decl": "export function onPageConsole(cb: (e: PageConsoleEvent) => void): () => void;"
4787
4793
  },
4788
4794
  {
4789
4795
  "name": "exposeCapabilitiesToPage",
4790
4796
  "kind": "function",
4791
- "jsdoc": "/**\n * Expose selected typed @himalaya/* modules to the hosting page.\n *\n * The worker author must pass both the imported module and its explicit verb allowlist:\n *\n * import * as health from \"@himalaya/health\";\n * exposeCapabilitiesToPage({\n * health: { module: health, methods: [\"availability\", \"query\"] },\n * });\n *\n * The bundle still has to declare `webview` plus every exposed capability. Importing only\n * selected modules keeps native permissions and `himi dist` additions opt-in.\n */",
4797
+ "jsdoc": "/**\n * Expose selected typed @himalaya/* modules to the hosting page.\n *\n * The worker author must pass both the imported module and its explicit verb allowlist:\n *\n * import * as health from \"@himalaya/health\";\n * exposeCapabilitiesToPage({\n * health: { module: health, methods: [\"availability\", \"query\"] },\n * });\n *\n * The bundle still has to declare `webview` plus every exposed capability. Importing only\n * selected modules keeps native permissions and `himi dist` additions opt-in. The page handshake\n * (`call(\"himalaya\", \"capabilities\")`) answers this allowlist plus every `onPageRequest` namespace.\n */",
4792
4798
  "decl": "export function exposeCapabilitiesToPage(\n capabilities: Readonly<Record<string, ExposedPageCapability>>,\n): void;"
4793
4799
  },
4794
4800
  {
@@ -4811,52 +4817,46 @@
4811
4817
  "decl": "export type { HimalayaPageCapabilities, HimalayaPageSdk } from \"./capabilities.generated.js\";"
4812
4818
  },
4813
4819
  {
4814
- "name": "WebViewPageRequest",
4815
- "kind": "interface",
4816
- "jsdoc": "",
4817
- "decl": "export interface WebViewPageRequest {\n v: 1;\n id: string;\n capability: string;\n method: string;\n args: unknown[];\n}"
4818
- },
4819
- {
4820
- "name": "WebViewPageError",
4821
- "kind": "interface",
4822
- "jsdoc": "",
4823
- "decl": "export interface WebViewPageError {\n message: string;\n code?: string;\n}"
4824
- },
4825
- {
4826
- "name": "WebViewPageResponse",
4827
- "kind": "interface",
4820
+ "name": "\"./client.js\"",
4821
+ "kind": "reexport",
4828
4822
  "jsdoc": "",
4829
- "decl": "export interface WebViewPageResponse {\n v: 1;\n id: string;\n ok: boolean;\n result?: unknown;\n error?: WebViewPageError;\n}"
4823
+ "decl": "export type {\n PageEventHandler,\n WebViewPageError,\n WebViewPageEvent,\n WebViewPageRequest,\n WebViewPageResponse,\n WebViewPageStreamEnvelope,\n} from \"./client.js\";"
4830
4824
  },
4831
4825
  {
4832
4826
  "name": "call",
4833
4827
  "kind": "function",
4834
- "jsdoc": "/** Invoke one method through the companion worker. Primarily useful to generated bindings.\n * A call that shows native UI (a permission prompt, the share sheet) answers only when the user\n * is done with it: pass a long `timeoutMs` (e.g. 120_000) rather than the 30-second default. */",
4828
+ "jsdoc": "/** Invoke one method through the companion worker. Primarily useful to generated bindings.\n * A call that shows native UI (a permission prompt, the share sheet) answers only when the user\n * is done with it: pass a long `timeoutMs` (e.g. 120_000) rather than the 30-second default.\n * A method the worker answers as a stream resolves to an `AsyncIterable` of its chunks:\n * `for await (const chunk of await call(…))`; `break` cancels it on the worker. */",
4835
4829
  "decl": "export function call<T>(\n capability: string,\n method: string,\n args: unknown[] = [],\n timeoutMs = 30_000,\n): Promise<T>;"
4836
4830
  },
4831
+ {
4832
+ "name": "on",
4833
+ "kind": "function",
4834
+ "jsdoc": "/** Subscribe to `namespace.event`, pushed by the worker's `emitToPage(iid, \"<namespace>.<event>\", data)`.\n * Same as `himalaya.<namespace>.on(event, handler)`. Returns the unsubscribe function. */",
4835
+ "decl": "export function on(namespace: string, event: string, handler: PageEventHandler): () => void;"
4836
+ },
4837
4837
  {
4838
4838
  "name": "enabledCapabilities",
4839
4839
  "kind": "function",
4840
4840
  "jsdoc": "/**\n * The capabilities and methods this page's companion worker answers, as `{ <id>: [<method>…] }`;\n * `null` outside the app, when the app is older than the handshake, or nothing answered in\n * `timeoutMs`. A list is remembered (and returned at once, whatever `timeoutMs`); a miss is not,\n * because a worker that is still booting drops the first messages.\n */",
4841
- "decl": "export async function enabledCapabilities(timeoutMs = 3_000): Promise<Record<string, string[]> | null>;"
4841
+ "decl": "export function enabledCapabilities(timeoutMs = 3_000): Promise<Record<string, string[]> | null>;"
4842
4842
  },
4843
4843
  {
4844
4844
  "name": "isEnabled",
4845
4845
  "kind": "function",
4846
4846
  "jsdoc": "/** Whether the companion worker lets this page call `capability.method`, read from\n * enabledCapabilities() — nothing is called. `false` when there is no list. */",
4847
- "decl": "export async function isEnabled(capability: string, method: string, timeoutMs = 3_000): Promise<boolean>;"
4847
+ "decl": "export function isEnabled(capability: string, method: string, timeoutMs = 3_000): Promise<boolean>;"
4848
4848
  },
4849
4849
  {
4850
4850
  "name": "probe",
4851
4851
  "kind": "function",
4852
- "jsdoc": "/** For an app older than the handshake: CALL `capability.method` and report whether it answered\n * (even with an error). It runs the method, so pass only a read-only one. */",
4853
- "decl": "export async function probe(capability: string, method: string, args: unknown[] = [], timeoutMs = 3_000): Promise<boolean>;"
4852
+ "jsdoc": "/** For an app older than the handshake: CALL `capability.method` and report whether it answered\n * (even with an error other than `unknown_method`). It runs the method, so pass only a read-only one. */",
4853
+ "decl": "export function probe(capability: string, method: string, args: unknown[] = [], timeoutMs = 3_000): Promise<boolean>;"
4854
4854
  },
4855
4855
  {
4856
4856
  "name": "himalaya",
4857
4857
  "kind": "const",
4858
- "jsdoc": "/**\n * Typed SDK proxy for every request/response capability in CAPABILITY_REGISTRY.\n * A namespace is usable only when this screen's companion worker explicitly imported and\n * exposed it and the bundle + host both declared it.\n */",
4859
- "decl": "export const himalaya = new Proxy({ version: 1 as const }, {\n get(target, property) {\n if (property === \"version\") return target.version;\n if (typeof property !== \"string\" || property === \"then\") return undefined;\n return pageMember(property, []);\n },\n}) as HimalayaPageSdk;"
4858
+ "jsdoc": "/**\n * Typed SDK proxy for every request/response capability in CAPABILITY_REGISTRY.\n * A namespace is usable only when this screen's companion worker explicitly imported and\n * exposed it and the bundle + host both declared it. Built by the same `merge` a host uses for the\n * injected client, so `himalaya.call/on/probe/…` are the client's members here too.\n */",
4859
+ "decl": "export const himalaya = client.merge({}) as HimalayaPageSdk;"
4860
4860
  },
4861
4861
  {
4862
4862
  "name": "health",
@@ -79,7 +79,7 @@ type Bridge = { post(message: unknown): boolean; onMessage(handler: (message: un
79
79
  type Reply = { v: 1; id: string; ok: boolean; result?: unknown; error?: { message: string; code?: string } };
80
80
 
81
81
  export class HimalayaError extends Error {
82
- /** "not_in_app" | "bridge_refused" | "timeout", or the capability's own code (e.g. "not_authorized"). */
82
+ /** "not_in_app" | "bridge_refused" | "timeout" | "unknown_method", or the capability's own code (e.g. "not_authorized"). */
83
83
  readonly code: string;
84
84
  constructor(message: string, code: string) {
85
85
  super(message);
@@ -224,8 +224,9 @@ export async function probe(capability: string, method: string, args: unknown[]
224
224
  await call(capability, method, args, timeoutMs);
225
225
  return true;
226
226
  } catch (error) {
227
- // Only an answer from the app (even an error) proves the method is enabled.
228
- return !(error instanceof HimalayaError && ["timeout", "not_in_app", "bridge_refused"].includes(error.code));
227
+ // Only an answer from the app (even an error) proves the method is enabled — except the app
228
+ // saying it has no such method.
229
+ return !(error instanceof HimalayaError && ["timeout", "not_in_app", "bridge_refused", "unknown_method"].includes(error.code));
229
230
  }
230
231
  }
231
232
 
@@ -85,6 +85,104 @@ Do not build one worker that imports every SDK. Static bundle scanning would cor
85
85
  that the release uses every capability, forcing unrelated permissions and native modules into the
86
86
  binary. One app-specific worker imports exactly the app’s selected subset.
87
87
 
88
+ ## Custom namespaces, events and streams
89
+
90
+ One worker can serve any number of embedded pages at once. Beside `exposeCapabilitiesToPage`, the
91
+ worker answers its own namespaces, pushes events and streams results; all of them share one
92
+ internal dispatcher, so registering one never replaces another (and `onPageMessage` still receives
93
+ every message none of them claims, including a handler registered directly on
94
+ `himalaya.webview.onPageMessage` after `@himalaya/webview` loaded).
95
+
96
+ ```ts
97
+ import { emitToPage, onPageLifecycle, onPageRequest, openPageStream } from "@himalaya/webview";
98
+
99
+ onPageRequest("nano", {
100
+ // (args, ctx) → value | promise. ctx = { instanceId, requestId, active }.
101
+ "location.getCurrentLocation": async () => ({ lat: 32.08, lng: 34.78 }),
102
+ // Return a stream handle to answer with many chunks.
103
+ "steps.watch": (_args, ctx) => {
104
+ const stream = openPageStream(ctx.instanceId, ctx.requestId);
105
+ const timer = setInterval(() => stream.next({ steps: Math.random() }), 1000);
106
+ stream.onCancel(() => clearInterval(timer)); // the page broke out of its loop
107
+ return stream;
108
+ },
109
+ });
110
+
111
+ emitToPage("*", "nano.theme.changed", { dark: true }); // or one instance id
112
+ const off = onPageLifecycle(({ instanceId, state }) => console.log(instanceId, state)); // mounted … unmounted
113
+ ```
114
+
115
+ - `ctx.instanceId` is the WebView the **host** says the request came from; a page-sent `iid` is
116
+ ignored. `ctx.active` is live: `false` between `inactive` and `active` lifecycle events.
117
+ - A namespace — and an exposed capability id — follows the capability-id pattern
118
+ (`/^[a-z][a-z0-9_]{0,63}$/`). Registering it twice, registering `himalaya`, or a name already
119
+ exposed throws. So do names the page's `himalaya.<name>` cannot reach: `call`, `on`, `probe`,
120
+ `version`, `then`, `constructor` and `general`, a method named `on`, and a method with a `then`,
121
+ `toString` or `valueOf` segment. The handshake lists custom namespaces beside exposed capabilities.
122
+ - A request whose page unmounted, reloaded or crashed before it answered is not answered at all:
123
+ the instance id may already belong to a new document. A `reloaded` right after `crashed` counts
124
+ as one boundary; a `mounted` or `loaded` in between ends that pair (so a host that remounts after
125
+ a crash delivers `reloaded` before the new document's `loaded`). A message still queued when
126
+ its instance unmounted reaches `onPageMessage` but revives nothing, and its request is not
127
+ answered; the instance is back once it mounts again.
128
+ - A method a registered namespace lacks answers at once with
129
+ `{ ok: false, error: { code: "unknown_method" } }` — the page does not wait out a timeout, and
130
+ `probe()` reads it as "not enabled". A request for a capability that is neither exposed nor
131
+ registered is not the dispatcher's: it reaches `onPageMessage` (the bundle's own protocol) and is
132
+ otherwise unanswered, because pages built against an older `probe()` read any answer as
133
+ "enabled". A method outside an allowlist and unknown methods on the reserved `himalaya` name also
134
+ stay unanswered.
135
+ - `emitToPage(iid, "<ns>.<event>", data)` splits on the first dot; `"*"` reaches every instance
136
+ that has sent a message or mounted and has not unmounted (on a single-page runtime, its page).
137
+ - A stream answers `{ v: 1, id, ok: true, stream: true }`, then `{ v: 1, sid, chunk }`… and
138
+ `{ v: 1, sid, end: true }` or `{ v: 1, sid, error }`; `sid` is the request id. After `end`,
139
+ `error`, a page cancel, or the instance unmounting, reloading or crashing, `next` is a no-op.
140
+ `onCancel` also runs when a handler opens a stream and then returns something else or throws,
141
+ and at once if it is registered after the stream was abandoned. `next(undefined)` sends `null`.
142
+ - `onPageLifecycle` envelopes come only from the host's lifecycle primitive; one arriving as a page
143
+ message is never treated as lifecycle. Of the page's `{ v: 1, sys }` messages the dispatcher keeps
144
+ only `cancel` and `console`; any other `sys` reaches `onPageMessage`. `onPageConsole` surfaces `console_forwarding` output — **untrusted** page
145
+ data the page can forge; never treat it as a host event.
146
+ - On a runtime without instance support (`himalaya.webview.onPageLifecycle` absent) every message
147
+ is instance `""`, lifecycle never fires, and posts to any other instance are dropped. Such a host
148
+ does not report a page reload either, so a stream there stops only when the page cancels it:
149
+ bound the producer (a count or a deadline) rather than relying on `onCancel` alone.
150
+ - A page `probe()` of a streamed method cancels the stream it started.
151
+
152
+ On the page:
153
+
154
+ ```ts
155
+ import { call, on } from "@wix/himalaya/webview-page";
156
+
157
+ declare function applyTheme(theme: unknown): void;
158
+ declare function render(reading: { steps: number }): void;
159
+
160
+ // A typed capability has the same method: himalaya.<capability>.on("<event>", cb).
161
+ const off = on("nano", "theme.changed", (data) => applyTheme(data));
162
+ let readings = 0;
163
+ for await (const reading of await call<AsyncIterable<{ steps: number }>>("nano", "steps.watch")) {
164
+ render(reading);
165
+ if (++readings === 10) break; // sends { v: 1, sys: "cancel", sid } — the worker's onCancel runs
166
+ }
167
+ off();
168
+ ```
169
+
170
+ A stream `error` envelope throws from the loop. The page accepts only `v: 1` envelopes, and
171
+ responses only for a request id it sent.
172
+
173
+ **Injected client (planned host behaviour).** No host injects it yet: today a page imports
174
+ `@wix/himalaya/webview-page`. The native and web hosts are planned to inject the same client at
175
+ document start for worker-bridged WebViews, so inline HTML will need no import.
176
+ `PAGE_CLIENT_SOURCE` (`@himalaya/webview-page/page-client`, generated from `src/client.ts` by
177
+ `scripts/generate-page-client.ts`) is a script that defines no global and evaluates to
178
+ `createPageClient(getBridge)`. The host's bridge shim defines `window.himalaya` non-writable, so
179
+ the client is to be merged into the shim's own object before it is frozen:
180
+ `client.merge({ general: … })` adds `call`, `on`, `enabledCapabilities`, `isEnabled`, `probe` and
181
+ `version`, freezes the object, and resolves any other name to a namespace proxy
182
+ (`himalaya.nano.on(…)`, `himalaya.nano.location.getCurrentLocation()`). The package's own
183
+ `himalaya` export is built the same way, so both surfaces have the same members. Coercing a
184
+ namespace to a string (`` `${himalaya.nano}` ``) sends nothing to the worker.
185
+
88
186
  ## Brandy
89
187
 
90
188
  Name the capabilities; Brandy supplies the companion worker, so neither the page author nor the
@@ -135,9 +233,10 @@ Base44 is accepted only when the same Brandy contract contains a Base44 binding.
135
233
  - Errors preserve a typed `code` when the SDK provides one.
136
234
  - The page cannot use raw `capability.*` messages: Brandy site descriptors keep
137
235
  `capabilitiesRequired: []`; native calls go through the companion worker.
138
- - v1 is request/response. SDK helpers returning `AsyncIterable`, accepting JavaScript callbacks,
139
- or depending on worker action names need a page-specific adapter built from their lower-level
140
- start/next/stop verbs. Do not expose such a helper unchanged.
236
+ - `exposeCapabilitiesToPage` is request/response. SDK helpers returning `AsyncIterable`, accepting
237
+ JavaScript callbacks, or depending on worker action names need an `onPageRequest` adapter
238
+ (a stream or `emitToPage` events) built from their lower-level start/next/stop verbs. Do not
239
+ expose such a helper unchanged.
141
240
 
142
241
  The package source includes complete `health-page.ts` and `health-worker.ts` examples matching the
143
242
  snippets above.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1451.0",
3
+ "version": "0.1453.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": "cb3204a26dd5603f14ee56cbf6422f13f5ffa19d1500d2fef6fa3e99"
74
+ "falconPackageHash": "297fee15efe43307e066ee404fb90579178287997e8e01f43d6c03a3"
75
75
  }