@wix/himalaya-cli 0.1489.0 → 0.1491.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.
- package/dist/cli.mjs +11 -6
- package/guides/widgets.md +20 -7
- package/package.json +2 -2
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/community-club-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/events-meetups-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/owner-lite/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/restaurant-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/retail-store-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-hosted-sites.ts +60 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-overview/index.ts +21 -7
package/dist/cli.mjs
CHANGED
|
@@ -130176,7 +130176,7 @@ function stableWidgets(widgets) {
|
|
|
130176
130176
|
if (!widgets || typeof widgets !== "object" || Array.isArray(widgets)) return widgets;
|
|
130177
130177
|
return Object.fromEntries(Object.entries(widgets).map(([id, value]) => {
|
|
130178
130178
|
if (!value || typeof value !== "object" || Array.isArray(value)) return [id, value];
|
|
130179
|
-
const { contentEtag: _regenerated, ...stable } = value;
|
|
130179
|
+
const { contentEtag: _regenerated, family: _listed, ...stable } = value;
|
|
130180
130180
|
return [id, stable];
|
|
130181
130181
|
}));
|
|
130182
130182
|
}
|
|
@@ -135146,7 +135146,9 @@ async function bakeWidgets(fetchPath, putWidget, ownBundles) {
|
|
|
135146
135146
|
await putWidget(widgetId, json8);
|
|
135147
135147
|
widgets[widgetId] = {
|
|
135148
135148
|
contentEtag: snap.contentEtag ?? sha2563(json8),
|
|
135149
|
-
ttlSeconds: snap.refresh?.ttlSeconds ?? 0
|
|
135149
|
+
ttlSeconds: snap.refresh?.ttlSeconds ?? 0,
|
|
135150
|
+
// Listed so a device fetches only the families it draws (RFC 0039 M2); absent in older releases.
|
|
135151
|
+
...typeof snap.family === "string" ? { family: snap.family } : {}
|
|
135150
135152
|
};
|
|
135151
135153
|
}
|
|
135152
135154
|
return { widgets, failures };
|
|
@@ -163610,12 +163612,15 @@ ${releaseId}`;
|
|
|
163610
163612
|
headers: { "content-type": JSON_CT3, etag, "cache-control": DOC_CACHE_CONTROL, "x-himalaya-app": app }
|
|
163611
163613
|
});
|
|
163612
163614
|
}
|
|
163615
|
+
const widgetSite = path2 === "/v1/widgets" || path2.startsWith("/v1/widget/") ? url.searchParams.get("site") : null;
|
|
163616
|
+
if (widgetSite !== null && !/^[A-Za-z0-9_-]{1,64}$/.test(widgetSite)) return json5(400, { error: "invalid_site" });
|
|
163617
|
+
const widgetScope = widgetSite !== null ? { "x-himalaya-widget-scope": "app" } : {};
|
|
163613
163618
|
if (path2 === "/v1/widgets") {
|
|
163614
163619
|
if (!app) return json5(400, { error: "missing_app" });
|
|
163615
163620
|
const m2 = pin ? await pinnedManifest(app, pin) : await manifest(app);
|
|
163616
163621
|
if (!m2) return json5(404, { error: pin ? "release_not_found" : "no_release", app });
|
|
163617
|
-
const widgets = Object.entries(m2.widgets ?? {}).map(([widgetId, meta]) => ({ widgetId, contentEtag: meta.contentEtag }));
|
|
163618
|
-
return json5(200, { widgets });
|
|
163622
|
+
const widgets = Object.entries(m2.widgets ?? {}).map(([widgetId, meta]) => ({ widgetId, contentEtag: meta.contentEtag, ...meta.family ? { family: meta.family } : {} }));
|
|
163623
|
+
return json5(200, { widgets }, widgetScope);
|
|
163619
163624
|
}
|
|
163620
163625
|
const widgetMatch = /^\/v1\/widget\/([^/]+)$/.exec(path2);
|
|
163621
163626
|
if (widgetMatch) {
|
|
@@ -163629,13 +163634,13 @@ ${releaseId}`;
|
|
|
163629
163634
|
const cache = pin ? PINNED_CACHE_CONTROL : `max-age=${meta.ttlSeconds}`;
|
|
163630
163635
|
const vary = pin ? { vary: "x-himalaya-release" } : {};
|
|
163631
163636
|
if (request.headers.get("if-none-match") === etag) {
|
|
163632
|
-
return new Response(null, { status: 304, headers: { etag, "cache-control": cache, ...vary } });
|
|
163637
|
+
return new Response(null, { status: 304, headers: { etag, "cache-control": cache, ...vary, ...widgetScope } });
|
|
163633
163638
|
}
|
|
163634
163639
|
const body = await store.getWidget(app, widgetId);
|
|
163635
163640
|
if (!body) return json5(404, { error: "widget_not_found", widgetId });
|
|
163636
163641
|
return new Response(body, {
|
|
163637
163642
|
status: 200,
|
|
163638
|
-
headers: { "content-type": JSON_CT3, etag, "cache-control": cache, ...vary, "x-himalaya-app": app }
|
|
163643
|
+
headers: { "content-type": JSON_CT3, etag, "cache-control": cache, ...vary, "x-himalaya-app": app, ...widgetScope }
|
|
163639
163644
|
});
|
|
163640
163645
|
}
|
|
163641
163646
|
return json5(404, { error: "not_found" });
|
package/guides/widgets.md
CHANGED
|
@@ -46,13 +46,23 @@ bindings, only **resolved literal values**. Reading the clock is the one allowed
|
|
|
46
46
|
the last produce (see [refresh](#refresh-taps-and-previews)). A `render` that throws, or returns a
|
|
47
47
|
tree with a binding in it, makes that widget fail to produce.
|
|
48
48
|
|
|
49
|
-
###
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
### Live data reaches a widget only through the app on the device
|
|
50
|
+
|
|
51
|
+
The produce above has no credential, so data your worker fetches **cannot** reach a widget through
|
|
52
|
+
`defineWidget`. The one path for live data runs on the device, in the app: the app fetches the
|
|
53
|
+
widget's descriptors, runs their worker **headlessly** with the signed-in session, resolves every
|
|
54
|
+
`$service:` binding to a literal, and publishes the frozen tree itself —
|
|
55
|
+
`WidgetSnapshotSync.publishLocal` on iOS, `WidgetSnapshotSync.publishLocalBlocking` on Android.
|
|
56
|
+
Owner Lite's Today widget works this way (`OwnerLiteHomeWidget`): it refreshes when the app runs or
|
|
57
|
+
the OS grants a background refresh, never on its own. An app that embeds Himalaya apps gets the same
|
|
58
|
+
produce from `HimalayaEmbedSurfaces.publishWidgets` (see
|
|
59
|
+
[embedding → widgets](embedding-sdk.md#home-and-lock-screen-widgets)).
|
|
60
|
+
|
|
61
|
+
`refresh.pushTopic` exists in the type definitions, but nothing consumes it yet: setting it does not
|
|
62
|
+
make the widget update when data changes. Without a device produce, plan the widget around what you
|
|
63
|
+
know when the app is built — a call to action, a constant, or a schedule (see
|
|
64
|
+
[timelines](#content-whose-future-you-know-live-time-and-timelines)) — and let the tap take the user
|
|
65
|
+
into the app, where the live data is.
|
|
56
66
|
|
|
57
67
|
## A widget that stays inside the contract
|
|
58
68
|
|
|
@@ -217,6 +227,9 @@ not, because the car lays out its own rows. It never goes through `defineWidget`
|
|
|
217
227
|
request, not a schedule.
|
|
218
228
|
- **`deepLink`** is where a tap takes the user. Point it at the screen that shows the live version
|
|
219
229
|
of what the widget summarises.
|
|
230
|
+
- **`GET /v1/widgets?site=<metaSiteId>`** (and `/v1/widget/<id>?site=`) is accepted for a host that
|
|
231
|
+
shows one app on many sites. The snapshot is still the app's — nothing is produced per site — and
|
|
232
|
+
the response says so with `x-himalaya-widget-scope: app`.
|
|
220
233
|
- **Preview a widget** while you work: `himi run --watch --content .` serves it at
|
|
221
234
|
`GET /v1/widget/<widget id>`, and `GET /v1/widgets` lists every widget the app produces.
|
|
222
235
|
`himi surfaces --measure-widgets` reports which families your package actually produces.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1491.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": "
|
|
74
|
+
"falconPackageHash": "69ee53fb46bd8285fb455df2eb9a446d954346c0b2f990cfb7e54d09"
|
|
75
75
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The businesses an EMBEDDING HOST says the signed-in owner owns — or null when there is no
|
|
2
|
+
// such host.
|
|
3
|
+
//
|
|
4
|
+
// Standalone, an owner screen lists businesses itself (`querySites`, account level) and reads
|
|
5
|
+
// each one with `site.forSite(msid)`, which stamps `wix-site-id` on that call. Embedded, the
|
|
6
|
+
// host owns site selection: its data source signs every call for the business the user picked
|
|
7
|
+
// and drops a worker-sent `wix-site-id`, so a fan-out read ONE business N times. A host that
|
|
8
|
+
// opts in supplies its own list instead (`HimalayaOwnedSitesDataSource` on iOS / Android, the
|
|
9
|
+
// `ownedSites` prop on web) and answers `himi.wix.ownedSites` with it; the embed then routes a
|
|
10
|
+
// `site.forSite(msid)` call to the host's per-site call for any id on that list and refuses
|
|
11
|
+
// every other id. So a worker reads the list here and keeps using `site.forSite` unchanged.
|
|
12
|
+
//
|
|
13
|
+
// A rejection is "not hosted": a standalone shell has no handler for the verb, and an older embed
|
|
14
|
+
// predates it. Callers then do exactly what they did before. A host that opted in but cannot list
|
|
15
|
+
// answers `{ error }` instead, which THROWS here: falling back to `querySites` would read the
|
|
16
|
+
// account through the host's single-site signer and fan out to ids the host then refuses.
|
|
17
|
+
|
|
18
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
19
|
+
|
|
20
|
+
export const OWNED_SITES_ACTION = "himi.wix.ownedSites";
|
|
21
|
+
|
|
22
|
+
export interface HostedSite {
|
|
23
|
+
/** The business's metaSiteId — what `site.forSite` takes. */
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
logoUrl?: string;
|
|
27
|
+
color?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function text(v: unknown): string {
|
|
31
|
+
return typeof v === "string" ? v.trim() : "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The host's list, in the host's order; [] is a real answer (an owner with no businesses).
|
|
35
|
+
* null when the verb is unanswered or the answer is not a list. Throws the host's `{ error }`. */
|
|
36
|
+
export async function hostOwnedSites(): Promise<HostedSite[] | null> {
|
|
37
|
+
let value: unknown;
|
|
38
|
+
try {
|
|
39
|
+
value = await dispatch(OWNED_SITES_ACTION);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Native hosts resolve the payload itself; some web shells wrap it as `{ payload }`.
|
|
44
|
+
const body = value as { sites?: unknown; error?: unknown; payload?: { sites?: unknown; error?: unknown } } | null;
|
|
45
|
+
const error = body?.error ?? body?.payload?.error;
|
|
46
|
+
if (typeof error === "string") throw new Error(error || "The host could not list your businesses");
|
|
47
|
+
const raw = body?.sites ?? body?.payload?.sites;
|
|
48
|
+
if (!Array.isArray(raw)) return null;
|
|
49
|
+
const sites: HostedSite[] = [];
|
|
50
|
+
for (const entry of raw) {
|
|
51
|
+
const e = (entry ?? {}) as Record<string, unknown>;
|
|
52
|
+
const id = text(e.id);
|
|
53
|
+
if (!id) continue;
|
|
54
|
+
const site: HostedSite = { id, name: text(e.name) };
|
|
55
|
+
if (text(e.logoUrl)) site.logoUrl = text(e.logoUrl);
|
|
56
|
+
if (text(e.color)) site.color = text(e.color);
|
|
57
|
+
sites.push(site);
|
|
58
|
+
}
|
|
59
|
+
return sites;
|
|
60
|
+
}
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
// or one whose mint fails, resolves to `null` and renders as "—" beside the others rather
|
|
23
23
|
// than throwing the whole screen into its error state. A rollup that disappears because one
|
|
24
24
|
// of five businesses is unreachable is worse than one that says so in a row.
|
|
25
|
+
//
|
|
26
|
+
// EMBEDDED, THE HOST NAMES THE BUSINESSES. A host that owns site selection (Symphony's Manage
|
|
27
|
+
// Wix) signs each call itself and would drop the per-call `wix-site-id`, so it supplies the
|
|
28
|
+
// owner's sites instead (`hostOwnedSites`) and resolves each `site.forSite(msid)` call against
|
|
29
|
+
// that list. The list replaces rung 1 only; the fan-out below is identical either way.
|
|
25
30
|
|
|
26
31
|
import { defineService } from "@himalaya/state";
|
|
27
32
|
import { querySites, type WixSite } from "@himalaya/wix-site-list";
|
|
@@ -31,6 +36,7 @@ import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback } from "../_share
|
|
|
31
36
|
import { DEMO_SITES, DEMO_NOTE } from "../_shared/owner-preview-demo.js";
|
|
32
37
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
33
38
|
import { pickSite, pickedSite } from "../_shared/owner-site-pick.js";
|
|
39
|
+
import { hostOwnedSites } from "../_shared/owner-hosted-sites.js";
|
|
34
40
|
|
|
35
41
|
/** Mirrors the Home dashboard's "needs attention" definition, so a number here means the
|
|
36
42
|
* same thing it means after the owner drills into that business. */
|
|
@@ -176,16 +182,24 @@ async function attentionCount(msid: string): Promise<number | null> {
|
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
/** Rung 1: the host's list when embedded, else the account's own (`querySites`). */
|
|
186
|
+
async function listBusinesses(): Promise<Array<{ id: string; name: string }>> {
|
|
187
|
+
const unnamed = () => copyParam("ownerOverview.unnamedBusiness", "Business");
|
|
188
|
+
const hosted = await hostOwnedSites();
|
|
189
|
+
if (hosted) return hosted.map((h) => ({ id: h.id, name: h.name || unnamed() }));
|
|
190
|
+
const res = await querySites({ query: { cursorPaging: { limit: 50 } } });
|
|
191
|
+
return (res.sites ?? [])
|
|
192
|
+
.filter((r: WixSite) => r?.id)
|
|
193
|
+
.map((r: WixSite) => ({
|
|
194
|
+
id: r.id || "",
|
|
195
|
+
name: r.displayName || (r as { name?: string }).name || unnamed(),
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
|
|
179
199
|
async function loadRollup(): Promise<OverviewState> {
|
|
180
200
|
if (isOwnerPreviewDemo()) return demoRollup();
|
|
181
201
|
try {
|
|
182
|
-
const
|
|
183
|
-
const businesses = (res.sites ?? [])
|
|
184
|
-
.filter((r: WixSite) => r?.id)
|
|
185
|
-
.map((r: WixSite) => ({
|
|
186
|
-
id: r.id || "",
|
|
187
|
-
name: r.displayName || (r as { name?: string }).name || copyParam("ownerOverview.unnamedBusiness", "Business"),
|
|
188
|
-
}));
|
|
202
|
+
const businesses = await listBusinesses();
|
|
189
203
|
if (businesses.length === 0) return shape({ status: "ready", rows: [] });
|
|
190
204
|
|
|
191
205
|
// THE FAN-OUT. Concurrent, one business each, active scope untouched.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The businesses an EMBEDDING HOST says the signed-in owner owns — or null when there is no
|
|
2
|
+
// such host.
|
|
3
|
+
//
|
|
4
|
+
// Standalone, an owner screen lists businesses itself (`querySites`, account level) and reads
|
|
5
|
+
// each one with `site.forSite(msid)`, which stamps `wix-site-id` on that call. Embedded, the
|
|
6
|
+
// host owns site selection: its data source signs every call for the business the user picked
|
|
7
|
+
// and drops a worker-sent `wix-site-id`, so a fan-out read ONE business N times. A host that
|
|
8
|
+
// opts in supplies its own list instead (`HimalayaOwnedSitesDataSource` on iOS / Android, the
|
|
9
|
+
// `ownedSites` prop on web) and answers `himi.wix.ownedSites` with it; the embed then routes a
|
|
10
|
+
// `site.forSite(msid)` call to the host's per-site call for any id on that list and refuses
|
|
11
|
+
// every other id. So a worker reads the list here and keeps using `site.forSite` unchanged.
|
|
12
|
+
//
|
|
13
|
+
// A rejection is "not hosted": a standalone shell has no handler for the verb, and an older embed
|
|
14
|
+
// predates it. Callers then do exactly what they did before. A host that opted in but cannot list
|
|
15
|
+
// answers `{ error }` instead, which THROWS here: falling back to `querySites` would read the
|
|
16
|
+
// account through the host's single-site signer and fan out to ids the host then refuses.
|
|
17
|
+
|
|
18
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
19
|
+
|
|
20
|
+
export const OWNED_SITES_ACTION = "himi.wix.ownedSites";
|
|
21
|
+
|
|
22
|
+
export interface HostedSite {
|
|
23
|
+
/** The business's metaSiteId — what `site.forSite` takes. */
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
logoUrl?: string;
|
|
27
|
+
color?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function text(v: unknown): string {
|
|
31
|
+
return typeof v === "string" ? v.trim() : "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The host's list, in the host's order; [] is a real answer (an owner with no businesses).
|
|
35
|
+
* null when the verb is unanswered or the answer is not a list. Throws the host's `{ error }`. */
|
|
36
|
+
export async function hostOwnedSites(): Promise<HostedSite[] | null> {
|
|
37
|
+
let value: unknown;
|
|
38
|
+
try {
|
|
39
|
+
value = await dispatch(OWNED_SITES_ACTION);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Native hosts resolve the payload itself; some web shells wrap it as `{ payload }`.
|
|
44
|
+
const body = value as { sites?: unknown; error?: unknown; payload?: { sites?: unknown; error?: unknown } } | null;
|
|
45
|
+
const error = body?.error ?? body?.payload?.error;
|
|
46
|
+
if (typeof error === "string") throw new Error(error || "The host could not list your businesses");
|
|
47
|
+
const raw = body?.sites ?? body?.payload?.sites;
|
|
48
|
+
if (!Array.isArray(raw)) return null;
|
|
49
|
+
const sites: HostedSite[] = [];
|
|
50
|
+
for (const entry of raw) {
|
|
51
|
+
const e = (entry ?? {}) as Record<string, unknown>;
|
|
52
|
+
const id = text(e.id);
|
|
53
|
+
if (!id) continue;
|
|
54
|
+
const site: HostedSite = { id, name: text(e.name) };
|
|
55
|
+
if (text(e.logoUrl)) site.logoUrl = text(e.logoUrl);
|
|
56
|
+
if (text(e.color)) site.color = text(e.color);
|
|
57
|
+
sites.push(site);
|
|
58
|
+
}
|
|
59
|
+
return sites;
|
|
60
|
+
}
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
// or one whose mint fails, resolves to `null` and renders as "—" beside the others rather
|
|
23
23
|
// than throwing the whole screen into its error state. A rollup that disappears because one
|
|
24
24
|
// of five businesses is unreachable is worse than one that says so in a row.
|
|
25
|
+
//
|
|
26
|
+
// EMBEDDED, THE HOST NAMES THE BUSINESSES. A host that owns site selection (Symphony's Manage
|
|
27
|
+
// Wix) signs each call itself and would drop the per-call `wix-site-id`, so it supplies the
|
|
28
|
+
// owner's sites instead (`hostOwnedSites`) and resolves each `site.forSite(msid)` call against
|
|
29
|
+
// that list. The list replaces rung 1 only; the fan-out below is identical either way.
|
|
25
30
|
|
|
26
31
|
import { defineService } from "@himalaya/state";
|
|
27
32
|
import { querySites, type WixSite } from "@himalaya/wix-site-list";
|
|
@@ -31,6 +36,7 @@ import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback } from "../_share
|
|
|
31
36
|
import { DEMO_SITES, DEMO_NOTE } from "../_shared/owner-preview-demo.js";
|
|
32
37
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
33
38
|
import { pickSite, pickedSite } from "../_shared/owner-site-pick.js";
|
|
39
|
+
import { hostOwnedSites } from "../_shared/owner-hosted-sites.js";
|
|
34
40
|
|
|
35
41
|
/** Mirrors the Home dashboard's "needs attention" definition, so a number here means the
|
|
36
42
|
* same thing it means after the owner drills into that business. */
|
|
@@ -176,16 +182,24 @@ async function attentionCount(msid: string): Promise<number | null> {
|
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
/** Rung 1: the host's list when embedded, else the account's own (`querySites`). */
|
|
186
|
+
async function listBusinesses(): Promise<Array<{ id: string; name: string }>> {
|
|
187
|
+
const unnamed = () => copyParam("ownerOverview.unnamedBusiness", "Business");
|
|
188
|
+
const hosted = await hostOwnedSites();
|
|
189
|
+
if (hosted) return hosted.map((h) => ({ id: h.id, name: h.name || unnamed() }));
|
|
190
|
+
const res = await querySites({ query: { cursorPaging: { limit: 50 } } });
|
|
191
|
+
return (res.sites ?? [])
|
|
192
|
+
.filter((r: WixSite) => r?.id)
|
|
193
|
+
.map((r: WixSite) => ({
|
|
194
|
+
id: r.id || "",
|
|
195
|
+
name: r.displayName || (r as { name?: string }).name || unnamed(),
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
|
|
179
199
|
async function loadRollup(): Promise<OverviewState> {
|
|
180
200
|
if (isOwnerPreviewDemo()) return demoRollup();
|
|
181
201
|
try {
|
|
182
|
-
const
|
|
183
|
-
const businesses = (res.sites ?? [])
|
|
184
|
-
.filter((r: WixSite) => r?.id)
|
|
185
|
-
.map((r: WixSite) => ({
|
|
186
|
-
id: r.id || "",
|
|
187
|
-
name: r.displayName || (r as { name?: string }).name || copyParam("ownerOverview.unnamedBusiness", "Business"),
|
|
188
|
-
}));
|
|
202
|
+
const businesses = await listBusinesses();
|
|
189
203
|
if (businesses.length === 0) return shape({ status: "ready", rows: [] });
|
|
190
204
|
|
|
191
205
|
// THE FAN-OUT. Concurrent, one business each, active scope untouched.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The businesses an EMBEDDING HOST says the signed-in owner owns — or null when there is no
|
|
2
|
+
// such host.
|
|
3
|
+
//
|
|
4
|
+
// Standalone, an owner screen lists businesses itself (`querySites`, account level) and reads
|
|
5
|
+
// each one with `site.forSite(msid)`, which stamps `wix-site-id` on that call. Embedded, the
|
|
6
|
+
// host owns site selection: its data source signs every call for the business the user picked
|
|
7
|
+
// and drops a worker-sent `wix-site-id`, so a fan-out read ONE business N times. A host that
|
|
8
|
+
// opts in supplies its own list instead (`HimalayaOwnedSitesDataSource` on iOS / Android, the
|
|
9
|
+
// `ownedSites` prop on web) and answers `himi.wix.ownedSites` with it; the embed then routes a
|
|
10
|
+
// `site.forSite(msid)` call to the host's per-site call for any id on that list and refuses
|
|
11
|
+
// every other id. So a worker reads the list here and keeps using `site.forSite` unchanged.
|
|
12
|
+
//
|
|
13
|
+
// A rejection is "not hosted": a standalone shell has no handler for the verb, and an older embed
|
|
14
|
+
// predates it. Callers then do exactly what they did before. A host that opted in but cannot list
|
|
15
|
+
// answers `{ error }` instead, which THROWS here: falling back to `querySites` would read the
|
|
16
|
+
// account through the host's single-site signer and fan out to ids the host then refuses.
|
|
17
|
+
|
|
18
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
19
|
+
|
|
20
|
+
export const OWNED_SITES_ACTION = "himi.wix.ownedSites";
|
|
21
|
+
|
|
22
|
+
export interface HostedSite {
|
|
23
|
+
/** The business's metaSiteId — what `site.forSite` takes. */
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
logoUrl?: string;
|
|
27
|
+
color?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function text(v: unknown): string {
|
|
31
|
+
return typeof v === "string" ? v.trim() : "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The host's list, in the host's order; [] is a real answer (an owner with no businesses).
|
|
35
|
+
* null when the verb is unanswered or the answer is not a list. Throws the host's `{ error }`. */
|
|
36
|
+
export async function hostOwnedSites(): Promise<HostedSite[] | null> {
|
|
37
|
+
let value: unknown;
|
|
38
|
+
try {
|
|
39
|
+
value = await dispatch(OWNED_SITES_ACTION);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Native hosts resolve the payload itself; some web shells wrap it as `{ payload }`.
|
|
44
|
+
const body = value as { sites?: unknown; error?: unknown; payload?: { sites?: unknown; error?: unknown } } | null;
|
|
45
|
+
const error = body?.error ?? body?.payload?.error;
|
|
46
|
+
if (typeof error === "string") throw new Error(error || "The host could not list your businesses");
|
|
47
|
+
const raw = body?.sites ?? body?.payload?.sites;
|
|
48
|
+
if (!Array.isArray(raw)) return null;
|
|
49
|
+
const sites: HostedSite[] = [];
|
|
50
|
+
for (const entry of raw) {
|
|
51
|
+
const e = (entry ?? {}) as Record<string, unknown>;
|
|
52
|
+
const id = text(e.id);
|
|
53
|
+
if (!id) continue;
|
|
54
|
+
const site: HostedSite = { id, name: text(e.name) };
|
|
55
|
+
if (text(e.logoUrl)) site.logoUrl = text(e.logoUrl);
|
|
56
|
+
if (text(e.color)) site.color = text(e.color);
|
|
57
|
+
sites.push(site);
|
|
58
|
+
}
|
|
59
|
+
return sites;
|
|
60
|
+
}
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
// or one whose mint fails, resolves to `null` and renders as "—" beside the others rather
|
|
23
23
|
// than throwing the whole screen into its error state. A rollup that disappears because one
|
|
24
24
|
// of five businesses is unreachable is worse than one that says so in a row.
|
|
25
|
+
//
|
|
26
|
+
// EMBEDDED, THE HOST NAMES THE BUSINESSES. A host that owns site selection (Symphony's Manage
|
|
27
|
+
// Wix) signs each call itself and would drop the per-call `wix-site-id`, so it supplies the
|
|
28
|
+
// owner's sites instead (`hostOwnedSites`) and resolves each `site.forSite(msid)` call against
|
|
29
|
+
// that list. The list replaces rung 1 only; the fan-out below is identical either way.
|
|
25
30
|
|
|
26
31
|
import { defineService } from "@himalaya/state";
|
|
27
32
|
import { querySites, type WixSite } from "@himalaya/wix-site-list";
|
|
@@ -31,6 +36,7 @@ import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback } from "../_share
|
|
|
31
36
|
import { DEMO_SITES, DEMO_NOTE } from "../_shared/owner-preview-demo.js";
|
|
32
37
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
33
38
|
import { pickSite, pickedSite } from "../_shared/owner-site-pick.js";
|
|
39
|
+
import { hostOwnedSites } from "../_shared/owner-hosted-sites.js";
|
|
34
40
|
|
|
35
41
|
/** Mirrors the Home dashboard's "needs attention" definition, so a number here means the
|
|
36
42
|
* same thing it means after the owner drills into that business. */
|
|
@@ -176,16 +182,24 @@ async function attentionCount(msid: string): Promise<number | null> {
|
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
/** Rung 1: the host's list when embedded, else the account's own (`querySites`). */
|
|
186
|
+
async function listBusinesses(): Promise<Array<{ id: string; name: string }>> {
|
|
187
|
+
const unnamed = () => copyParam("ownerOverview.unnamedBusiness", "Business");
|
|
188
|
+
const hosted = await hostOwnedSites();
|
|
189
|
+
if (hosted) return hosted.map((h) => ({ id: h.id, name: h.name || unnamed() }));
|
|
190
|
+
const res = await querySites({ query: { cursorPaging: { limit: 50 } } });
|
|
191
|
+
return (res.sites ?? [])
|
|
192
|
+
.filter((r: WixSite) => r?.id)
|
|
193
|
+
.map((r: WixSite) => ({
|
|
194
|
+
id: r.id || "",
|
|
195
|
+
name: r.displayName || (r as { name?: string }).name || unnamed(),
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
|
|
179
199
|
async function loadRollup(): Promise<OverviewState> {
|
|
180
200
|
if (isOwnerPreviewDemo()) return demoRollup();
|
|
181
201
|
try {
|
|
182
|
-
const
|
|
183
|
-
const businesses = (res.sites ?? [])
|
|
184
|
-
.filter((r: WixSite) => r?.id)
|
|
185
|
-
.map((r: WixSite) => ({
|
|
186
|
-
id: r.id || "",
|
|
187
|
-
name: r.displayName || (r as { name?: string }).name || copyParam("ownerOverview.unnamedBusiness", "Business"),
|
|
188
|
-
}));
|
|
202
|
+
const businesses = await listBusinesses();
|
|
189
203
|
if (businesses.length === 0) return shape({ status: "ready", rows: [] });
|
|
190
204
|
|
|
191
205
|
// THE FAN-OUT. Concurrent, one business each, active scope untouched.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The businesses an EMBEDDING HOST says the signed-in owner owns — or null when there is no
|
|
2
|
+
// such host.
|
|
3
|
+
//
|
|
4
|
+
// Standalone, an owner screen lists businesses itself (`querySites`, account level) and reads
|
|
5
|
+
// each one with `site.forSite(msid)`, which stamps `wix-site-id` on that call. Embedded, the
|
|
6
|
+
// host owns site selection: its data source signs every call for the business the user picked
|
|
7
|
+
// and drops a worker-sent `wix-site-id`, so a fan-out read ONE business N times. A host that
|
|
8
|
+
// opts in supplies its own list instead (`HimalayaOwnedSitesDataSource` on iOS / Android, the
|
|
9
|
+
// `ownedSites` prop on web) and answers `himi.wix.ownedSites` with it; the embed then routes a
|
|
10
|
+
// `site.forSite(msid)` call to the host's per-site call for any id on that list and refuses
|
|
11
|
+
// every other id. So a worker reads the list here and keeps using `site.forSite` unchanged.
|
|
12
|
+
//
|
|
13
|
+
// A rejection is "not hosted": a standalone shell has no handler for the verb, and an older embed
|
|
14
|
+
// predates it. Callers then do exactly what they did before. A host that opted in but cannot list
|
|
15
|
+
// answers `{ error }` instead, which THROWS here: falling back to `querySites` would read the
|
|
16
|
+
// account through the host's single-site signer and fan out to ids the host then refuses.
|
|
17
|
+
|
|
18
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
19
|
+
|
|
20
|
+
export const OWNED_SITES_ACTION = "himi.wix.ownedSites";
|
|
21
|
+
|
|
22
|
+
export interface HostedSite {
|
|
23
|
+
/** The business's metaSiteId — what `site.forSite` takes. */
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
logoUrl?: string;
|
|
27
|
+
color?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function text(v: unknown): string {
|
|
31
|
+
return typeof v === "string" ? v.trim() : "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The host's list, in the host's order; [] is a real answer (an owner with no businesses).
|
|
35
|
+
* null when the verb is unanswered or the answer is not a list. Throws the host's `{ error }`. */
|
|
36
|
+
export async function hostOwnedSites(): Promise<HostedSite[] | null> {
|
|
37
|
+
let value: unknown;
|
|
38
|
+
try {
|
|
39
|
+
value = await dispatch(OWNED_SITES_ACTION);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Native hosts resolve the payload itself; some web shells wrap it as `{ payload }`.
|
|
44
|
+
const body = value as { sites?: unknown; error?: unknown; payload?: { sites?: unknown; error?: unknown } } | null;
|
|
45
|
+
const error = body?.error ?? body?.payload?.error;
|
|
46
|
+
if (typeof error === "string") throw new Error(error || "The host could not list your businesses");
|
|
47
|
+
const raw = body?.sites ?? body?.payload?.sites;
|
|
48
|
+
if (!Array.isArray(raw)) return null;
|
|
49
|
+
const sites: HostedSite[] = [];
|
|
50
|
+
for (const entry of raw) {
|
|
51
|
+
const e = (entry ?? {}) as Record<string, unknown>;
|
|
52
|
+
const id = text(e.id);
|
|
53
|
+
if (!id) continue;
|
|
54
|
+
const site: HostedSite = { id, name: text(e.name) };
|
|
55
|
+
if (text(e.logoUrl)) site.logoUrl = text(e.logoUrl);
|
|
56
|
+
if (text(e.color)) site.color = text(e.color);
|
|
57
|
+
sites.push(site);
|
|
58
|
+
}
|
|
59
|
+
return sites;
|
|
60
|
+
}
|
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
// or one whose mint fails, resolves to `null` and renders as "—" beside the others rather
|
|
23
23
|
// than throwing the whole screen into its error state. A rollup that disappears because one
|
|
24
24
|
// of five businesses is unreachable is worse than one that says so in a row.
|
|
25
|
+
//
|
|
26
|
+
// EMBEDDED, THE HOST NAMES THE BUSINESSES. A host that owns site selection (Symphony's Manage
|
|
27
|
+
// Wix) signs each call itself and would drop the per-call `wix-site-id`, so it supplies the
|
|
28
|
+
// owner's sites instead (`hostOwnedSites`) and resolves each `site.forSite(msid)` call against
|
|
29
|
+
// that list. The list replaces rung 1 only; the fan-out below is identical either way.
|
|
25
30
|
|
|
26
31
|
import { defineService } from "@himalaya/state";
|
|
27
32
|
import { querySites, type WixSite } from "@himalaya/wix-site-list";
|
|
@@ -31,6 +36,7 @@ import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback } from "../_share
|
|
|
31
36
|
import { DEMO_SITES, DEMO_NOTE } from "../_shared/owner-preview-demo.js";
|
|
32
37
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
33
38
|
import { pickSite, pickedSite } from "../_shared/owner-site-pick.js";
|
|
39
|
+
import { hostOwnedSites } from "../_shared/owner-hosted-sites.js";
|
|
34
40
|
|
|
35
41
|
/** Mirrors the Home dashboard's "needs attention" definition, so a number here means the
|
|
36
42
|
* same thing it means after the owner drills into that business. */
|
|
@@ -176,16 +182,24 @@ async function attentionCount(msid: string): Promise<number | null> {
|
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
184
|
|
|
185
|
+
/** Rung 1: the host's list when embedded, else the account's own (`querySites`). */
|
|
186
|
+
async function listBusinesses(): Promise<Array<{ id: string; name: string }>> {
|
|
187
|
+
const unnamed = () => copyParam("ownerOverview.unnamedBusiness", "Business");
|
|
188
|
+
const hosted = await hostOwnedSites();
|
|
189
|
+
if (hosted) return hosted.map((h) => ({ id: h.id, name: h.name || unnamed() }));
|
|
190
|
+
const res = await querySites({ query: { cursorPaging: { limit: 50 } } });
|
|
191
|
+
return (res.sites ?? [])
|
|
192
|
+
.filter((r: WixSite) => r?.id)
|
|
193
|
+
.map((r: WixSite) => ({
|
|
194
|
+
id: r.id || "",
|
|
195
|
+
name: r.displayName || (r as { name?: string }).name || unnamed(),
|
|
196
|
+
}));
|
|
197
|
+
}
|
|
198
|
+
|
|
179
199
|
async function loadRollup(): Promise<OverviewState> {
|
|
180
200
|
if (isOwnerPreviewDemo()) return demoRollup();
|
|
181
201
|
try {
|
|
182
|
-
const
|
|
183
|
-
const businesses = (res.sites ?? [])
|
|
184
|
-
.filter((r: WixSite) => r?.id)
|
|
185
|
-
.map((r: WixSite) => ({
|
|
186
|
-
id: r.id || "",
|
|
187
|
-
name: r.displayName || (r as { name?: string }).name || copyParam("ownerOverview.unnamedBusiness", "Business"),
|
|
188
|
-
}));
|
|
202
|
+
const businesses = await listBusinesses();
|
|
189
203
|
if (businesses.length === 0) return shape({ status: "ready", rows: [] });
|
|
190
204
|
|
|
191
205
|
// THE FAN-OUT. Concurrent, one business each, active scope untouched.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// The businesses an EMBEDDING HOST says the signed-in owner owns — or null when there is no
|
|
2
|
+
// such host.
|
|
3
|
+
//
|
|
4
|
+
// Standalone, an owner screen lists businesses itself (`querySites`, account level) and reads
|
|
5
|
+
// each one with `site.forSite(msid)`, which stamps `wix-site-id` on that call. Embedded, the
|
|
6
|
+
// host owns site selection: its data source signs every call for the business the user picked
|
|
7
|
+
// and drops a worker-sent `wix-site-id`, so a fan-out read ONE business N times. A host that
|
|
8
|
+
// opts in supplies its own list instead (`HimalayaOwnedSitesDataSource` on iOS / Android, the
|
|
9
|
+
// `ownedSites` prop on web) and answers `himi.wix.ownedSites` with it; the embed then routes a
|
|
10
|
+
// `site.forSite(msid)` call to the host's per-site call for any id on that list and refuses
|
|
11
|
+
// every other id. So a worker reads the list here and keeps using `site.forSite` unchanged.
|
|
12
|
+
//
|
|
13
|
+
// A rejection is "not hosted": a standalone shell has no handler for the verb, and an older embed
|
|
14
|
+
// predates it. Callers then do exactly what they did before. A host that opted in but cannot list
|
|
15
|
+
// answers `{ error }` instead, which THROWS here: falling back to `querySites` would read the
|
|
16
|
+
// account through the host's single-site signer and fan out to ids the host then refuses.
|
|
17
|
+
|
|
18
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
19
|
+
|
|
20
|
+
export const OWNED_SITES_ACTION = "himi.wix.ownedSites";
|
|
21
|
+
|
|
22
|
+
export interface HostedSite {
|
|
23
|
+
/** The business's metaSiteId — what `site.forSite` takes. */
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
logoUrl?: string;
|
|
27
|
+
color?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function text(v: unknown): string {
|
|
31
|
+
return typeof v === "string" ? v.trim() : "";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The host's list, in the host's order; [] is a real answer (an owner with no businesses).
|
|
35
|
+
* null when the verb is unanswered or the answer is not a list. Throws the host's `{ error }`. */
|
|
36
|
+
export async function hostOwnedSites(): Promise<HostedSite[] | null> {
|
|
37
|
+
let value: unknown;
|
|
38
|
+
try {
|
|
39
|
+
value = await dispatch(OWNED_SITES_ACTION);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
// Native hosts resolve the payload itself; some web shells wrap it as `{ payload }`.
|
|
44
|
+
const body = value as { sites?: unknown; error?: unknown; payload?: { sites?: unknown; error?: unknown } } | null;
|
|
45
|
+
const error = body?.error ?? body?.payload?.error;
|
|
46
|
+
if (typeof error === "string") throw new Error(error || "The host could not list your businesses");
|
|
47
|
+
const raw = body?.sites ?? body?.payload?.sites;
|
|
48
|
+
if (!Array.isArray(raw)) return null;
|
|
49
|
+
const sites: HostedSite[] = [];
|
|
50
|
+
for (const entry of raw) {
|
|
51
|
+
const e = (entry ?? {}) as Record<string, unknown>;
|
|
52
|
+
const id = text(e.id);
|
|
53
|
+
if (!id) continue;
|
|
54
|
+
const site: HostedSite = { id, name: text(e.name) };
|
|
55
|
+
if (text(e.logoUrl)) site.logoUrl = text(e.logoUrl);
|
|
56
|
+
if (text(e.color)) site.color = text(e.color);
|
|
57
|
+
sites.push(site);
|
|
58
|
+
}
|
|
59
|
+
return sites;
|
|
60
|
+
}
|