viveworker 0.7.0-beta.2 → 0.7.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/README.md +10 -0
- package/package.json +2 -1
- package/scripts/share-cli.mjs +553 -3
- package/scripts/viveworker-bridge.mjs +1147 -148
- package/viveworker.env.example +4 -0
- package/web/app.css +126 -4
- package/web/app.js +207 -57
- package/web/i18n.js +42 -8
- package/web/index.html +84 -0
- package/web/sw.js +2 -2
package/web/app.js
CHANGED
|
@@ -106,6 +106,8 @@ const state = {
|
|
|
106
106
|
defaultLocale: DEFAULT_LOCALE,
|
|
107
107
|
supportedLocales: [...SUPPORTED_LOCALES],
|
|
108
108
|
appVersion: "",
|
|
109
|
+
versionStatus: null,
|
|
110
|
+
versionStatusError: "",
|
|
109
111
|
};
|
|
110
112
|
|
|
111
113
|
let detailLoadSequence = 0;
|
|
@@ -130,6 +132,23 @@ function hazbasePasskeyHostSupport() {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
const app = document.querySelector("#app");
|
|
135
|
+
let bootSplashDismissed = false;
|
|
136
|
+
|
|
137
|
+
function dismissBootSplash() {
|
|
138
|
+
if (bootSplashDismissed || typeof document === "undefined") {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
bootSplashDismissed = true;
|
|
142
|
+
const splash = document.querySelector("#boot-splash");
|
|
143
|
+
document.body?.classList.add("viveworker-ready");
|
|
144
|
+
if (!splash) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
splash.setAttribute("aria-hidden", "true");
|
|
148
|
+
window.setTimeout(() => {
|
|
149
|
+
splash.remove();
|
|
150
|
+
}, 280);
|
|
151
|
+
}
|
|
133
152
|
const params = new URLSearchParams(window.location.search);
|
|
134
153
|
const initialItem = params.get("item") || "";
|
|
135
154
|
const initialTargetTab = params.get("tab") || "";
|
|
@@ -144,6 +163,7 @@ boot().catch((error) => {
|
|
|
144
163
|
const hint = /Load failed|Failed to fetch|NetworkError|fetch/i.test(message)
|
|
145
164
|
? `<p class="muted">${escapeHtml(L("error.networkHint"))}</p>`
|
|
146
165
|
: "";
|
|
166
|
+
dismissBootSplash();
|
|
147
167
|
app.innerHTML = `
|
|
148
168
|
<main class="onboarding-shell">
|
|
149
169
|
<section class="onboarding-card">
|
|
@@ -225,6 +245,7 @@ async function boot() {
|
|
|
225
245
|
ensureCurrentSelection();
|
|
226
246
|
maybeAutoFocusClaudePending();
|
|
227
247
|
await renderShell();
|
|
248
|
+
refreshVersionStatusForTechnicalPage();
|
|
228
249
|
|
|
229
250
|
// Diff fetch runs as a background phase because `/api/inbox/diff`
|
|
230
251
|
// spawns `git` subprocesses per tracked repo and can stall for several
|
|
@@ -586,6 +607,29 @@ async function fetchHazbaseStatus() {
|
|
|
586
607
|
}
|
|
587
608
|
}
|
|
588
609
|
|
|
610
|
+
async function fetchVersionStatus() {
|
|
611
|
+
try {
|
|
612
|
+
state.versionStatus = await apiGet("/api/version/status");
|
|
613
|
+
state.versionStatusError = "";
|
|
614
|
+
} catch (error) {
|
|
615
|
+
state.versionStatus = null;
|
|
616
|
+
state.versionStatusError = error.message || String(error);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function refreshVersionStatusForTechnicalPage() {
|
|
621
|
+
if (state.currentTab !== "settings" || state.settingsSubpage !== "advanced") {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
fetchVersionStatus()
|
|
625
|
+
.then(async () => {
|
|
626
|
+
if (state.currentTab === "settings" && state.settingsSubpage === "advanced" && !shouldDeferRenderForActiveInteraction()) {
|
|
627
|
+
await renderShell();
|
|
628
|
+
}
|
|
629
|
+
})
|
|
630
|
+
.catch(() => {});
|
|
631
|
+
}
|
|
632
|
+
|
|
589
633
|
async function getClientPushState() {
|
|
590
634
|
const registration = state.serviceWorkerRegistration || (await navigator.serviceWorker?.ready.catch(() => null));
|
|
591
635
|
if (registration) {
|
|
@@ -827,6 +871,7 @@ function normalizeProviderClient(value) {
|
|
|
827
871
|
if (normalized === "claude") return "claude";
|
|
828
872
|
if (normalized === "moltbook") return "moltbook";
|
|
829
873
|
if (normalized === "a2a") return "a2a";
|
|
874
|
+
if (normalized === "viveworker") return "viveworker";
|
|
830
875
|
return "codex";
|
|
831
876
|
}
|
|
832
877
|
|
|
@@ -835,6 +880,7 @@ function providerDisplayName(provider) {
|
|
|
835
880
|
if (p === "claude") return L("common.claude");
|
|
836
881
|
if (p === "moltbook") return "Moltbook";
|
|
837
882
|
if (p === "a2a") return "A2A";
|
|
883
|
+
if (p === "viveworker") return L("common.appName");
|
|
838
884
|
return L("common.codex");
|
|
839
885
|
}
|
|
840
886
|
|
|
@@ -1143,6 +1189,7 @@ function renderPair() {
|
|
|
1143
1189
|
});
|
|
1144
1190
|
|
|
1145
1191
|
bindSharedUi(renderPair);
|
|
1192
|
+
requestAnimationFrame(dismissBootSplash);
|
|
1146
1193
|
}
|
|
1147
1194
|
|
|
1148
1195
|
async function pair(payload) {
|
|
@@ -1234,6 +1281,7 @@ async function renderShell() {
|
|
|
1234
1281
|
<div class="${shellClassName}">
|
|
1235
1282
|
${desktop ? renderDesktopHeader(detail) : renderMobileTopBar(detail)}
|
|
1236
1283
|
${renderTopBanner()}
|
|
1284
|
+
${renderGlobalErrorBanner()}
|
|
1237
1285
|
<main class="app-main">
|
|
1238
1286
|
${desktop ? renderDesktopWorkspace(detail) : renderMobileWorkspace(detail)}
|
|
1239
1287
|
</main>
|
|
@@ -1250,6 +1298,7 @@ async function renderShell() {
|
|
|
1250
1298
|
applyPendingListScrollRestore();
|
|
1251
1299
|
applyPendingSettingsSubpageScrollReset();
|
|
1252
1300
|
applyPendingSettingsScrollRestore();
|
|
1301
|
+
requestAnimationFrame(dismissBootSplash);
|
|
1253
1302
|
}
|
|
1254
1303
|
|
|
1255
1304
|
function applyPendingDetailScrollReset() {
|
|
@@ -1870,7 +1919,11 @@ function buildActionOutcomeDetail({ kind, title, message }) {
|
|
|
1870
1919
|
|
|
1871
1920
|
function approvalOutcomeMessage(actionUrl, provider) {
|
|
1872
1921
|
const vars = { provider: providerDisplayName(provider) };
|
|
1873
|
-
|
|
1922
|
+
const normalizedUrl = String(actionUrl || "");
|
|
1923
|
+
if (/\/api\/payments\/x402\/hazbase-wallet\/[^/]+\/pay$/u.test(normalizedUrl)) {
|
|
1924
|
+
return L("server.message.paymentSubmitted");
|
|
1925
|
+
}
|
|
1926
|
+
return /\/accept$/u.test(normalizedUrl)
|
|
1874
1927
|
? L("server.message.approvalAccepted", vars)
|
|
1875
1928
|
: L("server.message.approvalRejected", vars);
|
|
1876
1929
|
}
|
|
@@ -2588,6 +2641,8 @@ function timelineEntryStatusLabel(item, isMessageLike) {
|
|
|
2588
2641
|
return L("timeline.status.approved");
|
|
2589
2642
|
case "rejected":
|
|
2590
2643
|
return L("timeline.status.rejected");
|
|
2644
|
+
case "failed":
|
|
2645
|
+
return L("timeline.status.failed");
|
|
2591
2646
|
case "implemented":
|
|
2592
2647
|
return L("timeline.status.implemented");
|
|
2593
2648
|
case "dismissed":
|
|
@@ -3489,8 +3544,11 @@ function renderSettingsRoot(context, { mobile }) {
|
|
|
3489
3544
|
page: "wallet",
|
|
3490
3545
|
icon: "coin",
|
|
3491
3546
|
title: L("settings.wallet.title"),
|
|
3547
|
+
badge: "beta",
|
|
3492
3548
|
subtitle: L("settings.wallet.subtitle"),
|
|
3493
|
-
value: context.hazbase?.
|
|
3549
|
+
value: context.hazbase?.sessionInvalid
|
|
3550
|
+
? L("settings.hazbase.status.sessionExpired")
|
|
3551
|
+
: context.hazbase?.signedIn ? L("settings.hazbase.status.signedIn") : L("settings.hazbase.status.signedOut"),
|
|
3494
3552
|
}) : "",
|
|
3495
3553
|
].filter(Boolean)) : ""}
|
|
3496
3554
|
${renderSettingsGroup(L("settings.pairing.title"), deviceRows)}
|
|
@@ -3673,6 +3731,7 @@ function renderSettingsDevicePage(context) {
|
|
|
3673
3731
|
}
|
|
3674
3732
|
|
|
3675
3733
|
function renderSettingsAdvancedPage(context) {
|
|
3734
|
+
const versionNotice = renderVersionUpdateNotice();
|
|
3676
3735
|
return `
|
|
3677
3736
|
<div class="settings-page">
|
|
3678
3737
|
${context.diagnostics.map((message) => `<p class="inline-alert">${escapeHtml(message)}</p>`).join("")}
|
|
@@ -3691,10 +3750,28 @@ function renderSettingsAdvancedPage(context) {
|
|
|
3691
3750
|
: "",
|
|
3692
3751
|
renderSettingsInfoRow(L("settings.row.version"), state.appVersion || L("common.unavailable")),
|
|
3693
3752
|
].filter(Boolean), { listClassName: "settings-list settings-list--compact" })}
|
|
3753
|
+
${versionNotice}
|
|
3694
3754
|
</div>
|
|
3695
3755
|
`;
|
|
3696
3756
|
}
|
|
3697
3757
|
|
|
3758
|
+
function renderVersionUpdateNotice() {
|
|
3759
|
+
const status = state.versionStatus;
|
|
3760
|
+
if (!status?.updateAvailable || !status.latestVersion) {
|
|
3761
|
+
return "";
|
|
3762
|
+
}
|
|
3763
|
+
return `
|
|
3764
|
+
<section class="settings-copy-block settings-copy-block--compact settings-update-notice">
|
|
3765
|
+
<p class="settings-update-notice__title">${escapeHtml(L("settings.updateAvailable.title"))}</p>
|
|
3766
|
+
<p class="muted">${escapeHtml(L("settings.updateAvailable.copy", {
|
|
3767
|
+
current: status.currentVersion || state.appVersion || "",
|
|
3768
|
+
latest: status.latestVersion,
|
|
3769
|
+
}))}</p>
|
|
3770
|
+
<code class="settings-update-notice__command">npx viveworker update</code>
|
|
3771
|
+
</section>
|
|
3772
|
+
`;
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3698
3775
|
function renderSettingsNotificationActions({ push, canEnable, standalone }) {
|
|
3699
3776
|
if (push.serverSubscribed) {
|
|
3700
3777
|
return `
|
|
@@ -3730,12 +3807,15 @@ function renderSettingsGroup(title, rows, options = {}) {
|
|
|
3730
3807
|
`;
|
|
3731
3808
|
}
|
|
3732
3809
|
|
|
3733
|
-
function renderSettingsNavRow({ page, icon, title, subtitle, value }) {
|
|
3810
|
+
function renderSettingsNavRow({ page, icon, title, badge, subtitle, value }) {
|
|
3734
3811
|
return `
|
|
3735
3812
|
<button class="settings-nav-row" type="button" data-settings-subpage="${escapeHtml(page)}">
|
|
3736
3813
|
<span class="settings-row__icon" aria-hidden="true">${renderIcon(icon)}</span>
|
|
3737
3814
|
<span class="settings-row__body">
|
|
3738
|
-
<span class="settings-row__title"
|
|
3815
|
+
<span class="settings-row__title-line">
|
|
3816
|
+
<span class="settings-row__title">${escapeHtml(title)}</span>
|
|
3817
|
+
${badge ? `<span class="settings-row__badge">${escapeHtml(badge)}</span>` : ""}
|
|
3818
|
+
</span>
|
|
3739
3819
|
${subtitle ? `<span class="settings-row__subtitle">${escapeHtml(subtitle)}</span>` : ""}
|
|
3740
3820
|
</span>
|
|
3741
3821
|
<span class="settings-row__value">${escapeHtml(value || "")}</span>
|
|
@@ -4515,6 +4595,7 @@ function renderSettingsWalletPage(context) {
|
|
|
4515
4595
|
// longer duplicated.
|
|
4516
4596
|
const guideRows = [
|
|
4517
4597
|
renderHazbaseWalletBanner(flow),
|
|
4598
|
+
renderHazbaseWalletBetaNotice(),
|
|
4518
4599
|
state.hazbaseNotice
|
|
4519
4600
|
? `<div class="settings-copy-block settings-copy-block--compact wallet-flow-message wallet-flow-message--notice"><p>${escapeHtml(state.hazbaseNotice)}</p></div>`
|
|
4520
4601
|
: "",
|
|
@@ -4523,8 +4604,16 @@ function renderSettingsWalletPage(context) {
|
|
|
4523
4604
|
: "",
|
|
4524
4605
|
renderHazbaseWalletStepList(flow),
|
|
4525
4606
|
].filter(Boolean);
|
|
4526
|
-
const
|
|
4527
|
-
|
|
4607
|
+
const canRefreshSession = Boolean(hazbase.sessionInvalid);
|
|
4608
|
+
const advancedActions = canRefreshSession || hazbase.signedIn
|
|
4609
|
+
? [
|
|
4610
|
+
canRefreshSession
|
|
4611
|
+
? `<button class="secondary secondary--wide" type="button" data-hazbase-action="refresh-session">${escapeHtml(L("settings.hazbase.action.refreshSession"))}</button>`
|
|
4612
|
+
: "",
|
|
4613
|
+
hazbase.signedIn
|
|
4614
|
+
? `<button class="secondary secondary--wide" type="button" data-hazbase-action="logout">${escapeHtml(L("settings.hazbase.action.signOut"))}</button>`
|
|
4615
|
+
: "",
|
|
4616
|
+
].filter(Boolean).join("")
|
|
4528
4617
|
: "";
|
|
4529
4618
|
// Render the wallet flow without `renderSettingsGroup`'s `.settings-list`
|
|
4530
4619
|
// wrapper. The banner (`.settings-copy-block`), notice/error blocks, and
|
|
@@ -4558,15 +4647,10 @@ function renderSettingsWalletPage(context) {
|
|
|
4558
4647
|
function renderHazbaseWalletStepList(flow) {
|
|
4559
4648
|
const rendered = [];
|
|
4560
4649
|
for (const step of flow.steps) {
|
|
4561
|
-
//
|
|
4562
|
-
//
|
|
4563
|
-
//
|
|
4564
|
-
|
|
4565
|
-
// true completion boundary; the ready banner already keys off
|
|
4566
|
-
// `coreReady = signedIn && hasPasskey && hasBaseSepolia`. Drop this
|
|
4567
|
-
// guard (and restore the opt-in reveal) once mainnet is enabled
|
|
4568
|
-
// server-side.
|
|
4569
|
-
if (step.number === 4) {
|
|
4650
|
+
// Keep the Base mainnet roadmap visible only after the usable Base Sepolia
|
|
4651
|
+
// wallet is ready. Showing Step 4 during email/passkey setup makes the
|
|
4652
|
+
// sequential flow feel like there is another action competing for focus.
|
|
4653
|
+
if (step.number === 4 && step.status === "comingSoon" && !flow.coreReady) {
|
|
4570
4654
|
continue;
|
|
4571
4655
|
}
|
|
4572
4656
|
|
|
@@ -4599,7 +4683,8 @@ function deriveHazbaseWalletFlow(hazbase) {
|
|
|
4599
4683
|
const accounts = Array.isArray(hazbase.accounts) ? hazbase.accounts : [];
|
|
4600
4684
|
const baseSepolia = accounts.find((entry) => Number(entry.chainId) === 84532) || null;
|
|
4601
4685
|
const baseMainnet = accounts.find((entry) => Number(entry.chainId) === 8453) || null;
|
|
4602
|
-
const
|
|
4686
|
+
const sessionInvalid = Boolean(hazbase.sessionInvalid);
|
|
4687
|
+
const signedIn = Boolean(hazbase.signedIn) && !sessionInvalid;
|
|
4603
4688
|
const passkeyHost = hazbasePasskeyHostSupport();
|
|
4604
4689
|
const hasPasskey = Boolean(hazbase.credentialId || hazbase.deviceBindingId);
|
|
4605
4690
|
const hasBaseSepolia = Boolean(baseSepolia?.smartAccountAddress);
|
|
@@ -4619,17 +4704,20 @@ function deriveHazbaseWalletFlow(hazbase) {
|
|
|
4619
4704
|
hasPasskey,
|
|
4620
4705
|
baseSepolia,
|
|
4621
4706
|
baseMainnet,
|
|
4707
|
+
sessionInvalid,
|
|
4622
4708
|
coreReady,
|
|
4623
4709
|
steps: [
|
|
4624
4710
|
{
|
|
4625
4711
|
number: 1,
|
|
4626
4712
|
icon: "approval",
|
|
4627
|
-
title: L("settings.wallet.step.signIn.title"),
|
|
4628
|
-
copy: L("settings.wallet.step.signIn.copy"),
|
|
4713
|
+
title: sessionInvalid ? L("settings.wallet.step.refreshSession.title") : L("settings.wallet.step.signIn.title"),
|
|
4714
|
+
copy: sessionInvalid ? L("settings.wallet.step.refreshSession.copy") : L("settings.wallet.step.signIn.copy"),
|
|
4629
4715
|
detail: signedIn
|
|
4630
4716
|
? hazbase.email || L("settings.hazbase.status.signedIn")
|
|
4631
4717
|
: state.hazbaseOtpRequested
|
|
4632
4718
|
? L("settings.hazbase.status.otpAwaitingVerify")
|
|
4719
|
+
: sessionInvalid
|
|
4720
|
+
? L("settings.hazbase.status.sessionExpired")
|
|
4633
4721
|
: L("settings.hazbase.status.signedOut"),
|
|
4634
4722
|
status: signedIn ? "complete" : "current",
|
|
4635
4723
|
// Inline form: email input (always visible pre-sign-in) and the
|
|
@@ -4657,7 +4745,7 @@ function deriveHazbaseWalletFlow(hazbase) {
|
|
|
4657
4745
|
actionButton("settings.hazbase.action.resendOtp", "request-otp"),
|
|
4658
4746
|
]
|
|
4659
4747
|
: [
|
|
4660
|
-
actionButton("settings.hazbase.action.requestOtp", "request-otp", { primary: true }),
|
|
4748
|
+
actionButton(sessionInvalid ? "settings.hazbase.action.refreshSession" : "settings.hazbase.action.requestOtp", "request-otp", { primary: true }),
|
|
4661
4749
|
],
|
|
4662
4750
|
},
|
|
4663
4751
|
{
|
|
@@ -4702,25 +4790,23 @@ function deriveHazbaseWalletFlow(hazbase) {
|
|
|
4702
4790
|
icon: "coin",
|
|
4703
4791
|
title: L("settings.wallet.step.base.title"),
|
|
4704
4792
|
copy: L("settings.wallet.step.base.copy"),
|
|
4705
|
-
detail: baseMainnet?.smartAccountAddress || L("settings.
|
|
4793
|
+
detail: baseMainnet?.smartAccountAddress || L("settings.wallet.step.base.comingSoonDetail"),
|
|
4706
4794
|
monoDetail: Boolean(baseMainnet?.smartAccountAddress),
|
|
4707
|
-
status: hasBaseMainnet ? "complete" :
|
|
4708
|
-
actions:
|
|
4709
|
-
? []
|
|
4710
|
-
: [
|
|
4711
|
-
actionButton("settings.hazbase.action.bootstrapBase", "bootstrap-base", {
|
|
4712
|
-
disabled: !coreReady,
|
|
4713
|
-
}),
|
|
4714
|
-
],
|
|
4795
|
+
status: hasBaseMainnet ? "complete" : "comingSoon",
|
|
4796
|
+
actions: [],
|
|
4715
4797
|
},
|
|
4716
4798
|
],
|
|
4717
4799
|
};
|
|
4718
4800
|
}
|
|
4719
4801
|
|
|
4720
4802
|
function renderHazbaseWalletBanner(flow) {
|
|
4721
|
-
const title = flow.
|
|
4803
|
+
const title = flow.sessionInvalid
|
|
4804
|
+
? L("settings.wallet.sessionExpired.title")
|
|
4805
|
+
: flow.coreReady ? L("settings.wallet.ready.title") : L("settings.wallet.flow.banner.title");
|
|
4722
4806
|
const className = flow.coreReady
|
|
4723
4807
|
? "settings-copy-block settings-copy-block--stacked wallet-flow-banner wallet-flow-banner--ready"
|
|
4808
|
+
: flow.sessionInvalid
|
|
4809
|
+
? "settings-copy-block settings-copy-block--stacked wallet-flow-banner wallet-flow-banner--session-expired"
|
|
4724
4810
|
: "settings-copy-block settings-copy-block--stacked wallet-flow-banner";
|
|
4725
4811
|
// Ready state: surface the smart-account address prominently so the user
|
|
4726
4812
|
// can see at a glance which wallet agents will use as `--pay-to` when
|
|
@@ -4746,7 +4832,9 @@ function renderHazbaseWalletBanner(flow) {
|
|
|
4746
4832
|
</button>
|
|
4747
4833
|
`
|
|
4748
4834
|
: `<p class="wallet-flow-banner__copy muted">${escapeHtml(
|
|
4749
|
-
flow.
|
|
4835
|
+
flow.sessionInvalid
|
|
4836
|
+
? L("settings.wallet.sessionExpired.copy")
|
|
4837
|
+
: flow.coreReady ? L("settings.wallet.ready.copy") : L("settings.wallet.flow.copy"),
|
|
4750
4838
|
)}</p>`;
|
|
4751
4839
|
return `
|
|
4752
4840
|
<div class="${className}">
|
|
@@ -4757,14 +4845,29 @@ function renderHazbaseWalletBanner(flow) {
|
|
|
4757
4845
|
`;
|
|
4758
4846
|
}
|
|
4759
4847
|
|
|
4848
|
+
function renderHazbaseWalletBetaNotice() {
|
|
4849
|
+
return `
|
|
4850
|
+
<div class="settings-copy-block settings-copy-block--compact wallet-beta-notice">
|
|
4851
|
+
<p>${escapeHtml(L("settings.wallet.betaNotice"))}</p>
|
|
4852
|
+
</div>
|
|
4853
|
+
`;
|
|
4854
|
+
}
|
|
4855
|
+
|
|
4760
4856
|
function renderHazbaseWalletStepCard(step, { mode = "full" } = {}) {
|
|
4761
4857
|
const statusMeta = {
|
|
4762
4858
|
complete: { label: L("settings.wallet.status.complete"), icon: "completed" },
|
|
4763
4859
|
current: { label: L("settings.wallet.status.current"), icon: "pending" },
|
|
4764
4860
|
locked: { label: L("settings.wallet.status.locked"), icon: "lock" },
|
|
4765
4861
|
optional: { label: L("settings.wallet.status.optional"), icon: "coin" },
|
|
4862
|
+
comingSoon: { label: L("settings.wallet.status.comingSoon"), icon: "coin" },
|
|
4766
4863
|
pending: { label: L("settings.wallet.status.pending"), icon: "pending" },
|
|
4767
4864
|
}[step.status] || { label: L("settings.wallet.status.pending"), icon: "pending" };
|
|
4865
|
+
const statusChipHtml = step.status === "current"
|
|
4866
|
+
? ""
|
|
4867
|
+
: `<span class="wallet-step-card__status wallet-step-card__status--${escapeHtml(step.status)}">
|
|
4868
|
+
<span class="wallet-step-card__status-icon" aria-hidden="true">${renderIcon(statusMeta.icon)}</span>
|
|
4869
|
+
<span>${escapeHtml(statusMeta.label)}</span>
|
|
4870
|
+
</span>`;
|
|
4768
4871
|
|
|
4769
4872
|
if (mode === "compact") {
|
|
4770
4873
|
// Compact row for finished steps. Keeps the check icon + title + one-line
|
|
@@ -4795,10 +4898,7 @@ function renderHazbaseWalletStepCard(step, { mode = "full" } = {}) {
|
|
|
4795
4898
|
<h3 class="wallet-step-card__title">${escapeHtml(step.title)}</h3>
|
|
4796
4899
|
</div>
|
|
4797
4900
|
</div>
|
|
4798
|
-
|
|
4799
|
-
<span class="wallet-step-card__status-icon" aria-hidden="true">${renderIcon(statusMeta.icon)}</span>
|
|
4800
|
-
<span>${escapeHtml(statusMeta.label)}</span>
|
|
4801
|
-
</span>
|
|
4901
|
+
${statusChipHtml}
|
|
4802
4902
|
</div>
|
|
4803
4903
|
<p class="wallet-step-card__copy">${escapeHtml(step.copy)}</p>
|
|
4804
4904
|
<p class="wallet-step-card__detail ${step.monoDetail ? "wallet-step-card__detail--mono" : ""}">${escapeHtml(step.detail)}</p>
|
|
@@ -6305,6 +6405,20 @@ function renderTopBanner() {
|
|
|
6305
6405
|
return "";
|
|
6306
6406
|
}
|
|
6307
6407
|
|
|
6408
|
+
function renderGlobalErrorBanner() {
|
|
6409
|
+
if (!state.pushError) {
|
|
6410
|
+
return "";
|
|
6411
|
+
}
|
|
6412
|
+
return `
|
|
6413
|
+
<section class="install-banner install-banner--push">
|
|
6414
|
+
<div class="install-banner__copy">
|
|
6415
|
+
<strong>${escapeHtml(state.locale === "ja" ? "エラー" : "Error")}</strong>
|
|
6416
|
+
<p class="muted">${escapeHtml(state.pushError)}</p>
|
|
6417
|
+
</div>
|
|
6418
|
+
</section>
|
|
6419
|
+
`;
|
|
6420
|
+
}
|
|
6421
|
+
|
|
6308
6422
|
function renderPushBanner() {
|
|
6309
6423
|
if (!shouldShowPushBanner()) {
|
|
6310
6424
|
return "";
|
|
@@ -6414,20 +6528,7 @@ function renderLogoutConfirmModal() {
|
|
|
6414
6528
|
<strong id="logout-confirm-title">${escapeHtml(L("logout.confirm.title"))}</strong>
|
|
6415
6529
|
<p class="muted">${escapeHtml(L("logout.confirm.copy"))}</p>
|
|
6416
6530
|
</div>
|
|
6417
|
-
<
|
|
6418
|
-
<div class="logout-option__copy">
|
|
6419
|
-
<strong>${escapeHtml(L("logout.confirm.keepTrustedTitle"))}</strong>
|
|
6420
|
-
<p class="muted">${escapeHtml(L("logout.confirm.keepTrustedCopy"))}</p>
|
|
6421
|
-
</div>
|
|
6422
|
-
<button class="primary primary--wide" type="button" data-logout-mode="session">${escapeHtml(L("logout.action.keepTrusted"))}</button>
|
|
6423
|
-
</div>
|
|
6424
|
-
<div class="logout-option logout-option--danger">
|
|
6425
|
-
<div class="logout-option__copy">
|
|
6426
|
-
<strong>${escapeHtml(L("logout.confirm.removeTitle"))}</strong>
|
|
6427
|
-
<p class="muted">${escapeHtml(L("logout.confirm.removeCopy"))}</p>
|
|
6428
|
-
</div>
|
|
6429
|
-
<button class="secondary secondary--wide" type="button" data-logout-mode="revoke">${escapeHtml(L("logout.action.removeDevice"))}</button>
|
|
6430
|
-
</div>
|
|
6531
|
+
<button class="secondary secondary--wide" type="button" data-logout-mode="session">${escapeHtml(L("logout.action.keepTrusted"))}</button>
|
|
6431
6532
|
<button class="ghost ghost--wide" type="button" data-close-logout-confirm>${escapeHtml(L("common.cancel"))}</button>
|
|
6432
6533
|
</section>
|
|
6433
6534
|
</div>
|
|
@@ -6488,6 +6589,7 @@ function bindShellInteractions() {
|
|
|
6488
6589
|
openSettingsSubpage(nextPage);
|
|
6489
6590
|
}
|
|
6490
6591
|
await renderShell();
|
|
6592
|
+
refreshVersionStatusForTechnicalPage();
|
|
6491
6593
|
});
|
|
6492
6594
|
}
|
|
6493
6595
|
|
|
@@ -6500,6 +6602,7 @@ function bindShellInteractions() {
|
|
|
6500
6602
|
syncCurrentItemUrl(null);
|
|
6501
6603
|
openSettingsSubpage("advanced");
|
|
6502
6604
|
await renderShell();
|
|
6605
|
+
refreshVersionStatusForTechnicalPage();
|
|
6503
6606
|
});
|
|
6504
6607
|
}
|
|
6505
6608
|
|
|
@@ -6507,6 +6610,7 @@ function bindShellInteractions() {
|
|
|
6507
6610
|
button.addEventListener("click", async () => {
|
|
6508
6611
|
openSettingsSubpage(button.dataset.settingsSubpage || "");
|
|
6509
6612
|
await renderShell();
|
|
6613
|
+
refreshVersionStatusForTechnicalPage();
|
|
6510
6614
|
});
|
|
6511
6615
|
}
|
|
6512
6616
|
|
|
@@ -6749,7 +6853,23 @@ function bindShellInteractions() {
|
|
|
6749
6853
|
button.innerHTML = `<span class="action-spinner" aria-hidden="true"></span><span>${escapeHtml(L("reply.sendSending"))}</span>`;
|
|
6750
6854
|
|
|
6751
6855
|
try {
|
|
6752
|
-
|
|
6856
|
+
let postBody = body;
|
|
6857
|
+
if (body?.hazbaseReauth === true) {
|
|
6858
|
+
if (!hazbasePasskeyHostSupport().eligible) {
|
|
6859
|
+
throw new Error(L("error.hazbasePasskeyLocalHostRequired"));
|
|
6860
|
+
}
|
|
6861
|
+
const { createPasskeyAssertionCredential } = await loadHazbasePasskeyModule();
|
|
6862
|
+
const challenge = await apiPost("/api/hazbase/passkey/assert/challenge", { purpose: "reauth" });
|
|
6863
|
+
const credential = await createPasskeyAssertionCredential(challenge);
|
|
6864
|
+
await apiPost("/api/hazbase/passkey/assert/complete", {
|
|
6865
|
+
challengeId: challenge.challengeId,
|
|
6866
|
+
credential,
|
|
6867
|
+
purpose: "reauth",
|
|
6868
|
+
});
|
|
6869
|
+
postBody = { ...body };
|
|
6870
|
+
delete postBody.hazbaseReauth;
|
|
6871
|
+
}
|
|
6872
|
+
await apiPost(actionUrl, postBody);
|
|
6753
6873
|
if (keepDetailOpen && activeItem?.kind === "approval") {
|
|
6754
6874
|
pinActionOutcomeDetail(
|
|
6755
6875
|
activeItem,
|
|
@@ -6769,16 +6889,35 @@ function bindShellInteractions() {
|
|
|
6769
6889
|
state.pendingActionUrls.delete(actionUrl);
|
|
6770
6890
|
} catch (error) {
|
|
6771
6891
|
state.pendingActionUrls.delete(actionUrl);
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6892
|
+
const approvalFinalized = Boolean(error?.payload?.approvalFinalized);
|
|
6893
|
+
if (error?.errorKey === "hazbase-session-expired") {
|
|
6894
|
+
await fetchHazbaseStatus();
|
|
6895
|
+
}
|
|
6896
|
+
if (approvalFinalized) {
|
|
6897
|
+
await refreshAuthenticatedState();
|
|
6898
|
+
if (keepDetailOpen && activeItem?.kind === "approval") {
|
|
6899
|
+
pinActionOutcomeDetail(
|
|
6900
|
+
activeItem,
|
|
6901
|
+
buildActionOutcomeDetail({
|
|
6902
|
+
kind: "approval",
|
|
6903
|
+
title: state.currentDetail?.title,
|
|
6904
|
+
message: L("server.message.paymentFailed", { reason: error.message || String(error) }),
|
|
6905
|
+
})
|
|
6906
|
+
);
|
|
6907
|
+
}
|
|
6908
|
+
} else {
|
|
6909
|
+
// Restore buttons on recoverable failure so the user can retry the same action.
|
|
6910
|
+
for (const sibling of siblingButtons) {
|
|
6911
|
+
if (originalLabels.has(sibling)) {
|
|
6912
|
+
sibling.innerHTML = originalLabels.get(sibling);
|
|
6913
|
+
}
|
|
6914
|
+
sibling.disabled = false;
|
|
6915
|
+
sibling.removeAttribute("aria-busy");
|
|
6776
6916
|
}
|
|
6777
|
-
|
|
6778
|
-
sibling.removeAttribute("aria-busy");
|
|
6917
|
+
button.classList.remove("is-loading");
|
|
6779
6918
|
}
|
|
6780
|
-
|
|
6781
|
-
|
|
6919
|
+
state.pushError = error.message || String(error);
|
|
6920
|
+
await renderShell();
|
|
6782
6921
|
}
|
|
6783
6922
|
});
|
|
6784
6923
|
}
|
|
@@ -7209,6 +7348,11 @@ for (const button of document.querySelectorAll("[data-hazbase-action]")) {
|
|
|
7209
7348
|
state.hazbaseLogoutConfirmOpen = false;
|
|
7210
7349
|
await apiPost("/api/hazbase/logout", {});
|
|
7211
7350
|
state.hazbaseNotice = L("settings.hazbase.notice.signedOut");
|
|
7351
|
+
} else if (action === "refresh-session") {
|
|
7352
|
+
await apiPost("/api/hazbase/session/refresh", {});
|
|
7353
|
+
state.hazbaseOtpRequested = false;
|
|
7354
|
+
state.hazbaseOtpCode = "";
|
|
7355
|
+
state.hazbaseNotice = L("settings.hazbase.notice.sessionRefreshStarted");
|
|
7212
7356
|
} else if (action === "change-email") {
|
|
7213
7357
|
// Flip the form back to pre-send mode. We keep the email so typo
|
|
7214
7358
|
// recovery ("hoshin" → "hoshino") stays one edit away, but drop
|
|
@@ -7233,6 +7377,11 @@ for (const button of document.querySelectorAll("[data-hazbase-action]")) {
|
|
|
7233
7377
|
}
|
|
7234
7378
|
await fetchHazbaseStatus();
|
|
7235
7379
|
} catch (error) {
|
|
7380
|
+
if (error?.errorKey === "hazbase-session-expired") {
|
|
7381
|
+
state.hazbaseOtpRequested = false;
|
|
7382
|
+
state.hazbaseOtpCode = "";
|
|
7383
|
+
await fetchHazbaseStatus();
|
|
7384
|
+
}
|
|
7236
7385
|
state.hazbaseError = error.message || String(error);
|
|
7237
7386
|
}
|
|
7238
7387
|
await renderShell();
|
|
@@ -8697,6 +8846,7 @@ function localizeApiError(value) {
|
|
|
8697
8846
|
"choice-input-already-handled": "error.choiceInputAlreadyHandled",
|
|
8698
8847
|
"mkcert-root-ca-not-found": "error.mkcertRootCaNotFound",
|
|
8699
8848
|
"hazbase-auth-required": "error.hazbaseAuthRequired",
|
|
8849
|
+
"hazbase-session-expired": "error.hazbaseSessionExpired",
|
|
8700
8850
|
"hazbase-passkey-local-host-required": "error.hazbasePasskeyLocalHostRequired",
|
|
8701
8851
|
"hazbase-wallet-account-missing": "error.hazbaseWalletAccountMissing",
|
|
8702
8852
|
"unsupported-chain": "error.unsupportedChain",
|