@wix/himalaya-cli 0.1410.0 → 0.1412.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 +1175 -790
- package/guides/release-notes.md +12 -0
- package/guides/wix-business-context.md +32 -0
- package/guides/wix-site-to-himi-app.md +21 -3
- package/package.json +2 -2
- package/templates/beauty-salon-owner/config.ts +29 -0
- package/templates/beauty-salon-owner/dev/index.ts +1121 -0
- package/templates/beauty-salon-owner/himalaya.content.json +6 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/branded-lite/dev/index.ts +1 -0
- package/templates/branded-lite/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/branded-lite/tier5-src/wix-spaces-chat-thread/index.ts +131 -21
- package/templates/community-club-owner/config.ts +29 -0
- package/templates/community-club-owner/dev/index.ts +1121 -0
- package/templates/community-club-owner/himalaya.content.json +6 -0
- package/templates/community-club-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/community-club-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/events-meetups-owner/config.ts +29 -0
- package/templates/events-meetups-owner/dev/index.ts +1121 -0
- package/templates/events-meetups-owner/himalaya.content.json +6 -0
- package/templates/events-meetups-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/events-meetups-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/gym-fitness-owner/config.ts +29 -0
- package/templates/gym-fitness-owner/dev/index.ts +1121 -0
- package/templates/gym-fitness-owner/himalaya.content.json +6 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/nonprofit-cause-owner/config.ts +29 -0
- package/templates/nonprofit-cause-owner/dev/index.ts +1121 -0
- package/templates/nonprofit-cause-owner/himalaya.content.json +6 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/owner-lite/config.ts +29 -0
- package/templates/owner-lite/dev/index.ts +1121 -0
- package/templates/owner-lite/himalaya.content.json +6 -0
- package/templates/owner-lite/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/owner-lite/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/podcast-creator-owner/config.ts +29 -0
- package/templates/podcast-creator-owner/dev/index.ts +1121 -0
- package/templates/podcast-creator-owner/himalaya.content.json +6 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/portfolio-creative-owner/config.ts +29 -0
- package/templates/portfolio-creative-owner/dev/index.ts +1121 -0
- package/templates/portfolio-creative-owner/himalaya.content.json +6 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/restaurant-owner/config.ts +29 -0
- package/templates/restaurant-owner/dev/index.ts +1121 -0
- package/templates/restaurant-owner/himalaya.content.json +6 -0
- package/templates/restaurant-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/restaurant-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/retail-store-owner/config.ts +29 -0
- package/templates/retail-store-owner/dev/index.ts +1121 -0
- package/templates/retail-store-owner/himalaya.content.json +6 -0
- package/templates/retail-store-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/retail-store-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
- package/templates/yoga-studio-owner/config.ts +29 -0
- package/templates/yoga-studio-owner/dev/index.ts +1121 -0
- package/templates/yoga-studio-owner/himalaya.content.json +6 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/chat-live.ts +242 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/detail-presence.ts +51 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-inbox/index.ts +308 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-inbox-thread/index.ts +307 -0
|
@@ -3,17 +3,33 @@
|
|
|
3
3
|
// id (member picker / member detail "Message"). When the input is a member id we
|
|
4
4
|
// resolve (get-or-create) the 1:1 chatroom first; getMessages/sendChatMessage always
|
|
5
5
|
// operate on the resolved CHATROOM id.
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
//
|
|
7
|
+
// LIVE: once the conversation is open, `load` also returns what keeps it live —
|
|
8
|
+
// `_shared/chat-live.ts` picks a `wix-realtime://` stream when the screen passes a
|
|
9
|
+
// `chatRealtime` channel, else a 4 s foreground `_poll` of `refresh`. A message from the
|
|
10
|
+
// business then appears without the member leaving the screen. `load`, `setDraft` and
|
|
11
|
+
// `send` keep their contract; the directives are stripped by the runtime, never state.
|
|
12
|
+
import { defineService, type PollDirective, type StreamDirective } from "@himalaya/state";
|
|
13
|
+
import { getMessages, sendChatMessage, getOrCreateDirectChatroom, getChatToken, type ChatMessage } from "@himalaya/wix-chat-web";
|
|
8
14
|
import { getMyData } from "@himalaya/wix-users";
|
|
9
15
|
import { formatMonthDayTime } from "../_shared/engine-intl.js";
|
|
10
16
|
import { presence, presenceAfterError, detailInputId, type PresenceFlags } from "../_shared/detail-presence.js";
|
|
17
|
+
import { writeMayHaveLanded } from "../_shared/owner-load-error.js";
|
|
18
|
+
import { chatRealtimeConfig, createReloadGate, createSendIntents, createVisitToken, liveDirectives, postWithVisitToken, liveFallback, type ChatLiveMode, type ChatRealtimeConfig } from "../_shared/chat-live.js";
|
|
19
|
+
|
|
20
|
+
const SERVICE = "spacesChatThread";
|
|
11
21
|
|
|
12
22
|
// leadFill gates a per-row right-align: " " for MY messages (a flexible gated
|
|
13
23
|
// spacer to the bubble's left → bubble hugs the right edge), "" for others
|
|
14
24
|
// (spacer collapses → bubble stays left). mine drives the bubble tint.
|
|
15
25
|
interface Row { id: string; text: string; when: string; leadFill: string; mine: string }
|
|
16
|
-
interface S extends PresenceFlags {
|
|
26
|
+
interface S extends PresenceFlags {
|
|
27
|
+
status: "loading" | "ready"; messages: Row[]; emptyText: string; draft: string; sendHint: string; resolvedChatroomId: string; myUserId: string;
|
|
28
|
+
/** Which path keeps the open thread live (diagnostic). */
|
|
29
|
+
live?: ChatLiveMode;
|
|
30
|
+
_stream?: StreamDirective;
|
|
31
|
+
_poll?: PollDirective;
|
|
32
|
+
}
|
|
17
33
|
|
|
18
34
|
function inputId(): string {
|
|
19
35
|
return (globalThis as { himalaya?: { input?: { id?: string } } }).himalaya?.input?.id ?? "";
|
|
@@ -49,9 +65,65 @@ function norm(m: ChatMessage, i: number, myUserId: string): Row {
|
|
|
49
65
|
return { id: mm.id || `m${i}`, text: text || "Message", when, leadFill: mine ? " " : "", mine: mine ? "1" : "" };
|
|
50
66
|
}
|
|
51
67
|
|
|
68
|
+
// ONE member chatToken per visit (`_shared/chat-live.ts`): a 4 s re-read would otherwise
|
|
69
|
+
// mint a token every 4 s on top of its GET. Reset on every `load`.
|
|
70
|
+
const memberToken = createVisitToken(() => getChatToken({ role: "member" }).then((t) => t.chatToken));
|
|
71
|
+
|
|
52
72
|
async function reload(id: string, myUserId: string): Promise<Row[]> {
|
|
53
|
-
|
|
54
|
-
|
|
73
|
+
if (!id) return [];
|
|
74
|
+
const token = memberToken.get();
|
|
75
|
+
try {
|
|
76
|
+
const msgs = await getMessages(id, 30, { role: "member", chatToken: await token });
|
|
77
|
+
return (msgs ?? []).map((m, i) => norm(m, i, myUserId));
|
|
78
|
+
} catch (e) {
|
|
79
|
+
memberToken.drop(token);
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Send ids that belong to the intent (conversation + text), not the tap. */
|
|
85
|
+
const intents = createSendIntents();
|
|
86
|
+
|
|
87
|
+
/** Post once per intent: a retried tap of the same text reuses its message id, so a send
|
|
88
|
+
* that failed ambiguously and is tapped again stays one message. */
|
|
89
|
+
async function postOnce(chatroomId: string, text: string): Promise<void> {
|
|
90
|
+
const messageId = intents.idFor(chatroomId, text);
|
|
91
|
+
await postWithVisitToken(memberToken, (chatToken) => sendChatMessage(chatroomId, text, { role: "member", chatToken, messageId }));
|
|
92
|
+
intents.settle(chatroomId, text);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** A send in flight. Set synchronously before the first `await`: an async action publishes
|
|
96
|
+
* no state until it returns, so a state flag could not stop a second tap. */
|
|
97
|
+
let sendingNow = false;
|
|
98
|
+
|
|
99
|
+
// The conversation a live re-read reads, and the channel the screen passed (if any).
|
|
100
|
+
let current = { chatroomId: "", myUserId: "" };
|
|
101
|
+
let realtime: ChatRealtimeConfig | null = null;
|
|
102
|
+
const gate = createReloadGate(() => reload(current.chatroomId, current.myUserId));
|
|
103
|
+
|
|
104
|
+
/** A tick or a stream event: re-read the open conversation. A failed tick keeps the
|
|
105
|
+
* thread as it is — the next one tries again.
|
|
106
|
+
*
|
|
107
|
+
* The read lands on the LIVE state (`svc.getState()`), not the state the tick started
|
|
108
|
+
* with: the runtime replaces the whole service state with what an action returns, so a
|
|
109
|
+
* tick that resolves after the member typed or sent would otherwise put back the old
|
|
110
|
+
* draft and the old messages. */
|
|
111
|
+
async function reread(s: S): Promise<S> {
|
|
112
|
+
if (!s.found || !s.resolvedChatroomId || s.resolvedChatroomId !== current.chatroomId) return s;
|
|
113
|
+
try {
|
|
114
|
+
const messages = await gate.request();
|
|
115
|
+
const now = svc.getState();
|
|
116
|
+
if (now.resolvedChatroomId !== s.resolvedChatroomId) return now;
|
|
117
|
+
return { ...now, messages, emptyText: messages.length ? "" : now.emptyText };
|
|
118
|
+
} catch {
|
|
119
|
+
return svc.getState();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The live service state, for an action that awaited. A typed function rather than
|
|
124
|
+
* `svc.getState()` inline, so `svc`'s own initializer does not reference it. */
|
|
125
|
+
function liveState(): S {
|
|
126
|
+
return svc.getState();
|
|
55
127
|
}
|
|
56
128
|
|
|
57
129
|
// Resolve my user id once (best-effort) so messages can be tagged mine/other.
|
|
@@ -60,17 +132,23 @@ async function resolveMyUserId(): Promise<string> {
|
|
|
60
132
|
}
|
|
61
133
|
|
|
62
134
|
const svc = defineService<S, {
|
|
63
|
-
load: (s: S) => Promise<S>;
|
|
135
|
+
load: (s: S, a?: unknown) => Promise<S>;
|
|
64
136
|
setDraft: (s: S, a?: unknown) => S;
|
|
65
137
|
send: (s: S) => Promise<S>;
|
|
66
|
-
|
|
138
|
+
refresh: (s: S) => Promise<S>;
|
|
139
|
+
onStreamEvent: (s: S) => Promise<S>;
|
|
140
|
+
onStreamError: (s: S) => S;
|
|
141
|
+
}>(SERVICE, {
|
|
67
142
|
state: { status: "loading", messages: [], emptyText: "", draft: "", sendHint: "", resolvedChatroomId: "", myUserId: "", ...presence("loading") },
|
|
68
143
|
actions: {
|
|
69
|
-
load: async (s) => {
|
|
144
|
+
load: async (s, a) => {
|
|
145
|
+
const param = (a as { chatRealtime?: unknown } | null | undefined)?.chatRealtime;
|
|
146
|
+
if (param !== undefined) realtime = chatRealtimeConfig(param);
|
|
70
147
|
const raw = inputId();
|
|
71
148
|
// No thread / recipient id (a stale link, a route opened directly) -> the shared
|
|
72
149
|
// not-found state, and no composer: there is nobody to send to.
|
|
73
150
|
if (!detailInputId()) return { ...s, status: "ready", messages: [], emptyText: "", ...presence("missing") };
|
|
151
|
+
memberToken.reset();
|
|
74
152
|
// Resolve chatroom + my user id concurrently (myUserId tags own messages).
|
|
75
153
|
const [chatroomId, myUserId] = await Promise.all([resolveChatroomId(raw, inputKind()), resolveMyUserId()]);
|
|
76
154
|
// A recipient whose 1:1 chatroom could not be resolved is not a conversation yet:
|
|
@@ -79,7 +157,8 @@ const svc = defineService<S, {
|
|
|
79
157
|
if (!chatroomId) return { ...s, status: "ready", resolvedChatroomId: "", myUserId, messages: [], emptyText: "", ...presence("failed") };
|
|
80
158
|
try {
|
|
81
159
|
const messages = await reload(chatroomId, myUserId);
|
|
82
|
-
|
|
160
|
+
current = { chatroomId, myUserId };
|
|
161
|
+
return { ...s, status: "ready", resolvedChatroomId: chatroomId, myUserId, messages, emptyText: messages.length ? "" : "No messages yet. Say hello!", ...presence("found"), ...liveDirectives(realtime, chatroomId, SERVICE) };
|
|
83
162
|
} catch (e) {
|
|
84
163
|
// A thread whose messages cannot be read is not a live, empty conversation: a 404
|
|
85
164
|
// is the shared not-found state, anything else the load-error line — never a
|
|
@@ -88,24 +167,55 @@ const svc = defineService<S, {
|
|
|
88
167
|
}
|
|
89
168
|
},
|
|
90
169
|
setDraft: (s, a) => ({ ...s, draft: val(a) }),
|
|
170
|
+
// The fallback poll's tick.
|
|
171
|
+
refresh: async (s) => reread(s),
|
|
172
|
+
// "The conversation changed" — the event's payload is deliberately not read.
|
|
173
|
+
onStreamEvent: async (s) => reread(s),
|
|
174
|
+
// The channel is gone, or this host refuses it (the web host does): poll instead.
|
|
175
|
+
onStreamError: (s) => (s.found && s.resolvedChatroomId ? { ...s, ...liveFallback(SERVICE) } : s),
|
|
91
176
|
// REAL WRITE: send the typed message to the RESOLVED chatroom id, then re-read.
|
|
177
|
+
// Every return builds on the LIVE state: the runtime replaces state wholesale, and ticks
|
|
178
|
+
// or typing may have changed it while this awaited.
|
|
92
179
|
send: async (s) => {
|
|
93
180
|
const text = s.draft.trim();
|
|
94
181
|
if (!text) return { ...s, sendHint: "Type a message first." };
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (!chatroomId) chatroomId = await resolveChatroomId(inputId(), inputKind());
|
|
98
|
-
if (!chatroomId) return { ...s, sendHint: "Couldn't start conversation." };
|
|
182
|
+
if (sendingNow) return s;
|
|
183
|
+
sendingNow = true;
|
|
99
184
|
try {
|
|
100
|
-
|
|
185
|
+
// Resolve lazily in case send() runs before load() settled.
|
|
186
|
+
let chatroomId = s.resolvedChatroomId;
|
|
187
|
+
if (!chatroomId) chatroomId = await resolveChatroomId(inputId(), inputKind());
|
|
188
|
+
if (!chatroomId) return { ...liveState(), sendHint: "Couldn't start conversation." };
|
|
189
|
+
try {
|
|
190
|
+
await postOnce(chatroomId, text);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
// Surface a short reason (the SDK rejects with "HTTP <code>: <body>") so a
|
|
193
|
+
// real failure is diagnosable on-device instead of an opaque "Couldn't send".
|
|
194
|
+
// A failure that may have posted anyway (a gateway timeout after the backend stored
|
|
195
|
+
// it) must not read as "Couldn't send": say it may have gone. A retry of the same text
|
|
196
|
+
// reuses its message id, so it cannot become a second message.
|
|
197
|
+
const msg = (e as { message?: string })?.message || "";
|
|
198
|
+
if (writeMayHaveLanded(e)) return { ...liveState(), resolvedChatroomId: chatroomId, sendHint: "Couldn't confirm it was sent — check before sending again." };
|
|
199
|
+
return { ...liveState(), resolvedChatroomId: chatroomId, sendHint: msg ? `Couldn't send — ${msg.slice(0, 80)}` : "Couldn't send — try again." };
|
|
200
|
+
}
|
|
201
|
+
// SENT. From here on nothing may say otherwise: a failed re-read must not offer a
|
|
202
|
+
// second Send of a message that already posted.
|
|
101
203
|
const myUserId = s.myUserId || (await resolveMyUserId());
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
204
|
+
// Re-read THROUGH the live-read gate, so this read queues behind a tick already in
|
|
205
|
+
// flight instead of racing it: whatever lands last then holds the sent reply.
|
|
206
|
+
current = { chatroomId, myUserId };
|
|
207
|
+
const cleared = (now: S): string => (now.draft.trim() === text ? "" : now.draft);
|
|
208
|
+
try {
|
|
209
|
+
const messages = await gate.request();
|
|
210
|
+
const now = liveState();
|
|
211
|
+
return { ...now, resolvedChatroomId: chatroomId, myUserId, messages, draft: cleared(now), emptyText: "", sendHint: "Sent ✓" };
|
|
212
|
+
} catch {
|
|
213
|
+
const now = liveState();
|
|
214
|
+
const mine: Row = { id: `local-${now.messages.length}`, text, when: "", leadFill: " ", mine: "1" };
|
|
215
|
+
return { ...now, resolvedChatroomId: chatroomId, myUserId, messages: [...now.messages, mine], draft: cleared(now), emptyText: "", sendHint: "Sent ✓" };
|
|
216
|
+
}
|
|
217
|
+
} finally {
|
|
218
|
+
sendingNow = false;
|
|
109
219
|
}
|
|
110
220
|
},
|
|
111
221
|
},
|
|
@@ -78,6 +78,23 @@ export default defineApp({
|
|
|
78
78
|
"parentScreenId": "",
|
|
79
79
|
"tabId": "manage"
|
|
80
80
|
},
|
|
81
|
+
{
|
|
82
|
+
"screenId": "himi_owner_lite_inbox",
|
|
83
|
+
"isPublic": false,
|
|
84
|
+
"paramKeys": [],
|
|
85
|
+
"parentScreenId": "",
|
|
86
|
+
"tabId": "inbox"
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"screenId": "himi_owner_lite_inbox_thread",
|
|
90
|
+
"isPublic": false,
|
|
91
|
+
"paramKeys": [
|
|
92
|
+
"id",
|
|
93
|
+
"kind"
|
|
94
|
+
],
|
|
95
|
+
"parentScreenId": "himi_owner_lite_inbox",
|
|
96
|
+
"tabId": "inbox"
|
|
97
|
+
},
|
|
81
98
|
{
|
|
82
99
|
"screenId": "himi_owner_lite_search",
|
|
83
100
|
"isPublic": false,
|
|
@@ -528,6 +545,12 @@ export default defineApp({
|
|
|
528
545
|
"icon": "home",
|
|
529
546
|
"selectedIcon": "home_fill"
|
|
530
547
|
},
|
|
548
|
+
{
|
|
549
|
+
"id": "inbox",
|
|
550
|
+
"screenId": "himi_owner_lite_inbox",
|
|
551
|
+
"label": "Inbox",
|
|
552
|
+
"icon": "chat"
|
|
553
|
+
},
|
|
531
554
|
{
|
|
532
555
|
"id": "aria",
|
|
533
556
|
"screenId": "himi_owner_lite_aria",
|
|
@@ -552,6 +575,12 @@ export default defineApp({
|
|
|
552
575
|
"icon": "home",
|
|
553
576
|
"selectedIcon": "home_fill"
|
|
554
577
|
},
|
|
578
|
+
{
|
|
579
|
+
"id": "inbox",
|
|
580
|
+
"screenId": "himi_owner_lite_inbox",
|
|
581
|
+
"label": "Inbox",
|
|
582
|
+
"icon": "chat"
|
|
583
|
+
},
|
|
555
584
|
{
|
|
556
585
|
"id": "aria",
|
|
557
586
|
"screenId": "himi_owner_lite_aria",
|