@wix/himalaya-cli 0.1378.0 → 0.1380.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/app-info/src/index.d.ts +4 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/ecom/src/extensions.d.ts +113 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/app-info.mjs +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap-checkout.mjs +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +2 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-ecom.mjs +77 -0
- package/dist/cli.mjs +23 -24
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/importable-sdks.md +1 -1
- package/guides/sdk-reference.json +128 -2
- package/guides/wix-api-usage.md +2 -0
- package/package.json +2 -2
- package/templates/beauty-salon/config.ts +23 -0
- package/templates/beauty-salon/dev/index.ts +2611 -1
- package/templates/beauty-salon/himalaya.content.json +9 -0
- package/templates/beauty-salon/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/beauty-salon/tier5-src/_shared/member-access.ts +14 -0
- package/templates/beauty-salon/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/beauty-salon/tier5-src/_shared/member-address.ts +129 -0
- package/templates/beauty-salon/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-address/index.ts +223 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/beauty-salon/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/beauty-salon-owner/dev/index.ts +5 -27
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/branded-lite/config.ts +23 -0
- package/templates/branded-lite/dev/index.ts +2614 -2
- package/templates/branded-lite/himalaya.content.json +9 -0
- package/templates/branded-lite/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/branded-lite/tier5-src/_shared/member-access.ts +14 -0
- package/templates/branded-lite/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/branded-lite/tier5-src/_shared/member-address.ts +129 -0
- package/templates/branded-lite/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-address/index.ts +223 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
- package/templates/branded-lite/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/branded-lite/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/community-club/dev/index.ts +3 -1
- package/templates/community-club/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/community-club/tier5-src/_shared/member-access.ts +14 -0
- package/templates/community-club/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/community-club/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/community-club-owner/dev/index.ts +5 -27
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/community-club-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/community-club-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/events-meetups/dev/index.ts +3 -1
- package/templates/events-meetups/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/events-meetups/tier5-src/_shared/member-access.ts +14 -0
- package/templates/events-meetups/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/events-meetups/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/events-meetups-owner/dev/index.ts +5 -27
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/events-meetups-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/events-meetups-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/gym-fitness/config.ts +23 -0
- package/templates/gym-fitness/dev/index.ts +2611 -1
- package/templates/gym-fitness/himalaya.content.json +9 -0
- package/templates/gym-fitness/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/gym-fitness/tier5-src/_shared/member-access.ts +14 -0
- package/templates/gym-fitness/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/gym-fitness/tier5-src/_shared/member-address.ts +129 -0
- package/templates/gym-fitness/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-address/index.ts +223 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/gym-fitness/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/gym-fitness-owner/dev/index.ts +5 -27
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/nonprofit-cause/config.ts +7 -0
- package/templates/nonprofit-cause/dev/index.ts +762 -1
- package/templates/nonprofit-cause/himalaya.content.json +3 -0
- package/templates/nonprofit-cause/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/nonprofit-cause/tier5-src/_shared/member-access.ts +14 -0
- package/templates/nonprofit-cause/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/nonprofit-cause/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/nonprofit-cause/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/nonprofit-cause-owner/dev/index.ts +5 -27
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/owner-lite/dev/index.ts +5 -27
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/owner-lite/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/owner-lite/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/podcast-creator/config.ts +7 -0
- package/templates/podcast-creator/dev/index.ts +762 -1
- package/templates/podcast-creator/himalaya.content.json +3 -0
- package/templates/podcast-creator/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/podcast-creator/tier5-src/_shared/member-access.ts +14 -0
- package/templates/podcast-creator/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/podcast-creator/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/podcast-creator/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/podcast-creator/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/podcast-creator-owner/dev/index.ts +5 -27
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/portfolio-creative/config.ts +23 -0
- package/templates/portfolio-creative/dev/index.ts +2611 -1
- package/templates/portfolio-creative/himalaya.content.json +9 -0
- package/templates/portfolio-creative/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/portfolio-creative/tier5-src/_shared/member-access.ts +14 -0
- package/templates/portfolio-creative/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/portfolio-creative/tier5-src/_shared/member-address.ts +129 -0
- package/templates/portfolio-creative/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-address/index.ts +223 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/portfolio-creative/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/portfolio-creative-owner/dev/index.ts +5 -27
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/restaurant/config.ts +23 -0
- package/templates/restaurant/dev/index.ts +2611 -1
- package/templates/restaurant/himalaya.content.json +9 -0
- package/templates/restaurant/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/restaurant/tier5-src/_shared/member-access.ts +14 -0
- package/templates/restaurant/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/restaurant/tier5-src/_shared/member-address.ts +129 -0
- package/templates/restaurant/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/restaurant/tier5-src/himi-branded-lite-address/index.ts +223 -0
- package/templates/restaurant/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
- package/templates/restaurant/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/restaurant/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/restaurant-owner/dev/index.ts +5 -27
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/restaurant-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/restaurant-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/retail-store/config.ts +23 -0
- package/templates/retail-store/dev/index.ts +2611 -1
- package/templates/retail-store/himalaya.content.json +9 -0
- package/templates/retail-store/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/retail-store/tier5-src/_shared/member-access.ts +14 -0
- package/templates/retail-store/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/retail-store/tier5-src/_shared/member-address.ts +129 -0
- package/templates/retail-store/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-address/index.ts +223 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-addresses/index.ts +202 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/retail-store/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/retail-store-owner/dev/index.ts +5 -27
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/retail-store-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/retail-store-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
- package/templates/yoga-studio/config.ts +7 -0
- package/templates/yoga-studio/dev/index.ts +762 -1
- package/templates/yoga-studio/himalaya.content.json +3 -0
- package/templates/yoga-studio/tier5-src/_shared/branded-lite-hosted.ts +3 -0
- package/templates/yoga-studio/tier5-src/_shared/member-access.ts +14 -0
- package/templates/yoga-studio/tier5-src/_shared/member-account-routes.ts +43 -0
- package/templates/yoga-studio/tier5-src/_shared/saved-card.ts +20 -0
- package/templates/yoga-studio/tier5-src/himi-branded-lite-wallet/index.ts +217 -0
- package/templates/yoga-studio/tier5-src/himi-store-account/index.ts +17 -0
- package/templates/yoga-studio-owner/dev/index.ts +5 -27
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +99 -40
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-widget-oauth.ts +42 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-widget-view.ts +6 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +12 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
// Tier-5 bundle: Branded Lite — My Wallet (the member's saved payment methods).
|
|
2
|
+
//
|
|
3
|
+
// The cards a member chose to save at checkout, read from Wix Payments on the member's
|
|
4
|
+
// own site (@himalaya/wix-ecom listMySavedPaymentMethods — the native transport routes it there). Like the site's My Wallet
|
|
5
|
+
// page, a member can make one the default for checkout or remove one — there is no
|
|
6
|
+
// "add a card" here: a card is saved while paying. Only the last four digits, the
|
|
7
|
+
// expiry and the cardholder name ever reach the app.
|
|
8
|
+
import { defineService } from "@himalaya/state";
|
|
9
|
+
import {
|
|
10
|
+
deleteSavedPaymentMethod,
|
|
11
|
+
listMySavedPaymentMethods,
|
|
12
|
+
markSavedPaymentMethodPrimary,
|
|
13
|
+
type WixSavedPaymentMethod,
|
|
14
|
+
} from "@himalaya/wix-ecom";
|
|
15
|
+
import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
|
|
16
|
+
import { assertMemberAccess, memberAccess, memberAccessDenied, memberChanged, type MemberAccess } from "../_shared/member-access.js";
|
|
17
|
+
import { memberAccountRoutes } from "../_shared/member-account-routes.js";
|
|
18
|
+
import { cardBrand } from "../_shared/saved-card.js";
|
|
19
|
+
|
|
20
|
+
interface CardRow {
|
|
21
|
+
id: string;
|
|
22
|
+
title: string;
|
|
23
|
+
detail: string;
|
|
24
|
+
isPrimary: boolean;
|
|
25
|
+
notPrimary: boolean;
|
|
26
|
+
confirming: boolean;
|
|
27
|
+
notConfirming: boolean;
|
|
28
|
+
hideActions: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface State {
|
|
32
|
+
status: "loading" | "ready" | "error";
|
|
33
|
+
rows: CardRow[];
|
|
34
|
+
message: string;
|
|
35
|
+
errorText: string;
|
|
36
|
+
needsSignIn: boolean;
|
|
37
|
+
unavailable: boolean;
|
|
38
|
+
noUnavailable: boolean;
|
|
39
|
+
isLoading: boolean;
|
|
40
|
+
notLoading: boolean;
|
|
41
|
+
notErrored: boolean;
|
|
42
|
+
noSignIn: boolean;
|
|
43
|
+
hideList: boolean;
|
|
44
|
+
noEmpty: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let member: MemberAccess | undefined;
|
|
48
|
+
let methods: WixSavedPaymentMethod[] = [];
|
|
49
|
+
let confirmId = "";
|
|
50
|
+
let busy = false;
|
|
51
|
+
|
|
52
|
+
function rowOf(method: WixSavedPaymentMethod, now: Date): CardRow {
|
|
53
|
+
const id = method.id ?? "";
|
|
54
|
+
const card = method.paymentMethod?.cardInfo;
|
|
55
|
+
const last4 = card?.lastFourDigits?.trim() ?? "";
|
|
56
|
+
const isPayPal = (method.paymentMethod?.typeId ?? "").toLowerCase() === "paypal";
|
|
57
|
+
const name = isPayPal ? "PayPal" : cardBrand(card?.bin ?? "") || copyParam("wallet.cardLabel", "Card");
|
|
58
|
+
const title = last4 ? `${name} •••• ${last4}` : name;
|
|
59
|
+
const month = card?.expirationMonth, year = card?.expirationYear;
|
|
60
|
+
let expiry = "";
|
|
61
|
+
if (month && year) {
|
|
62
|
+
const mmyy = `${String(month).padStart(2, "0")}/${String(year).slice(-2)}`;
|
|
63
|
+
// A card is good through the last day of its expiry month.
|
|
64
|
+
const expired = year < now.getFullYear() || (year === now.getFullYear() && month < now.getMonth() + 1);
|
|
65
|
+
expiry = expired
|
|
66
|
+
? copyTemplate("wallet.expiredLabel", "Expired {date}", { date: mmyy })
|
|
67
|
+
: copyTemplate("wallet.expiresLabel", "Expires {date}", { date: mmyy });
|
|
68
|
+
}
|
|
69
|
+
const isPrimary = method.primary === true;
|
|
70
|
+
const confirming = id === confirmId;
|
|
71
|
+
return {
|
|
72
|
+
id,
|
|
73
|
+
title,
|
|
74
|
+
detail: [expiry, card?.cardholderName?.trim()].filter(Boolean).join(" · "),
|
|
75
|
+
isPrimary,
|
|
76
|
+
notPrimary: !isPrimary,
|
|
77
|
+
confirming,
|
|
78
|
+
notConfirming: !confirming,
|
|
79
|
+
hideActions: confirming,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function derive(state: State): State {
|
|
84
|
+
const ready = state.status === "ready" && !state.unavailable;
|
|
85
|
+
const now = new Date();
|
|
86
|
+
// The default card leads, like the site's own page.
|
|
87
|
+
const ordered = [...methods].sort((a, b) => Number(b.primary === true) - Number(a.primary === true));
|
|
88
|
+
const rows = ready ? ordered.filter((m) => !!m.id).map((m) => rowOf(m, now)) : [];
|
|
89
|
+
return {
|
|
90
|
+
...state,
|
|
91
|
+
rows,
|
|
92
|
+
noUnavailable: !state.unavailable,
|
|
93
|
+
isLoading: state.status === "loading",
|
|
94
|
+
notLoading: state.status !== "loading",
|
|
95
|
+
notErrored: state.status !== "error",
|
|
96
|
+
noSignIn: !ready || !state.needsSignIn,
|
|
97
|
+
hideList: !ready || state.needsSignIn || rows.length === 0,
|
|
98
|
+
noEmpty: !ready || state.needsSignIn || rows.length > 0,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const INITIAL: State = derive({
|
|
103
|
+
status: "loading", rows: [], message: "", errorText: "", needsSignIn: false, unavailable: false, noUnavailable: true,
|
|
104
|
+
isLoading: true, notLoading: false, notErrored: true, noSignIn: true, hideList: true, noEmpty: true,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// 100 per page is the service maximum; the page cap only stops a cursor that never ends.
|
|
108
|
+
const MAX_PAGES = 10;
|
|
109
|
+
|
|
110
|
+
async function allSavedPaymentMethods(): Promise<WixSavedPaymentMethod[]> {
|
|
111
|
+
const all: WixSavedPaymentMethod[] = [];
|
|
112
|
+
let cursor = "";
|
|
113
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
114
|
+
const response = await listMySavedPaymentMethods({ limit: 100, ...(cursor ? { cursor } : {}) });
|
|
115
|
+
all.push(...(response.savedPaymentMethods ?? []));
|
|
116
|
+
const next = response.pagingMetadata?.cursors?.next ?? "";
|
|
117
|
+
if (response.pagingMetadata?.hasNext === false || !next || next === cursor) break;
|
|
118
|
+
cursor = next;
|
|
119
|
+
}
|
|
120
|
+
return all;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function load(state: State, arg?: unknown): Promise<State> {
|
|
124
|
+
stashCopy(arg);
|
|
125
|
+
confirmId = "";
|
|
126
|
+
if (!(await memberAccountRoutes()).wallet) {
|
|
127
|
+
methods = [];
|
|
128
|
+
return derive({ ...state, status: "ready", unavailable: true, needsSignIn: false, message: "", errorText: "" });
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
member = await memberAccess();
|
|
132
|
+
if (!member.email) {
|
|
133
|
+
methods = [];
|
|
134
|
+
return derive({ ...state, status: "ready", unavailable: false, needsSignIn: true, message: "", errorText: "" });
|
|
135
|
+
}
|
|
136
|
+
const all = await allSavedPaymentMethods();
|
|
137
|
+
await assertMemberAccess(member);
|
|
138
|
+
methods = all;
|
|
139
|
+
// A fresh load drops the last write's confirmation; write() sets it again after this.
|
|
140
|
+
return derive({ ...state, status: "ready", unavailable: false, needsSignIn: false, message: "", errorText: "" });
|
|
141
|
+
} catch (error) {
|
|
142
|
+
methods = [];
|
|
143
|
+
return derive({
|
|
144
|
+
...state,
|
|
145
|
+
status: "error",
|
|
146
|
+
unavailable: false,
|
|
147
|
+
message: "",
|
|
148
|
+
errorText: memberAccessDenied(error)
|
|
149
|
+
? copyParam("wallet.accessErrorText", "Your wallet is unavailable for this account. Try signing in again.")
|
|
150
|
+
: copyParam("wallet.loadErrorText", "Your saved cards could not be loaded."),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function argId(arg: unknown): string {
|
|
156
|
+
const id = (arg as { id?: unknown } | null)?.id;
|
|
157
|
+
return typeof id === "string" ? id : "";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function write(state: State, run: () => Promise<unknown>, done: string, failed: string): Promise<State> {
|
|
161
|
+
if (busy || !member?.email) return state;
|
|
162
|
+
busy = true;
|
|
163
|
+
try {
|
|
164
|
+
await assertMemberAccess(member);
|
|
165
|
+
await run();
|
|
166
|
+
const next = await load(state);
|
|
167
|
+
return next.status === "ready" ? { ...next, message: done } : next;
|
|
168
|
+
} catch (error) {
|
|
169
|
+
if (await memberChanged(member, error)) return load(state);
|
|
170
|
+
return derive({ ...state, message: failed });
|
|
171
|
+
} finally {
|
|
172
|
+
busy = false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const svc = defineService<State, {
|
|
177
|
+
load: (state: State, arg?: unknown) => Promise<State>;
|
|
178
|
+
retry: (state: State) => Promise<State>;
|
|
179
|
+
setPrimary: (state: State, arg?: unknown) => Promise<State>;
|
|
180
|
+
askRemove: (state: State, arg?: unknown) => State;
|
|
181
|
+
keep: (state: State) => State;
|
|
182
|
+
remove: (state: State, arg?: unknown) => Promise<State>;
|
|
183
|
+
}>("himiBrandedWallet", {
|
|
184
|
+
state: INITIAL,
|
|
185
|
+
actions: {
|
|
186
|
+
load,
|
|
187
|
+
retry: (state) => load(derive({ ...state, status: "loading", message: "" })),
|
|
188
|
+
setPrimary: (state, arg) => {
|
|
189
|
+
const id = argId(arg);
|
|
190
|
+
const method = methods.find((m) => m.id === id);
|
|
191
|
+
if (!id || !method || method.primary) return Promise.resolve(state);
|
|
192
|
+
return write(state, () => markSavedPaymentMethodPrimary(id),
|
|
193
|
+
copyParam("wallet.primaryDone", "Default card updated."),
|
|
194
|
+
copyParam("wallet.primaryFailed", "The default card could not be changed. Try again."));
|
|
195
|
+
},
|
|
196
|
+
askRemove: (state, arg) => {
|
|
197
|
+
const id = argId(arg);
|
|
198
|
+
if (!methods.some((m) => m.id === id)) return state;
|
|
199
|
+
confirmId = id;
|
|
200
|
+
return derive({ ...state, message: "" });
|
|
201
|
+
},
|
|
202
|
+
keep: (state) => {
|
|
203
|
+
confirmId = "";
|
|
204
|
+
return derive(state);
|
|
205
|
+
},
|
|
206
|
+
remove: (state, arg) => {
|
|
207
|
+
// Only the card the member is confirming — a stale row tap removes nothing.
|
|
208
|
+
const id = argId(arg);
|
|
209
|
+
if (!id || id !== confirmId) return Promise.resolve(state);
|
|
210
|
+
return write(state, () => deleteSavedPaymentMethod(id),
|
|
211
|
+
copyParam("wallet.removeDone", "Card removed."),
|
|
212
|
+
copyParam("wallet.removeFailed", "The card could not be removed. Try again."));
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export { svc };
|
|
@@ -13,6 +13,7 @@ import { dispatch } from "@himalaya/dispatch";
|
|
|
13
13
|
import { searchOrders, type WixOrder } from "@himalaya/wix-ecom";
|
|
14
14
|
import { guestProfile, loadMemberProfile, strOf, fmtDate, titleCase, type AccountActivityRow } from "../_shared/himi-account";
|
|
15
15
|
import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
|
|
16
|
+
import { memberAccountRoutes } from "../_shared/member-account-routes.js";
|
|
16
17
|
|
|
17
18
|
interface AuthRouteInput {
|
|
18
19
|
intent?: string;
|
|
@@ -205,6 +206,14 @@ interface S {
|
|
|
205
206
|
activityEmptyText: string;
|
|
206
207
|
hasActivity: boolean;
|
|
207
208
|
noActivity: boolean;
|
|
209
|
+
/** Set once a descriptor asks for the member-account links (appear param
|
|
210
|
+
* `memberAccountLinks: "1"`). Only then does the probe read app_info, so a descriptor
|
|
211
|
+
* that renders no such link need not declare that capability. */
|
|
212
|
+
memberAccountLinks: boolean;
|
|
213
|
+
/** Hides My Addresses where this runtime cannot reach the address book. */
|
|
214
|
+
noMemberAddresses: boolean;
|
|
215
|
+
/** Hides My Wallet where this runtime cannot reach saved cards. */
|
|
216
|
+
noMemberWallet: boolean;
|
|
208
217
|
}
|
|
209
218
|
|
|
210
219
|
function derive(s: S): S {
|
|
@@ -278,6 +287,8 @@ async function orderRows(memberEmail: string): Promise<AccountActivityRow[]> {
|
|
|
278
287
|
async function probe(s: S, arg?: unknown): Promise<S> {
|
|
279
288
|
const intent = authIntentFromArg(arg, s);
|
|
280
289
|
const profile = await loadMemberProfile(copyParam("storeAccount.guestDetail", "Sign in to keep your cart and orders connected."));
|
|
290
|
+
const memberAccountLinks = s.memberAccountLinks || strField(arg, "memberAccountLinks") === "1";
|
|
291
|
+
const routes = memberAccountLinks ? await memberAccountRoutes() : { addresses: false, wallet: false };
|
|
281
292
|
let activityRows: AccountActivityRow[] = [];
|
|
282
293
|
if (profile.isMember) {
|
|
283
294
|
try {
|
|
@@ -301,6 +312,9 @@ async function probe(s: S, arg?: unknown): Promise<S> {
|
|
|
301
312
|
noMemberImage: profile.noMemberImage,
|
|
302
313
|
email: profile.memberEmail || s.email,
|
|
303
314
|
message: "",
|
|
315
|
+
memberAccountLinks,
|
|
316
|
+
noMemberAddresses: !routes.addresses,
|
|
317
|
+
noMemberWallet: !routes.wallet,
|
|
304
318
|
activityRows,
|
|
305
319
|
activityTitle: copyParam("storeAccount.activityTitle", "Previous orders"),
|
|
306
320
|
activitySummary: activityRows.length
|
|
@@ -394,6 +408,9 @@ const svc = defineService<S, {
|
|
|
394
408
|
activityEmptyText: "No previous orders for this member yet.",
|
|
395
409
|
hasActivity: false,
|
|
396
410
|
noActivity: true,
|
|
411
|
+
memberAccountLinks: false,
|
|
412
|
+
noMemberAddresses: true,
|
|
413
|
+
noMemberWallet: true,
|
|
397
414
|
}),
|
|
398
415
|
actions: {
|
|
399
416
|
load: (s, arg) => { stashCopy(arg); return probe(s, arg); },
|
|
@@ -60867,22 +60867,6 @@ const SCREENS = runtimeSetting({
|
|
|
60867
60867
|
"props": {
|
|
60868
60868
|
"spacing": 8,
|
|
60869
60869
|
"children": [
|
|
60870
|
-
{
|
|
60871
|
-
"id": "",
|
|
60872
|
-
"body": {
|
|
60873
|
-
"package": "himalaya",
|
|
60874
|
-
"type": "Text",
|
|
60875
|
-
"props": {
|
|
60876
|
-
"text": "$service:himiOwnerLiteAria.widget.badge",
|
|
60877
|
-
"style": "footnote",
|
|
60878
|
-
"color": "textSecondary",
|
|
60879
|
-
"maxLines": 12
|
|
60880
|
-
}
|
|
60881
|
-
},
|
|
60882
|
-
"modifiers": {
|
|
60883
|
-
"hiddenPath": "$service:himiOwnerLiteAria.widget.badgeHidden"
|
|
60884
|
-
}
|
|
60885
|
-
},
|
|
60886
60870
|
{
|
|
60887
60871
|
"id": "",
|
|
60888
60872
|
"body": {
|
|
@@ -60919,21 +60903,21 @@ const SCREENS = runtimeSetting({
|
|
|
60919
60903
|
"package": "himalaya",
|
|
60920
60904
|
"type": "Text",
|
|
60921
60905
|
"props": {
|
|
60922
|
-
"text": "$service:himiOwnerLiteAria.widget.
|
|
60906
|
+
"text": "$service:himiOwnerLiteAria.widget.notice",
|
|
60923
60907
|
"style": "footnote",
|
|
60924
60908
|
"color": "textSecondary",
|
|
60925
60909
|
"maxLines": 12
|
|
60926
60910
|
}
|
|
60927
60911
|
},
|
|
60928
60912
|
"modifiers": {
|
|
60929
|
-
"hiddenPath": "$service:himiOwnerLiteAria.widget.
|
|
60913
|
+
"hiddenPath": "$service:himiOwnerLiteAria.widget.noticeHidden"
|
|
60930
60914
|
}
|
|
60931
60915
|
},
|
|
60932
60916
|
{
|
|
60933
60917
|
"id": "himi_owner_lite_aria.widget.OAUTH_REQUIRED.footer",
|
|
60934
60918
|
"body": {
|
|
60935
60919
|
"package": "himalaya",
|
|
60936
|
-
"type": "
|
|
60920
|
+
"type": "VStack",
|
|
60937
60921
|
"props": {
|
|
60938
60922
|
"spacing": 8,
|
|
60939
60923
|
"children": [
|
|
@@ -60945,6 +60929,7 @@ const SCREENS = runtimeSetting({
|
|
|
60945
60929
|
"props": {
|
|
60946
60930
|
"title": "$service:himiOwnerLiteAria.widget.primaryLabel",
|
|
60947
60931
|
"style": "primary",
|
|
60932
|
+
"fullWidth": true,
|
|
60948
60933
|
"action": {
|
|
60949
60934
|
"actionId": "tier5.himiOwnerLiteAria.widgetPrimary"
|
|
60950
60935
|
}
|
|
@@ -60962,6 +60947,7 @@ const SCREENS = runtimeSetting({
|
|
|
60962
60947
|
"props": {
|
|
60963
60948
|
"title": "$service:himiOwnerLiteAria.widget.secondaryLabel",
|
|
60964
60949
|
"style": "secondary",
|
|
60950
|
+
"fullWidth": true,
|
|
60965
60951
|
"action": {
|
|
60966
60952
|
"actionId": "tier5.himiOwnerLiteAria.widgetSecondary"
|
|
60967
60953
|
}
|
|
@@ -60970,14 +60956,6 @@ const SCREENS = runtimeSetting({
|
|
|
60970
60956
|
"modifiers": {
|
|
60971
60957
|
"hiddenPath": "$service:himiOwnerLiteAria.widget.secondaryHidden"
|
|
60972
60958
|
}
|
|
60973
|
-
},
|
|
60974
|
-
{
|
|
60975
|
-
"id": "",
|
|
60976
|
-
"body": {
|
|
60977
|
-
"package": "himalaya",
|
|
60978
|
-
"type": "Spacer",
|
|
60979
|
-
"props": {}
|
|
60980
|
-
}
|
|
60981
60959
|
}
|
|
60982
60960
|
]
|
|
60983
60961
|
}
|
|
@@ -88,6 +88,7 @@ import {
|
|
|
88
88
|
type WidgetOptionRow,
|
|
89
89
|
type WidgetView,
|
|
90
90
|
} from "./aria-widget-view.js";
|
|
91
|
+
import { oauthView, DEFAULT_OAUTH_COPY, OAUTH_AUTHORIZED_NOTE, OAUTH_DECLINED_NOTE, type OAuthCopy } from "./aria-widget-oauth.js";
|
|
91
92
|
import { substituteSelection } from "./aria-widget-model.js";
|
|
92
93
|
import { MENTION_PAGE_SIZE, allowlisted, emptyMentionModel, mentionView,
|
|
93
94
|
type MentionItemRow, type MentionLabels, type MentionModel, type MentionProvider, type MentionView } from "./aria-mentions.js";
|
|
@@ -468,6 +469,7 @@ export interface AriaChatLabels {
|
|
|
468
469
|
mentionItemsFailed?: (category: string) => string;
|
|
469
470
|
/** Shown when a response widget's item provider cannot be queried. */
|
|
470
471
|
widgetItemsFailed?: Resolvable<string>;
|
|
472
|
+
oauth?: Partial<Record<keyof OAuthCopy, Resolvable<string>>>;
|
|
471
473
|
/** Shown when this host cannot reach Wix's item provider service. */
|
|
472
474
|
widgetItemsUnavailable?: Resolvable<string>;
|
|
473
475
|
/** Attachment copy — see `AriaChatOptions.attachments`. Chip states: */
|
|
@@ -994,13 +996,23 @@ function withAttachments(content: string, names: readonly string[]): { content:
|
|
|
994
996
|
return { content, attachText, attachHidden: !attachText };
|
|
995
997
|
}
|
|
996
998
|
|
|
999
|
+
/** Only OAuth protocol suffixes are hidden in owner replies. Ordinary owner text
|
|
1000
|
+
* stays literal; apply this to optimistic rows and their later server echoes. */
|
|
1001
|
+
function visibleOwnerReply(content: string): string {
|
|
1002
|
+
for (const note of [OAUTH_AUTHORIZED_NOTE, OAUTH_DECLINED_NOTE]) {
|
|
1003
|
+
const suffix = `<HIDDEN>${note}</HIDDEN>`;
|
|
1004
|
+
if (content.endsWith(suffix)) return content.slice(0, -suffix.length).trimEnd();
|
|
1005
|
+
}
|
|
1006
|
+
return content;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
997
1009
|
/** A greeting or optimistic bubble: no links, all slots defaulted. `attached`
|
|
998
1010
|
* names the files an optimistic USER bubble is being sent with, and `refs` is
|
|
999
1011
|
* its mention line, already formatted. */
|
|
1000
1012
|
export function plainBubble(
|
|
1001
1013
|
id: string, role: string, who: string, content: string, attached: readonly string[] = [], refs = "",
|
|
1002
1014
|
): AriaBubble {
|
|
1003
|
-
const a = withAttachments(content, attached);
|
|
1015
|
+
const a = withAttachments(role === "USER" ? visibleOwnerReply(content) : content, attached);
|
|
1004
1016
|
return { id, role, who, notUser: role !== "USER", notAria: role === "USER", spoken: spokenOf(who, a.content),
|
|
1005
1017
|
refs, refsHidden: !refs, ...linkFieldsOf("", role), ...a };
|
|
1006
1018
|
}
|
|
@@ -1086,7 +1098,7 @@ function bubbleOf(
|
|
|
1086
1098
|
// A row the service is still writing is scanned as STREAMING: an
|
|
1087
1099
|
// envelope whose `>>` has not arrived yet is held back rather than
|
|
1088
1100
|
// painted as raw JSON until the row finishes.
|
|
1089
|
-
const prose = role === "USER" ? raw : assistantProse(raw, !writing);
|
|
1101
|
+
const prose = role === "USER" ? visibleOwnerReply(raw) : assistantProse(raw, !writing);
|
|
1090
1102
|
const content = role === "USER" ? prose : stripMarkdown(prose);
|
|
1091
1103
|
// A turn that is only attachments still happened, and dropping it would hide
|
|
1092
1104
|
// what the owner sent: `withAttachments` makes the file line its content.
|
|
@@ -1512,7 +1524,7 @@ export interface AriaChatCore {
|
|
|
1512
1524
|
* region is `$service:`-bound and has no item, and the worker already holds
|
|
1513
1525
|
* the value anyway — so the descriptor names the action and nothing else,
|
|
1514
1526
|
* which is also one less place for a model-supplied url to travel through. */
|
|
1515
|
-
widgetPrimary(s: AriaChatState): Promise<AriaChatState>;
|
|
1527
|
+
widgetPrimary(s: AriaChatState): Promise<AriaChatState | undefined>;
|
|
1516
1528
|
widgetSecondary(s: AriaChatState): Promise<AriaChatState>;
|
|
1517
1529
|
/** Toggle one option of a MULTI_SELECT-style widget. Synchronous: it is a
|
|
1518
1530
|
* state edit, and making it async would put a turn boundary where there is
|
|
@@ -1651,6 +1663,16 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
1651
1663
|
o.labels?.dictationEmpty ?? "Didn't catch that. Check microphone access and try again.",
|
|
1652
1664
|
);
|
|
1653
1665
|
const dictationFailedText = () => res(o.labels?.dictationFailed ?? "Dictation isn't available right now.");
|
|
1666
|
+
const oauthCopy = (): OAuthCopy => Object.fromEntries(
|
|
1667
|
+
Object.entries(DEFAULT_OAUTH_COPY).map(([key, fallback]) =>
|
|
1668
|
+
[key, res(o.labels?.oauth?.[key as keyof OAuthCopy] ?? fallback)]),
|
|
1669
|
+
) as unknown as OAuthCopy;
|
|
1670
|
+
let oauthKey = "";
|
|
1671
|
+
let oauthSeq = 0;
|
|
1672
|
+
/** The identity of the OAuth ask on screen: its message and its URL. */
|
|
1673
|
+
const oauthKeyOf = (w: WidgetView): string => w.kind === "OAUTH_REQUIRED"
|
|
1674
|
+
? `${w.sourceMessageId ?? ""}\0${w.oauthAuthorizationUrl ?? ""}` : "";
|
|
1675
|
+
let oauthOpening = "";
|
|
1654
1676
|
const widgetItemsFailedText = () => res(o.labels?.widgetItemsFailed ?? "Couldn't load these items.");
|
|
1655
1677
|
const widgetItemsUnavailableText = () => res(o.labels?.widgetItemsUnavailable ?? "Items aren't available here.");
|
|
1656
1678
|
const attachUploadingText = () => res(o.labels?.attachUploading ?? "Uploading…");
|
|
@@ -2522,6 +2544,10 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2522
2544
|
// transcript row itself is unchanged.
|
|
2523
2545
|
if (!next.sourceMessageId || prev.sourceMessageId !== next.sourceMessageId) return next;
|
|
2524
2546
|
if (prev.kind !== next.kind) return next;
|
|
2547
|
+
if (next.kind === "OAUTH_REQUIRED" && prev.oauthAuthorizationUrl === next.oauthAuthorizationUrl) {
|
|
2548
|
+
return { ...oauthView(next, next.oauthAuthorizationUrl ?? "", disclosedLabel, oauthCopy(), prev.oauthPhase),
|
|
2549
|
+
notice: prev.notice, noticeHidden: prev.noticeHidden };
|
|
2550
|
+
}
|
|
2525
2551
|
// Keep the latest service status while this exact job and transcript row
|
|
2526
2552
|
// remain visible. Rebuilding from the envelope must not restart a terminal
|
|
2527
2553
|
// job or replace progress with its initial status message.
|
|
@@ -2634,36 +2660,43 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2634
2660
|
const localized = (w: WidgetView): WidgetView =>
|
|
2635
2661
|
w.kind === "QUESTIONNAIRE"
|
|
2636
2662
|
? questionnaireSynced(w, w.questionIndex, questionnaireCopy(), !w.answersTextHidden)
|
|
2637
|
-
: w
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2663
|
+
: w.kind === "OAUTH_REQUIRED"
|
|
2664
|
+
? oauthView(w, w.oauthAuthorizationUrl ?? "", disclosedLabel, oauthCopy(), w.oauthPhase)
|
|
2665
|
+
: w;
|
|
2666
|
+
|
|
2667
|
+
const observed = (msgs: ConversationMessage[], prev?: WidgetView) => {
|
|
2668
|
+
const result = {
|
|
2669
|
+
_srvUsers: countTurns(msgs, "USER"),
|
|
2670
|
+
_srvAssistants: countTurns(msgs, "ASSISTANT"),
|
|
2671
|
+
// Derived HERE, with the counts, because this is the one expression every
|
|
2672
|
+
// transcript rebuild already spreads — load, resume, tick and both newChat
|
|
2673
|
+
// arms. That is what makes the widget impossible to leave stale: a rebuild
|
|
2674
|
+
// that forgot to clear it would also have to have forgotten the counts,
|
|
2675
|
+
// which the reply-detection depends on. `observed([])` (new chat) yields
|
|
2676
|
+
// NO_WIDGET for free.
|
|
2677
|
+
// Content is normalized HERE, not in the view: `messageText` decodes the
|
|
2678
|
+
// `{ text: … }` shape the service also sends (treated as empty before,
|
|
2679
|
+
// so those widgets never appeared at all), and `stripHidden` removes
|
|
2680
|
+
// `<HIDDEN>` spans — which are transcript-only, and were promoting hidden
|
|
2681
|
+
// envelopes into live interactive cards. The transcript path already did
|
|
2682
|
+
// both; the widget path did neither.
|
|
2683
|
+
widget: carrySelection(
|
|
2684
|
+
localized(activeWidgetOf(
|
|
2685
|
+
// A draft offers no controls: the agent has not finished asking, and
|
|
2686
|
+
// an answer tapped now would start a second turn on top of this one.
|
|
2687
|
+
// An empty newest row is NO_WIDGET — the placeholder rule.
|
|
2688
|
+
msgs.map((m) => ({ id: m.id, role: m.role, content: stillWriting(m) ? "" : stripHidden(messageText(m)) })),
|
|
2689
|
+
disclosedLabel,
|
|
2690
|
+
o.subtree,
|
|
2691
|
+
(url) => isWixMediaHost(hostOf(url)),
|
|
2692
|
+
)),
|
|
2693
|
+
prev,
|
|
2694
|
+
),
|
|
2695
|
+
};
|
|
2696
|
+
const key = oauthKeyOf(result.widget);
|
|
2697
|
+
if (key !== oauthKey) { oauthKey = key; oauthSeq++; }
|
|
2698
|
+
return result;
|
|
2699
|
+
};
|
|
2667
2700
|
|
|
2668
2701
|
const greetingOnly = (): AriaBubble[] => [plainBubble(GREETING_ID, "ASSISTANT", assistant(), res(o.greeting))];
|
|
2669
2702
|
|
|
@@ -4028,12 +4061,38 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
4028
4061
|
// The url wins when both are present: a button that names a destination
|
|
4029
4062
|
// should go there, and `respondText` beside it is the fallback the
|
|
4030
4063
|
// service sends for clients that cannot open one.
|
|
4031
|
-
widgetPrimary: (s) =>
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4064
|
+
widgetPrimary: async (s) => {
|
|
4065
|
+
const w = s.widget;
|
|
4066
|
+
if (w.kind !== "OAUTH_REQUIRED") return w.primaryUrl
|
|
4067
|
+
? openWidgetUrl(s, w.primaryUrl) : impl.widgetRespond(s, { value: "primary" });
|
|
4068
|
+
if (w.primaryHidden || !w.oauthAuthorizationUrl || s._pending) return s;
|
|
4069
|
+
if (w.oauthPhase === "authorizing") {
|
|
4070
|
+
oauthSeq++;
|
|
4071
|
+
return impl.widgetRespond(s, { value: "primary" });
|
|
4072
|
+
}
|
|
4073
|
+
// Key from the card itself: a worker rebuilt over persisted state restores
|
|
4074
|
+
// the card without a transcript rebuild, so the module-local key is still
|
|
4075
|
+
// empty and the in-flight guard below would swallow the first Authorize.
|
|
4076
|
+
const key = oauthKeyOf(w);
|
|
4077
|
+
if (key !== oauthKey) { oauthKey = key; oauthSeq++; }
|
|
4078
|
+
if (oauthOpening === key) return undefined;
|
|
4079
|
+
oauthOpening = key;
|
|
4080
|
+
const seq = ++oauthSeq, turn = turnSeq, draft = draftSeq;
|
|
4081
|
+
let opened = false;
|
|
4082
|
+
try { opened = !!(await o.openLink?.(w.oauthAuthorizationUrl)); } catch { /* shown below */ }
|
|
4083
|
+
finally { if (oauthOpening === key) oauthOpening = ""; }
|
|
4084
|
+
// Never merge an old action snapshot over a newer conversation, widget
|
|
4085
|
+
// or composer edit. An external browser opening can take arbitrarily long.
|
|
4086
|
+
if (seq !== oauthSeq || key !== oauthKey || turn !== turnSeq || draft !== draftSeq) return undefined;
|
|
4087
|
+
return opened
|
|
4088
|
+
? { ...s, widget: oauthView(w, w.oauthAuthorizationUrl, disclosedLabel, oauthCopy(), "authorizing") }
|
|
4089
|
+
: { ...s, widget: { ...w, notice: oauthCopy().openFailed, noticeHidden: false } };
|
|
4090
|
+
},
|
|
4091
|
+
widgetSecondary: (s) => {
|
|
4092
|
+
if (s.widget.kind === "OAUTH_REQUIRED") oauthSeq++;
|
|
4093
|
+
return s.widget.secondaryUrl ? openWidgetUrl(s, s.widget.secondaryUrl)
|
|
4094
|
+
: impl.widgetRespond(s, { value: "secondary" });
|
|
4095
|
+
},
|
|
4037
4096
|
|
|
4038
4097
|
dictate: (s) => {
|
|
4039
4098
|
if (!o.dictation || s.listening) return s;
|
|
@@ -4478,7 +4537,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
4478
4537
|
widgetAnswer: (s, arg) => gated(impl.widgetAnswer(s, arg)),
|
|
4479
4538
|
widgetStep: (s, arg) => gated(impl.widgetStep(s, arg)),
|
|
4480
4539
|
widgetRespond: (s, arg) => impl.widgetRespond(s, arg).then(gated),
|
|
4481
|
-
widgetPrimary: (s) => impl.widgetPrimary(s).then(gated),
|
|
4540
|
+
widgetPrimary: (s) => impl.widgetPrimary(s).then((r) => r ? gated(r) : r),
|
|
4482
4541
|
widgetSecondary: (s) => impl.widgetSecondary(s).then(gated),
|
|
4483
4542
|
dictate: (s) => gated(impl.dictate(s)),
|
|
4484
4543
|
finishDictation: (s) => gated(impl.finishDictation(s)),
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Disclose, WidgetView } from "./aria-widget-view.js";
|
|
2
|
+
import { isWebUrl } from "./aria-widget-model.js";
|
|
3
|
+
|
|
4
|
+
export interface OAuthCopy {
|
|
5
|
+
title: string; body: string; pending: string; authorize: string; decline: string;
|
|
6
|
+
done: string; authorized: string; declined: string; openFailed: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const DEFAULT_OAUTH_COPY: OAuthCopy = {
|
|
10
|
+
title: "Connect an account", body: "Authorize access to continue this task.",
|
|
11
|
+
pending: "Finish authorization in your browser, then return and choose Done.",
|
|
12
|
+
authorize: "Authorize", decline: "Decline", done: "Done",
|
|
13
|
+
authorized: "Authorized", declined: "Declined",
|
|
14
|
+
openFailed: "Couldn't open authorization. Try again or decline.",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// These instructions are wire content from the native Aria client, not copy.
|
|
18
|
+
export const OAUTH_AUTHORIZED_NOTE = "The user completed the OAuth authorization flow. Please retry the previous tool call.";
|
|
19
|
+
export const OAUTH_DECLINED_NOTE = "The user declined OAuth authorization. Do not retry the tool.";
|
|
20
|
+
|
|
21
|
+
/** A browser-open result is not an OAuth callback. Only the owner's subsequent
|
|
22
|
+
* Done action sends completion. The destination is disclosed by the same helper
|
|
23
|
+
* as every other widget URL; no guessed server callback is invoked. */
|
|
24
|
+
export function oauthView(
|
|
25
|
+
view: WidgetView, rawUrl: string, disclose: Disclose,
|
|
26
|
+
copy: OAuthCopy = DEFAULT_OAUTH_COPY, phase: "pending" | "authorizing" = "pending",
|
|
27
|
+
): WidgetView {
|
|
28
|
+
const url = isWebUrl(rawUrl.trim()) ? rawUrl.trim() : "";
|
|
29
|
+
const authorizing = phase === "authorizing" && !!url;
|
|
30
|
+
return {
|
|
31
|
+
...view, notice: "", noticeHidden: true, hidden: false, oauthPhase: authorizing ? "authorizing" : "pending",
|
|
32
|
+
oauthAuthorizationUrl: url,
|
|
33
|
+
title: copy.title, titleHidden: !copy.title,
|
|
34
|
+
text: authorizing ? copy.pending : copy.body, textHidden: false,
|
|
35
|
+
primaryLabel: authorizing ? copy.done : disclose({ label: copy.authorize, url }),
|
|
36
|
+
primaryUrl: authorizing ? "" : url,
|
|
37
|
+
primaryHidden: !url, primaryDisabled: false, primaryEnabled: true,
|
|
38
|
+
primaryRespondText: authorizing ? `${copy.authorized}\n<HIDDEN>${OAUTH_AUTHORIZED_NOTE}</HIDDEN>` : "",
|
|
39
|
+
secondaryLabel: copy.decline, secondaryHidden: false, secondaryUrl: "",
|
|
40
|
+
secondaryRespondText: `${copy.declined}\n<HIDDEN>${OAUTH_DECLINED_NOTE}</HIDDEN>`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import type { NormalizedWidget, WidgetKind, WidgetOption } from "./aria-widget-model.js";
|
|
16
16
|
import { SELECTED_TOKEN, VALUES_TOKEN, isWebUrl, stripMarkdown } from "./aria-widget-model.js";
|
|
17
|
+
import { oauthView } from "./aria-widget-oauth.js";
|
|
17
18
|
import { composeGenericCard } from "./aria-generic-widget.js";
|
|
18
19
|
import { normalizeWidget } from "./aria-widget-kinds.js";
|
|
19
20
|
import { scanEnvelopes, widgetsOfParts } from "./aria-widgets.js";
|
|
@@ -127,6 +128,8 @@ export interface WidgetStep {
|
|
|
127
128
|
/** The active widget, as the screen reads it. */
|
|
128
129
|
export interface WidgetView {
|
|
129
130
|
kind: WidgetCase;
|
|
131
|
+
oauthPhase?: "pending" | "authorizing";
|
|
132
|
+
oauthAuthorizationUrl?: string;
|
|
130
133
|
/** Transcript row that owns this widget; prevents state carrying into a later
|
|
131
134
|
* identical-looking ask. */
|
|
132
135
|
sourceMessageId?: string;
|
|
@@ -1021,6 +1024,9 @@ export function viewOf(
|
|
|
1021
1024
|
// A questionnaire is shown one question at a time; its on-screen fields are
|
|
1022
1025
|
// derived, never stored, so they start from the same function every later
|
|
1023
1026
|
// change goes through. The worker re-derives with its own copy.
|
|
1027
|
+
if (w.kind === "OAUTH_REQUIRED") {
|
|
1028
|
+
return oauthView(view, typeof extra.authorizationUrl === "string" ? extra.authorizationUrl : "", disclose);
|
|
1029
|
+
}
|
|
1024
1030
|
return w.kind === "QUESTIONNAIRE"
|
|
1025
1031
|
? questionnaireSynced(view, 0, DEFAULT_QUESTIONNAIRE_COPY, false)
|
|
1026
1032
|
: view;
|