@wix/himalaya-cli 0.1567.0 → 0.1569.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/l10n/src/index.d.ts +9 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/message-format.d.ts +28 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/online-programs/src/index.d.ts +7 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/l10n.mjs +191 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-online-programs.mjs +14 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/package.json +4 -0
- package/dist/cli.mjs +20994 -20822
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/app-config.md +10 -0
- package/guides/app-icon.md +20 -1
- package/guides/himi-json-output.md +7 -0
- package/guides/importable-sdks.md +1 -0
- package/guides/one-app-to-himi-app.md +14 -0
- package/guides/release-notes.md +6 -0
- package/guides/sdk-reference.json +41 -2
- package/guides/splash-screens.md +156 -5
- package/package.json +2 -2
- package/templates/beauty-salon/config.ts +12 -1
- package/templates/beauty-salon/dev/index.ts +265 -0
- package/templates/beauty-salon/himalaya.content.json +3 -0
- package/templates/beauty-salon/splash.json +3 -0
- package/templates/beauty-salon/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/beauty-salon/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/beauty-salon/tier5-src/himi-booking-service/index.ts +13 -9
- package/templates/beauty-salon/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/beauty-salon-owner/config.ts +8 -0
- package/templates/beauty-salon-owner/dev/index.ts +1 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/beauty-salon-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/branded-lite/config.ts +12 -1
- package/templates/branded-lite/dev/index.ts +299 -2
- package/templates/branded-lite/himalaya.content.json +3 -0
- package/templates/branded-lite/splash.json +3 -0
- package/templates/branded-lite/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/branded-lite/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/branded-lite/tier5-src/himi-booking-service/index.ts +13 -9
- package/templates/branded-lite/tier5-src/himi-branded-lite-group/index.ts +20 -3
- package/templates/branded-lite/tier5-src/himi-branded-lite-groups/index.ts +18 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-notifsettings/index.ts +4 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +124 -15
- package/templates/branded-lite/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/branded-lite/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/brandy/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/community-club/config.ts +12 -1
- package/templates/community-club/dev/index.ts +267 -1
- package/templates/community-club/himalaya.content.json +3 -0
- package/templates/community-club/splash.json +3 -0
- package/templates/community-club/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/community-club/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/community-club/tier5-src/himi-branded-lite-group/index.ts +20 -3
- package/templates/community-club/tier5-src/himi-branded-lite-groups/index.ts +18 -2
- package/templates/community-club/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/community-club/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/community-club-owner/config.ts +8 -0
- package/templates/community-club-owner/dev/index.ts +1 -0
- package/templates/community-club-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/community-club-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/events-meetups/config.ts +12 -1
- package/templates/events-meetups/dev/index.ts +267 -1
- package/templates/events-meetups/himalaya.content.json +3 -0
- package/templates/events-meetups/splash.json +3 -0
- package/templates/events-meetups/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/events-meetups/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/events-meetups/tier5-src/himi-branded-lite-group/index.ts +20 -3
- package/templates/events-meetups/tier5-src/himi-branded-lite-groups/index.ts +18 -2
- package/templates/events-meetups/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/events-meetups/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/events-meetups-owner/config.ts +8 -0
- package/templates/events-meetups-owner/dev/index.ts +1 -0
- package/templates/events-meetups-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/events-meetups-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/gym-fitness/config.ts +12 -1
- package/templates/gym-fitness/dev/index.ts +265 -0
- package/templates/gym-fitness/himalaya.content.json +3 -0
- package/templates/gym-fitness/splash.json +3 -0
- package/templates/gym-fitness/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/gym-fitness/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/gym-fitness/tier5-src/himi-booking-service/index.ts +13 -9
- package/templates/gym-fitness/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/gym-fitness-owner/config.ts +8 -0
- package/templates/gym-fitness-owner/dev/index.ts +1 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/gym-fitness-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/himi-chat/dev/index.ts +1 -0
- package/templates/himi-chat/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/nonprofit-cause/config.ts +12 -1
- package/templates/nonprofit-cause/dev/index.ts +267 -1
- package/templates/nonprofit-cause/himalaya.content.json +3 -0
- package/templates/nonprofit-cause/splash.json +3 -0
- package/templates/nonprofit-cause/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/nonprofit-cause/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-group/index.ts +20 -3
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-groups/index.ts +18 -2
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/nonprofit-cause-owner/config.ts +8 -0
- package/templates/nonprofit-cause-owner/dev/index.ts +1 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/nonprofit-cause-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/owner-lite/config.ts +8 -0
- package/templates/owner-lite/dev/index.ts +1 -0
- package/templates/owner-lite/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/owner-lite/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/podcast-creator/config.ts +12 -1
- package/templates/podcast-creator/dev/index.ts +237 -0
- package/templates/podcast-creator/himalaya.content.json +3 -0
- package/templates/podcast-creator/splash.json +3 -0
- package/templates/podcast-creator/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/podcast-creator/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/podcast-creator-owner/config.ts +8 -0
- package/templates/podcast-creator-owner/dev/index.ts +1 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/podcast-creator-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/portfolio-creative/config.ts +12 -1
- package/templates/portfolio-creative/dev/index.ts +237 -0
- package/templates/portfolio-creative/himalaya.content.json +3 -0
- package/templates/portfolio-creative/splash.json +3 -0
- package/templates/portfolio-creative/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/portfolio-creative/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/portfolio-creative-owner/config.ts +8 -0
- package/templates/portfolio-creative-owner/dev/index.ts +1 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/portfolio-creative-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/restaurant/config.ts +12 -1
- package/templates/restaurant/dev/index.ts +237 -0
- package/templates/restaurant/himalaya.content.json +3 -0
- package/templates/restaurant/splash.json +3 -0
- package/templates/restaurant/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/restaurant/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/restaurant/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/restaurant/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/restaurant-owner/config.ts +8 -0
- package/templates/restaurant-owner/dev/index.ts +1 -0
- package/templates/restaurant-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/restaurant-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/retail-store/config.ts +12 -1
- package/templates/retail-store/dev/index.ts +237 -0
- package/templates/retail-store/himalaya.content.json +3 -0
- package/templates/retail-store/splash.json +3 -0
- package/templates/retail-store/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/retail-store/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/retail-store/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/retail-store-owner/config.ts +8 -0
- package/templates/retail-store-owner/dev/index.ts +1 -0
- package/templates/retail-store-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/retail-store-owner/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/surface-showcase/dev/index.ts +1 -0
- package/templates/yoga-studio/config.ts +12 -1
- package/templates/yoga-studio/dev/index.ts +269 -1
- package/templates/yoga-studio/himalaya.content.json +3 -0
- package/templates/yoga-studio/splash.json +3 -0
- package/templates/yoga-studio/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/yoga-studio/tier5-src/_shared/rich-body.ts +44 -0
- package/templates/yoga-studio/tier5-src/himi-booking-service/index.ts +13 -9
- package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +124 -15
- package/templates/yoga-studio/tier5-src/himi-branded-lite-splash/index.ts +35 -0
- package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +4 -0
- package/templates/yoga-studio-owner/config.ts +8 -0
- package/templates/yoga-studio-owner/dev/index.ts +1 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/himi-copy.ts +17 -6
- package/templates/yoga-studio-owner/tier5-src/_shared/rich-body.ts +44 -0
|
@@ -9,6 +9,8 @@ import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
|
|
|
9
9
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
10
10
|
import { get as dbGet, upsert as dbUpsert, type DbEntity } from "@himalaya/local-db";
|
|
11
11
|
import { isOfflineFailure, writeMayHaveLanded } from "../_shared/owner-load-error";
|
|
12
|
+
import { descriptionBody } from "../_shared/rich-body.js";
|
|
13
|
+
import { openRichLink } from "../_shared/rich-content-link.js";
|
|
12
14
|
|
|
13
15
|
interface MemberAvatar {
|
|
14
16
|
initials: string;
|
|
@@ -42,7 +44,8 @@ async function cachePutMembership(state: State, joined: boolean): Promise<void>
|
|
|
42
44
|
noImage: state.noImage,
|
|
43
45
|
detailScreenId: "himi_group",
|
|
44
46
|
};
|
|
45
|
-
await dbUpsert(ENTITY, { ...existing, id: state.id,
|
|
47
|
+
await dbUpsert(ENTITY, { ...existing, id: state.id, description: state.description,
|
|
48
|
+
descriptionJson: state.descriptionJson, joined: joined ? "true" : "false" });
|
|
46
49
|
} catch { /* capability not granted */ }
|
|
47
50
|
}
|
|
48
51
|
|
|
@@ -51,6 +54,8 @@ interface State {
|
|
|
51
54
|
id: string;
|
|
52
55
|
name: string;
|
|
53
56
|
description: string;
|
|
57
|
+
descriptionJson: string;
|
|
58
|
+
hasDescriptionJson: boolean;
|
|
54
59
|
privacy: string;
|
|
55
60
|
memberCount: string;
|
|
56
61
|
/** Member avatar strip (photo or initials per member) — best-effort: the
|
|
@@ -132,6 +137,8 @@ const initial: State = {
|
|
|
132
137
|
id: "",
|
|
133
138
|
name: "",
|
|
134
139
|
description: "",
|
|
140
|
+
descriptionJson: "",
|
|
141
|
+
hasDescriptionJson: false,
|
|
135
142
|
privacy: "",
|
|
136
143
|
memberCount: "",
|
|
137
144
|
memberAvatars: [],
|
|
@@ -162,6 +169,7 @@ const svc = defineService<State, {
|
|
|
162
169
|
load: (state: State, arg?: unknown) => Promise<State>;
|
|
163
170
|
join: (state: State) => Promise<State>;
|
|
164
171
|
leave: (state: State) => Promise<State>;
|
|
172
|
+
openLink: (state: State, arg?: unknown) => Promise<undefined>;
|
|
165
173
|
}>("himiBrandedGroup", {
|
|
166
174
|
state: initial,
|
|
167
175
|
actions: {
|
|
@@ -179,12 +187,16 @@ const svc = defineService<State, {
|
|
|
179
187
|
const privacyLabel = privacy[0].toUpperCase() + privacy.slice(1);
|
|
180
188
|
const signedIn = Boolean(await signedInEmail());
|
|
181
189
|
const memberAvatars = await memberAvatarsOf(id);
|
|
190
|
+
const body = descriptionBody(group.description, descriptionBody(group.teaser,
|
|
191
|
+
copyParam("group.descriptionFallback", "Join this group to connect with other members.")).bodyText);
|
|
182
192
|
const loaded: State = {
|
|
183
193
|
...state,
|
|
184
194
|
status: "ready",
|
|
185
195
|
id,
|
|
186
196
|
name,
|
|
187
|
-
description:
|
|
197
|
+
description: body.bodyText,
|
|
198
|
+
descriptionJson: body.bodyJson,
|
|
199
|
+
hasDescriptionJson: body.bodyJson !== "",
|
|
188
200
|
privacy: privacyLabel,
|
|
189
201
|
memberCount: count == null ? "" : `${count} ${count === 1 ? "member" : "members"}`,
|
|
190
202
|
memberAvatars,
|
|
@@ -209,12 +221,16 @@ const svc = defineService<State, {
|
|
|
209
221
|
const name = (String(cached.title ?? cached.name ?? "Group")).trim() || "Group";
|
|
210
222
|
const image = String(cached.image ?? "");
|
|
211
223
|
const signedIn = Boolean(await signedInEmail());
|
|
224
|
+
const body = descriptionBody(cached.descriptionJson || cached.description || cached.subtitle,
|
|
225
|
+
copyParam("group.descriptionFallback", "Join this group to connect with other members."));
|
|
212
226
|
const loaded: State = {
|
|
213
227
|
...state,
|
|
214
228
|
status: "ready",
|
|
215
229
|
id,
|
|
216
230
|
name,
|
|
217
|
-
description:
|
|
231
|
+
description: body.bodyText,
|
|
232
|
+
descriptionJson: body.bodyJson,
|
|
233
|
+
hasDescriptionJson: body.bodyJson !== "",
|
|
218
234
|
privacy: "Unknown",
|
|
219
235
|
memberCount: String(cached.meta ?? ""),
|
|
220
236
|
image,
|
|
@@ -233,6 +249,7 @@ const svc = defineService<State, {
|
|
|
233
249
|
return { ...state, id, status: "error", isLoading: false, notLoading: true, isErrored: true, notErrored: false, hideContent: true };
|
|
234
250
|
}
|
|
235
251
|
},
|
|
252
|
+
openLink: async (_state, arg) => { await openRichLink(arg); return undefined; },
|
|
236
253
|
join: async (state) => {
|
|
237
254
|
const signedIn = Boolean(await signedInEmail());
|
|
238
255
|
if (!signedIn) return membershipState({ ...state, message: copyParam("group.joinSignInMessage", "Sign in from Profile to join groups.") }, false, false);
|
|
@@ -3,6 +3,7 @@ import { queryGroups, type WixGroup } from "@himalaya/wix-groups";
|
|
|
3
3
|
import { groupImage } from "../_shared/himi-media.js";
|
|
4
4
|
import { copyParam, stashCopy } from "../_shared/himi-copy.js";
|
|
5
5
|
import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
|
|
6
|
+
import { descriptionBody, ROW_IMAGE_WIDTH } from "../_shared/rich-body.js";
|
|
6
7
|
|
|
7
8
|
// Offline-first (data-offline): the groups list is a public read-through — paint the
|
|
8
9
|
// last-synced rows from the on-device store first (instant, works offline), then
|
|
@@ -13,7 +14,11 @@ import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
|
|
|
13
14
|
// seam that keeps this shared bundle safe across every consuming app.
|
|
14
15
|
const ENTITY = "groups";
|
|
15
16
|
async function readCached(): Promise<GroupRow[]> {
|
|
16
|
-
try {
|
|
17
|
+
try {
|
|
18
|
+
const rows = (await getAll(ENTITY)) as unknown as GroupRow[];
|
|
19
|
+
return rows.map((row) => ({ ...row, subtitle: snippet(descriptionBody(row.subtitle).bodyText
|
|
20
|
+
|| descriptionBody(row.descriptionJson || row.description).bodyText) }));
|
|
21
|
+
} catch { return []; }
|
|
17
22
|
}
|
|
18
23
|
async function writeCached(rows: GroupRow[]): Promise<void> {
|
|
19
24
|
try { for (const r of rows) await upsert(ENTITY, r as unknown as DbEntity); } catch { /* capability not granted → online-only */ }
|
|
@@ -23,6 +28,8 @@ interface GroupRow {
|
|
|
23
28
|
id: string;
|
|
24
29
|
title: string;
|
|
25
30
|
subtitle: string;
|
|
31
|
+
description: string;
|
|
32
|
+
descriptionJson: string;
|
|
26
33
|
meta: string;
|
|
27
34
|
image: string;
|
|
28
35
|
initial: string;
|
|
@@ -45,14 +52,23 @@ interface State {
|
|
|
45
52
|
noEmpty: boolean;
|
|
46
53
|
}
|
|
47
54
|
|
|
55
|
+
function snippet(value: string): string {
|
|
56
|
+
const text = value.replace(/\s+/g, " ");
|
|
57
|
+
return text.length > 140 ? `${text.slice(0, 139).trimEnd()}…` : text;
|
|
58
|
+
}
|
|
59
|
+
|
|
48
60
|
function groupRow(group: WixGroup): GroupRow {
|
|
49
61
|
const title = (group.name || copyParam("groups.groupFallbackName", "Group")).trim();
|
|
50
62
|
const count = group.membersCount ?? group.memberCount;
|
|
51
63
|
const image = groupImage(group);
|
|
64
|
+
const teaser = descriptionBody(group.teaser).bodyText;
|
|
65
|
+
const body = descriptionBody(group.description, teaser, ROW_IMAGE_WIDTH);
|
|
52
66
|
return {
|
|
53
67
|
id: group.id || title,
|
|
54
68
|
title,
|
|
55
|
-
subtitle:
|
|
69
|
+
subtitle: snippet(teaser) || snippet(body.bodyText) || copyParam("groups.subtitleFallback", "Join the conversation."),
|
|
70
|
+
description: body.bodyText,
|
|
71
|
+
descriptionJson: body.bodyJson,
|
|
56
72
|
meta: count == null ? "" : `${count} ${count === 1 ? "member" : "members"}`,
|
|
57
73
|
image,
|
|
58
74
|
initial: (title[0] || "G").toUpperCase(),
|
|
@@ -8,6 +8,7 @@ import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
|
8
8
|
// best-effort upsertMyNotificationPreferences (a no-op on sample rows).
|
|
9
9
|
import { defineService as registerService } from "@himalaya/state";
|
|
10
10
|
import { withEmptyFlags } from "../_shared/empty-flags.js";
|
|
11
|
+
import { stashCopy } from "../_shared/himi-copy.js";
|
|
11
12
|
import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
|
|
12
13
|
import {
|
|
13
14
|
getNotificationTopics,
|
|
@@ -94,12 +95,13 @@ const COPY: LoadCopy<Row> = {
|
|
|
94
95
|
|
|
95
96
|
const defineService = withOnlineWrites(registerService, ["toggle"], { messageFields: ["actionMessage"] });
|
|
96
97
|
const svc = defineService<S, {
|
|
97
|
-
load: (s: S) => Promise<S>;
|
|
98
|
+
load: (s: S, arg?: unknown) => Promise<S>;
|
|
98
99
|
toggle: (s: S, p?: { id?: string; value?: unknown }) => Promise<S>;
|
|
99
100
|
}>("himiBrandedNotifSettings", withEmptyFlags({
|
|
100
101
|
state: { ...LOADING, topics: [], emptyText: "", actionMessage: "" },
|
|
101
102
|
actions: {
|
|
102
|
-
load: async (s) => {
|
|
103
|
+
load: async (s, arg) => {
|
|
104
|
+
stashCopy(arg);
|
|
103
105
|
try {
|
|
104
106
|
const [topicsRes, prefsRes] = await Promise.all([
|
|
105
107
|
getNotificationTopics(),
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dispatch } from "@himalaya/dispatch";
|
|
2
|
+
import { memberAccess, assertMemberAccess } from "../_shared/member-access.js";
|
|
3
|
+
import { offlineWriteMessage, withOnlineWrites } from "../_shared/online-actions.js";
|
|
2
4
|
// Tier-5 bundle: himi-branded-lite — single online-program detail (design §3.5
|
|
3
5
|
// `programs` vertical, screen full-potential pass).
|
|
4
6
|
//
|
|
@@ -20,7 +22,7 @@ import { withOnlineWrites } from "../_shared/online-actions.js";
|
|
|
20
22
|
// reads every program as `disabled` and keeps Enrol on its own commerce. An Apple-routed program
|
|
21
23
|
// offers Get access in place of Enrol and sells only through the shared checkout, never through the
|
|
22
24
|
// Wix payment rail.
|
|
23
|
-
import { defineService as registerService } from "@himalaya/state";
|
|
25
|
+
import { connectivity, defineService as registerService } from "@himalaya/state";
|
|
24
26
|
import { programImage } from "../_shared/himi-media.js";
|
|
25
27
|
import { WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
|
|
26
28
|
import {
|
|
@@ -28,10 +30,12 @@ import {
|
|
|
28
30
|
joinProgram,
|
|
29
31
|
payableIdOf,
|
|
30
32
|
queryPrograms,
|
|
33
|
+
searchMemberParticipation,
|
|
31
34
|
type WixProgram,
|
|
32
35
|
type WixProgramFull,
|
|
33
36
|
} from "@himalaya/wix-online-programs";
|
|
34
37
|
import { createRedirectSession } from "@himalaya/wix-redirects";
|
|
38
|
+
import { getMyMember } from "@himalaya/wix-members";
|
|
35
39
|
import { session as wixPlatformSession } from "@himalaya/wix-platform";
|
|
36
40
|
import { cleanTitle, initials } from "../_shared/himi-text.js";
|
|
37
41
|
import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
|
|
@@ -183,16 +187,17 @@ async function fetchProgram(id: string): Promise<WixProgram | undefined> {
|
|
|
183
187
|
/** Whether the existing commerce flow may start (before the join) and continue (before the Wix
|
|
184
188
|
* payment): a price refresh can move the program onto a route this flow must not charge. */
|
|
185
189
|
interface CommerceGuards {
|
|
190
|
+
assertMember(): Promise<void>;
|
|
186
191
|
mayStart(): boolean;
|
|
187
192
|
mayContinue(): boolean;
|
|
188
193
|
}
|
|
189
194
|
|
|
190
|
-
async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<string> {
|
|
195
|
+
async function enrollmentMessage(id: string, name: string, guards: CommerceGuards): Promise<string> {
|
|
191
196
|
const blocked = () => iapPriceCopy("program").purchaseUnavailable;
|
|
192
197
|
if (!guards.mayStart()) return blocked();
|
|
193
198
|
let preparationWithdrawn = false;
|
|
194
199
|
try {
|
|
195
|
-
const joined = await joinProgram(
|
|
200
|
+
const joined = await joinProgram(id, { beforeWrite: guards.assertMember, mayPreparePayment: () => {
|
|
196
201
|
if (!guards.mayContinue()) preparationWithdrawn = true;
|
|
197
202
|
return !preparationWithdrawn;
|
|
198
203
|
} });
|
|
@@ -218,6 +223,7 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
|
|
|
218
223
|
return !withdrawn;
|
|
219
224
|
};
|
|
220
225
|
if (!stillAllowed()) return blocked();
|
|
226
|
+
await guards.assertMember();
|
|
221
227
|
const pay = await payCashierOrder({
|
|
222
228
|
appId: WIX_PROGRAMS_APP_ID,
|
|
223
229
|
snapshotId: payable,
|
|
@@ -232,6 +238,7 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
|
|
|
232
238
|
// Join Applications returns a Wix Payments order id; the payment-request
|
|
233
239
|
// redirect uses that same order handle on the hosted rail.
|
|
234
240
|
cashierUrl: async () => {
|
|
241
|
+
await guards.assertMember();
|
|
235
242
|
if (!stillAllowed()) return undefined;
|
|
236
243
|
const redirect = await createRedirectSession({
|
|
237
244
|
ecomOrderPaymentRequest: { paymentRequestId: payable },
|
|
@@ -247,6 +254,7 @@ async function enrollmentMessage(name: string, guards: CommerceGuards): Promise<
|
|
|
247
254
|
if (pay.kind === "handoff") return "";
|
|
248
255
|
if (pay.kind === "cancelled") return copyParam("program.joinCancelledMessage", "Payment cancelled — you haven't enrolled.");
|
|
249
256
|
} catch {
|
|
257
|
+
if (!connectivity().online) return offlineWriteMessage;
|
|
250
258
|
if (preparationWithdrawn) return blocked();
|
|
251
259
|
// fall through to the site-directing message
|
|
252
260
|
}
|
|
@@ -274,6 +282,17 @@ let pricedItem: IapCatalogItem | undefined;
|
|
|
274
282
|
let actionMessage = "";
|
|
275
283
|
let loadSeq = 0;
|
|
276
284
|
let commerceStarted = false;
|
|
285
|
+
let enrollmentInFlight = false;
|
|
286
|
+
// Presentation dedupe only. The host remains the sole authority to resume a join.
|
|
287
|
+
let openedAuthToken = "";
|
|
288
|
+
let signInPresentation: "sheet" | "full_screen" = "sheet";
|
|
289
|
+
const continuationTarget = (id: string) => `program_enrol:${id}`;
|
|
290
|
+
|
|
291
|
+
async function continuation(operation: "begin" | "check" | "consume" | "cancel", id: string, token = "") {
|
|
292
|
+
try {
|
|
293
|
+
return await dispatch("himi.wix.authContinuation", { operation, target: continuationTarget(id), token }) as { supported?: boolean; confirmed?: boolean; account?: string; token?: string } | null;
|
|
294
|
+
} catch { return { supported: false }; } // Older binaries do not implement this host contract: fail closed.
|
|
295
|
+
}
|
|
277
296
|
|
|
278
297
|
function contentOf(state: State): Content {
|
|
279
298
|
return Object.fromEntries(CONTENT_KEYS.map((key) => [key, state[key]])) as Content;
|
|
@@ -283,13 +302,31 @@ const programReference = () => onlineProgramItem({ id: inputId() })?.reference;
|
|
|
283
302
|
|
|
284
303
|
async function load(state: State, arg?: unknown): Promise<undefined> {
|
|
285
304
|
stashCopy(arg);
|
|
305
|
+
const presentation = (arg as { signInPresentation?: unknown } | undefined)?.signInPresentation;
|
|
306
|
+
if (presentation === "sheet" || presentation === "full_screen") signInPresentation = presentation;
|
|
307
|
+
const id = inputId();
|
|
286
308
|
const seq = ++loadSeq;
|
|
287
309
|
const found = await fetchProgram(inputId()).then((program) => ({ program }), () => undefined);
|
|
288
310
|
if (seq !== loadSeq) return undefined;
|
|
289
311
|
content = found ? contentOf(ready(state, found.program)) : { ...content, status: "error" };
|
|
290
312
|
pricedItem = found?.program ? onlineProgramItem({ id: inputId() }, priceOf(found.program)) : pricedItem;
|
|
291
313
|
void svc.pricingPublish();
|
|
292
|
-
|
|
314
|
+
const prices = found?.program ? priceProgram() : Promise.resolve();
|
|
315
|
+
if (seq !== loadSeq || inputId() !== id) return undefined;
|
|
316
|
+
// The host owns the intent across account-scoped worker replacement. The consume is
|
|
317
|
+
// atomic and destructive. A background guest refresh keeps a pending request; the
|
|
318
|
+
// host modal boundary cancels it on dismissal. Route params and hydration never
|
|
319
|
+
// authorize a join. Check prices before spending the one shot.
|
|
320
|
+
const waiting = await continuation("check", id);
|
|
321
|
+
if (waiting?.confirmed && found?.program) {
|
|
322
|
+
await prices;
|
|
323
|
+
if (seq === loadSeq && inputId() === id) {
|
|
324
|
+
// Authority stays in the host until the last asynchronous prerequisite completes:
|
|
325
|
+
// expiry, cancellation or an intervening account switch can still revoke it.
|
|
326
|
+
const permit = await continuation("consume", id);
|
|
327
|
+
if (permit?.confirmed && permit.account) await enroll(permit.account);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
293
330
|
void resumeCommerce();
|
|
294
331
|
return undefined;
|
|
295
332
|
}
|
|
@@ -310,16 +347,88 @@ async function priceProgram(): Promise<void> {
|
|
|
310
347
|
void svc.pricingPublish();
|
|
311
348
|
}
|
|
312
349
|
|
|
313
|
-
async function enroll(): Promise<undefined> {
|
|
350
|
+
async function enroll(afterAuthAccount = ""): Promise<undefined> {
|
|
351
|
+
const id = inputId();
|
|
314
352
|
const reference = programReference();
|
|
315
|
-
if (!reference) return undefined;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
353
|
+
if (!reference || enrollmentInFlight) return undefined;
|
|
354
|
+
if (!connectivity().online) {
|
|
355
|
+
actionMessage = offlineWriteMessage;
|
|
356
|
+
void svc.pricingPublish();
|
|
357
|
+
return undefined;
|
|
358
|
+
}
|
|
359
|
+
if (!pricing.checkoutGuard(reference).allowed) {
|
|
360
|
+
actionMessage = pricing.checkoutGuard(reference).route === "resolving" ? "" : iapPriceCopy("program").purchaseUnavailable;
|
|
361
|
+
void svc.pricingPublish();
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
enrollmentInFlight = true;
|
|
322
365
|
void svc.pricingPublish();
|
|
366
|
+
try {
|
|
367
|
+
const member = await memberAccess();
|
|
368
|
+
if (id !== inputId()) return undefined;
|
|
369
|
+
if (afterAuthAccount && member.email !== afterAuthAccount) return undefined;
|
|
370
|
+
if (!member.email) {
|
|
371
|
+
const request = await continuation("begin", id);
|
|
372
|
+
if (request?.supported !== true || !request.token) {
|
|
373
|
+
actionMessage = copyParam("program.signInUpdateRequired", "Update the app to sign in and continue enrollment.");
|
|
374
|
+
return undefined;
|
|
375
|
+
}
|
|
376
|
+
if (openedAuthToken === request.token) return undefined;
|
|
377
|
+
openedAuthToken = request.token;
|
|
378
|
+
actionMessage = "";
|
|
379
|
+
try {
|
|
380
|
+
await dispatch("himi.open", { screenId: "himi_sign_in", mode: "signin", presentation: signInPresentation, modal: "1", authContinuation: request.token });
|
|
381
|
+
} catch (error) {
|
|
382
|
+
openedAuthToken = "";
|
|
383
|
+
await continuation("cancel", id, request.token);
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
388
|
+
await assertMemberAccess(member);
|
|
389
|
+
if (afterAuthAccount) {
|
|
390
|
+
// Branded's initPhase refetches participation after NOW_LOGGED_IN, before
|
|
391
|
+
// joining. A settled participant need not have a current Join Application.
|
|
392
|
+
const memberId = (await getMyMember()).member?.id;
|
|
393
|
+
if (!memberId) throw new Error("Current member unavailable");
|
|
394
|
+
const response = await searchMemberParticipation(id, memberId);
|
|
395
|
+
await assertMemberAccess(member);
|
|
396
|
+
if (id !== inputId()) return undefined;
|
|
397
|
+
if (!Array.isArray(response.participants)) throw new Error("Participation unavailable");
|
|
398
|
+
const participant = response.participants[0];
|
|
399
|
+
if (participant) {
|
|
400
|
+
if (participant.memberId !== memberId || participant.programId !== id) throw new Error("Participation identity changed");
|
|
401
|
+
const status = (participant.enrollmentStatus ?? "").toUpperCase();
|
|
402
|
+
const progress = (participant.progressStatus ?? "").toUpperCase();
|
|
403
|
+
if (status === "JOINED" || ["COMPLETED", "FAILED"].includes(progress)) {
|
|
404
|
+
actionMessage = ["COMPLETED", "FAILED"].includes(progress)
|
|
405
|
+
? copyTemplate("program.participationRecordedMessage", "Your participation in {name} is already recorded.", { name: content.name })
|
|
406
|
+
: copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name: content.name });
|
|
407
|
+
return undefined;
|
|
408
|
+
}
|
|
409
|
+
if (!["LEFT", "REMOVED"].includes(status)) {
|
|
410
|
+
actionMessage = copyParam("program.participationUnavailable", "Your current participation cannot be enrolled again.");
|
|
411
|
+
return undefined;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// joinProgram refreshes the current Join Application before creating anything. Its
|
|
416
|
+
// existing-participant/private/paid and pending-payment guards remain authoritative.
|
|
417
|
+
const message = await enrollmentMessage(id, content.name, {
|
|
418
|
+
assertMember: async () => {
|
|
419
|
+
await assertMemberAccess(member);
|
|
420
|
+
if (!connectivity().online) throw new Error(offlineWriteMessage);
|
|
421
|
+
},
|
|
422
|
+
mayStart: () => id === inputId() && pricing.checkoutGuard(reference).allowed,
|
|
423
|
+
mayContinue: () => id === inputId() && pricing.continueCheckoutGuard(reference).allowed,
|
|
424
|
+
});
|
|
425
|
+
if (id === inputId()) actionMessage = message;
|
|
426
|
+
} catch {
|
|
427
|
+
if (id === inputId()) actionMessage = copyTemplate("program.joinOnSiteMessage", "Enrolment happens on the website — visit the site to join {name}.", { name: content.name });
|
|
428
|
+
} finally {
|
|
429
|
+
enrollmentInFlight = false;
|
|
430
|
+
void svc.pricingPublish();
|
|
431
|
+
}
|
|
323
432
|
return undefined;
|
|
324
433
|
}
|
|
325
434
|
|
|
@@ -343,7 +452,7 @@ function pricedState(state: State): State {
|
|
|
343
452
|
actionMessage: purchase.accessReady ? copyTemplate("program.joinedMessage", "You're enrolled in {name}.", { name: content.name }) : actionMessage,
|
|
344
453
|
// `disabled`: this app sells nothing through the App Store, so the program shows its own price.
|
|
345
454
|
priceLine: view?.route === "disabled" ? pricedItem?.commerceDisplayPrice ?? "" : price.priceText, priceNote: price.priceNote, hidePriceRetry: view?.route === "disabled" || (price.hidePriceRetry && commerce.scopeComplete()),
|
|
346
|
-
enrollDisabled: price.actionDisabled, enrollNote: price.actionNote, enrollHidden: purchase.appleRoute,
|
|
455
|
+
enrollDisabled: price.actionDisabled || enrollmentInFlight, enrollNote: price.actionNote, enrollHidden: purchase.appleRoute,
|
|
347
456
|
buyHidden: !purchase.appleRoute || purchase.accessReady, buyLabel: purchase.buyLabel, buyDisabled: purchase.buyDisabled, buyNote: purchase.buyNote,
|
|
348
457
|
...(poll ? { _poll: poll } : {}),
|
|
349
458
|
});
|
|
@@ -394,7 +503,7 @@ const svc = defineService<State, Actions>("himiBrandedProgram", {
|
|
|
394
503
|
notHydrated: [...PRICING_NOT_HYDRATED],
|
|
395
504
|
actions: {
|
|
396
505
|
load,
|
|
397
|
-
enroll,
|
|
506
|
+
enroll: () => enroll(),
|
|
398
507
|
buy,
|
|
399
508
|
pricingPublish: pricedState,
|
|
400
509
|
pricingStart: pricedState,
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { defineService } from "@himalaya/state";
|
|
2
|
+
|
|
3
|
+
// Native renderers animate; this worker only publishes the requested visibility.
|
|
4
|
+
// The host waits for presentation, bounded by the original startup deadline.
|
|
5
|
+
const input = (globalThis as unknown as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input;
|
|
6
|
+
interface State {
|
|
7
|
+
title: string;
|
|
8
|
+
caption: string;
|
|
9
|
+
iconHidden: boolean;
|
|
10
|
+
presentationPending: boolean;
|
|
11
|
+
errorText?: string;
|
|
12
|
+
customLogoHidden?: boolean;
|
|
13
|
+
backgroundImageUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
export const splash = defineService<State, { present: (state: State) => State }>("splash", {
|
|
16
|
+
state: {
|
|
17
|
+
title: typeof input?.title === "string" ? input.title : "",
|
|
18
|
+
caption: typeof input?.caption === "string" ? input.caption : "",
|
|
19
|
+
iconHidden: false,
|
|
20
|
+
presentationPending: true,
|
|
21
|
+
// A malformed OTA toggle must not release the native cover as a successful
|
|
22
|
+
// presentation. The host's existing deadline remains the failure fallback.
|
|
23
|
+
...(input?.showIcon !== undefined && typeof input.showIcon !== "boolean"
|
|
24
|
+
? { errorText: "Splash showIcon must be a boolean" } : {}),
|
|
25
|
+
...(typeof input?.backgroundImageUrl === "string" ? { backgroundImageUrl: input.backgroundImageUrl } : {}),
|
|
26
|
+
...(input?.customLogo === true ? { customLogoHidden: true } : {}),
|
|
27
|
+
},
|
|
28
|
+
actions: {
|
|
29
|
+
present: (state) => state.errorText ? state : ({ ...state,
|
|
30
|
+
iconHidden: input?.showIcon === false || input?.customLogo === true,
|
|
31
|
+
...(input?.customLogo === true ? { customLogoHidden: input?.showIcon === false } : {}),
|
|
32
|
+
presentationPending: false,
|
|
33
|
+
}),
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -19,6 +19,7 @@ import { createIapCommerce, onlineProgramOrders, pricingPlanOrders, storesAppleS
|
|
|
19
19
|
import { IAP_RESTORE_KEYS, NO_IAP_RESTORE, createIapRestore, resetUnhydratedKeysOnOlderRuntime, type IapRestoreFields } from "../_shared/iap-worker.js";
|
|
20
20
|
|
|
21
21
|
interface AuthRouteInput {
|
|
22
|
+
authContinuation?: string;
|
|
22
23
|
/** Standalone sign-in only: `register` opens the form on Create account. */
|
|
23
24
|
mode?: string;
|
|
24
25
|
intent?: string;
|
|
@@ -356,6 +357,9 @@ async function authenticate(s: S, operation: "signIn" | "register"): Promise<S |
|
|
|
356
357
|
await svc.beginAuth(); // publish busy before waiting for the network
|
|
357
358
|
const raw = await dispatch(operation === "signIn" ? "himi.wix.signInDirect" : "himi.wix.registerDirect", {
|
|
358
359
|
email: s.email.trim(), password,
|
|
360
|
+
// An opaque host-issued token, never a return route. Native commits it before the
|
|
361
|
+
// account change can destroy this worker; the replacement does not need this promise.
|
|
362
|
+
...(routeInput().authContinuation ? { authContinuation: routeInput().authContinuation } : {}),
|
|
359
363
|
...(operation === "register" ? { firstName: s.firstName, lastName: s.lastName } : {}),
|
|
360
364
|
});
|
|
361
365
|
rejectResult(raw, operation);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// Copy-as-configuration for WORKER-emitted strings (`himi docs designing-himi-screens`,
|
|
2
2
|
// §"Copy is configuration"): a worker never hardcodes user-facing copy — the
|
|
3
3
|
// descriptor bakes the provisioned copy-pack overrides relevant to this worker
|
|
4
|
-
// into its onAppear action params as a `copy` JSON object (
|
|
5
|
-
//
|
|
4
|
+
// into its onAppear action params as a `copy` JSON object (customer strings or
|
|
5
|
+
// source-matched stock translations, computed at descriptor-build time), and the
|
|
6
6
|
// worker reads every user-facing string through `copyParam(key, fallback)`.
|
|
7
7
|
//
|
|
8
8
|
// STASHED (the stashed appear-params idiom, himi-branded-lite-home): the
|
|
@@ -16,9 +16,13 @@
|
|
|
16
16
|
// every worker that emits user-facing copy, and copied verbatim into the
|
|
17
17
|
// generated templates' tier5-src/_shared/.
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/** Customer strings override every read; translated stock is valid only for its
|
|
20
|
+
* original English fallback. Some existing keys serve more than one message. */
|
|
21
|
+
export type CopyPackValue = string | { source: string; translation: string };
|
|
20
22
|
|
|
21
|
-
|
|
23
|
+
let stashedCopy: Record<string, CopyPackValue> = Object.create(null);
|
|
24
|
+
|
|
25
|
+
/** Absorb the `copy` onAppear param (customer strings or stock translation records)
|
|
22
26
|
* from an action arg. Call first in every action that can receive onAppear
|
|
23
27
|
* params (load/refresh). Non-copy args and malformed packs are ignored — the
|
|
24
28
|
* stock fallbacks keep rendering. */
|
|
@@ -29,9 +33,15 @@ export function stashCopy(arg: unknown): void {
|
|
|
29
33
|
try {
|
|
30
34
|
const parsed = JSON.parse(raw) as unknown;
|
|
31
35
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
32
|
-
const next: Record<string,
|
|
36
|
+
const next: Record<string, CopyPackValue> = Object.create(null);
|
|
33
37
|
for (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {
|
|
34
38
|
if (typeof value === "string") next[key] = value;
|
|
39
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
40
|
+
const row = value as Record<string, unknown>;
|
|
41
|
+
if (typeof row.source === "string" && typeof row.translation === "string") {
|
|
42
|
+
next[key] = { source: row.source, translation: row.translation };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
35
45
|
}
|
|
36
46
|
stashedCopy = next;
|
|
37
47
|
} catch {
|
|
@@ -43,7 +53,8 @@ export function stashCopy(arg: unknown): void {
|
|
|
43
53
|
* pack / no such key reached this worker. */
|
|
44
54
|
export function copyParam(key: string, fallback: string): string {
|
|
45
55
|
const value = stashedCopy[key];
|
|
46
|
-
|
|
56
|
+
if (typeof value === "string") return value !== "" ? value : fallback;
|
|
57
|
+
return value?.source === fallback && value.translation !== "" ? value.translation : fallback;
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
/** Interpolate `{name}` placeholders in a (possibly overridden) template:
|
|
@@ -52,6 +52,16 @@ export default defineApp({
|
|
|
52
52
|
]
|
|
53
53
|
},
|
|
54
54
|
designTokensPath: "./tokens.json",
|
|
55
|
+
// Native fallback; the T5 splash descriptor owns the editable launch appearance.
|
|
56
|
+
splash: {
|
|
57
|
+
"enabled": true,
|
|
58
|
+
"backgroundColor": "accentPrimary",
|
|
59
|
+
"logoAsset": "branded_lite_app_icon",
|
|
60
|
+
"logoWidth": 192,
|
|
61
|
+
"layout": "logoOnly",
|
|
62
|
+
"minVisibleMs": 0,
|
|
63
|
+
"maxHoldMs": 5000
|
|
64
|
+
},
|
|
55
65
|
navigation: {
|
|
56
66
|
"navKind": "responsive",
|
|
57
67
|
"scheme": "himalaya-__HIMI_APP_SLUG__",
|
|
@@ -236,7 +246,8 @@ export default defineApp({
|
|
|
236
246
|
"contextName",
|
|
237
247
|
"returnScreenId",
|
|
238
248
|
"returnId",
|
|
239
|
-
"returnChannelId"
|
|
249
|
+
"returnChannelId",
|
|
250
|
+
"authContinuation"
|
|
240
251
|
],
|
|
241
252
|
"parentScreenId": "himi_account",
|
|
242
253
|
"tabId": "profile"
|