App: fix the clients change is not notified

This commit is contained in:
thislight 2024-10-11 16:39:42 +08:00
parent fd47639bef
commit bd3ad078a2
4 changed files with 122 additions and 79 deletions

View file

@ -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<unknwon>.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.

View file

@ -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<Session[]>([]);
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 (
<ErrorBoundary
fallback={(err, reset) => {
@ -87,7 +102,7 @@ const App: Component = () => {
>
<ThemeProvider theme={theme()}>
<DateFnScope>
<ClientProvider value={clientStore}>
<ClientProvider value={clients}>
<Routing />
</ClientProvider>
</DateFnScope>

View file

@ -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];
}

View file

@ -49,12 +49,12 @@ export type Session = {
client: mastodon.rest.Client;
};
const Context = /* @__PURE__ */ createContext<Signal<Session[]>>();
const Context = /* @__PURE__ */ createContext<Accessor<readonly Readonly<Session>[]>>();
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");