From bd3ad078a2952c16869b4d0b77702135621a431a Mon Sep 17 00:00:00 2001 From: thislight Date: Fri, 11 Oct 2024 16:39:42 +0800 Subject: [PATCH] App: fix the clients change is not notified --- docs/devnotes.md | 28 +++++++++ src/App.tsx | 39 +++++++++---- src/accounts/stores.ts | 128 ++++++++++++++++++++--------------------- src/masto/clients.ts | 6 +- 4 files changed, 122 insertions(+), 79 deletions(-) diff --git a/docs/devnotes.md b/docs/devnotes.md index 100207d..bd97c7b 100644 --- a/docs/devnotes.md +++ b/docs/devnotes.md @@ -13,3 +13,31 @@ You can debug on the Safari on iOS only if you have mac (and run macOS). The cer ## Hero Animation won't work (after hot reload) That's a known issue. Hot reload won't refresh the module sets the hero cache. Refresh the whole page and it should work. + +## The components don't react to the change as I setting the store, until the page reloaded + +The `WritableAtom.set` might do an equals check. You must set a different object to ensure the atom sending a notify. + +The code below may not notify the change: + +```ts +export function updateAcctInf(idx: number) { + const o = $accounts.get(); + // ... + o[idx].inf = inf; + $accounts.set(o); +} +``` + +Instead, set a new object: + +```ts +export function updateAcctInf(idx: number) { + const o = $accounts.get(); + // ... + o[idx] = Object.assign({}, o[idx], { inf }); + $accounts.set(Array.from(o)); +} +``` + +Ja, the code is weird, but that's the best we know. Anyway, you need new object on the path of your changed value. diff --git a/src/App.tsx b/src/App.tsx index cd6fe4f..f532882 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,8 +2,9 @@ import { Route, Router } from "@solidjs/router"; import { ThemeProvider } from "@suid/material"; import { Component, + createEffect, + createMemo, createRenderEffect, - createSignal, ErrorBoundary, lazy, onCleanup, @@ -12,9 +13,8 @@ import { useRootTheme } from "./material/mui.js"; import { Provider as ClientProvider, createMastoClientFor, - type Session, } from "./masto/clients.js"; -import { $accounts } from "./accounts/stores.js"; +import { $accounts, updateAcctInf } from "./accounts/stores.js"; import { useStore } from "@nanostores/solid"; import { DateFnScope, useLanguage } from "./platform/i18n.jsx"; @@ -28,6 +28,7 @@ const TootBottomSheet = lazy(() => import("./timelines/TootBottomSheet.js")); const MotionSettings = lazy(() => import("./settings/Motions.js")); const LanguageSettings = lazy(() => import("./settings/Language.js")); const RegionSettings = lazy(() => import("./settings/Region.jsx")); +const UnexpectedError = lazy(() => import("./UnexpectedError.js")); const Routing: Component = () => { return ( @@ -56,14 +57,30 @@ const Routing: Component = () => { const App: Component = () => { const theme = useRootTheme(); const accts = useStore($accounts); - const clientStore = createSignal([]); const lang = useLanguage(); - createRenderEffect(() => { - const [, setClients] = clientStore; - setClients( - accts().map((x) => ({ account: x, client: createMastoClientFor(x) })), - ); + const clients = createMemo(() => { + return accts().map((x) => ({ + account: x, + client: createMastoClientFor(x), + })); + }); + + createEffect(() => { + const neededUpdates = accts() + .map((x, i) => [i, x] as const) + .filter(([, x]) => !x.inf); + if (neededUpdates.length > 0) { + // FIXME: we might need some kind of concurrent control + Promise.all(neededUpdates.map(([i]) => updateAcctInf(i))).then( + (x) => { + console.info("acct info updated for %d acct(s)", x.length); + }, + (reason) => { + console.error("acct info update is fail", reason); + }, + ); + } }); createRenderEffect(() => { @@ -76,8 +93,6 @@ const App: Component = () => { root.removeAttribute("lang"); }); - const UnexpectedError = lazy(() => import("./UnexpectedError.js")); - return ( { @@ -87,7 +102,7 @@ const App: Component = () => { > - + diff --git a/src/accounts/stores.ts b/src/accounts/stores.ts index 9d7a270..9b66924 100644 --- a/src/accounts/stores.ts +++ b/src/accounts/stores.ts @@ -59,32 +59,32 @@ async function oauth2TokenViaAuthCode(app: RegisteredApp, authCode: string) { export async function acceptAccountViaAuthCode(site: string, authCode: string) { const $store = $accounts; const app = $registeredApps.get()[site]; - if (!app) { - throw TypeError("application not found"); - } - const token = await oauth2TokenViaAuthCode(app, authCode); + if (!app) { + throw TypeError("application not found"); + } + const token = await oauth2TokenViaAuthCode(app, authCode); - const acct = { - site: app.site, - accessToken: token.access_token, - tokenType: token.token_type, - scope: token.scope, - createdAt: token.created_at * 1000, - }; + const acct = { + site: app.site, + accessToken: token.access_token, + tokenType: token.token_type, + scope: token.scope, + createdAt: token.created_at * 1000, + }; - const all = [...$store.get(), acct]; - $store.set(all); + const all = [...$store.get(), acct]; + $store.set(all); - return acct; + return acct; } export async function updateAcctInf(idx: number) { const o = $accounts.get(); - const client = createMastoClientFor(o[idx]); - const inf = await client.v1.accounts.verifyCredentials(); - o[idx].inf = inf; - $accounts.set(o); - return inf; + const client = createMastoClientFor(o[idx]); + const inf = await client.v1.accounts.verifyCredentials(); + o[idx] = Object.assign({}, o[idx], { inf }); + $accounts.set(Array.from(o)); + return inf; } export function signOut(predicate: (acct: Account) => boolean) { @@ -131,57 +131,57 @@ async function getAppAccessToken(app: RegisteredApp) { export async function getOrRegisterApp(site: string, redirectUrl: string) { const $store = $registeredApps; const all = $store.get(); - const savedApp = all[site]; - if (savedApp && savedApp.redirectUrl === redirectUrl) { - const appAccessToken = await getAppAccessToken(savedApp); - if (appAccessToken) { - const client = createRestAPIClient({ + const savedApp = all[site]; + if (savedApp && savedApp.redirectUrl === redirectUrl) { + const appAccessToken = await getAppAccessToken(savedApp); + if (appAccessToken) { + const client = createRestAPIClient({ + url: site, + accessToken: appAccessToken, + }); + try { + const verify = await client.v1.apps.verifyCredentials(); + Object.assign(savedApp, { + vapidKey: verify.vapidKey, + }); + const oauthClient = createOAuthAPIClient({ url: site, accessToken: appAccessToken, }); try { - const verify = await client.v1.apps.verifyCredentials(); - Object.assign(savedApp, { - vapidKey: verify.vapidKey, + await oauthClient.revoke({ + clientId: savedApp.clientId, + clientSecret: savedApp.clientSecret, + token: appAccessToken, }); - const oauthClient = createOAuthAPIClient({ - url: site, - accessToken: appAccessToken, - }); - try { - await oauthClient.revoke({ - clientId: savedApp.clientId, - clientSecret: savedApp.clientSecret, - token: appAccessToken, - }); - } catch {} - return savedApp; - } finally { - $store.set(all); - } + } catch {} + return savedApp; + } finally { + $store.set(all); } } + } - const client = createRestAPIClient({ - url: site, - }); - const app = await client.v1.apps.create({ - clientName: "TuTu", - website: "https://github.com/thislight/tutu", - redirectUris: redirectUrl, - scopes: "read write push", - }); - if (!app.clientId || !app.clientSecret) { - return null; - } - all[site] = { - site, - clientId: app.clientId, - clientSecret: app.clientSecret, - vapidKey: app.vapidKey ?? undefined, - redirectUrl: redirectUrl, - scope: "read write push", - }; - $store.set(all); - return all[site]; + const client = createRestAPIClient({ + url: site, + }); + const app = await client.v1.apps.create({ + clientName: "TuTu", + website: "https://github.com/thislight/tutu", + redirectUris: redirectUrl, + scopes: "read write push", + }); + if (!app.clientId || !app.clientSecret) { + return null; + } + all[site] = { + site, + clientId: app.clientId, + clientSecret: app.clientSecret, + vapidKey: app.vapidKey ?? undefined, + redirectUrl: redirectUrl, + scope: "read write push", + }; + $store.set(all); + return all[site]; } diff --git a/src/masto/clients.ts b/src/masto/clients.ts index f100779..4ed109b 100644 --- a/src/masto/clients.ts +++ b/src/masto/clients.ts @@ -49,12 +49,12 @@ export type Session = { client: mastodon.rest.Client; }; -const Context = /* @__PURE__ */ createContext>(); +const Context = /* @__PURE__ */ createContext[]>>(); export const Provider = Context.Provider; export function useSessions() { - const [sessions] = useSessionsRw(); + const sessions = useSessionsRaw(); const navigate = useNavigate(); const location = useLocation(); @@ -69,7 +69,7 @@ export function useSessions() { return sessions; } -function useSessionsRw() { +function useSessionsRaw() { const store = useContext(Context); if (!store) { throw new TypeError("sessions are not provided");