App: fix the clients change is not notified
This commit is contained in:
parent
a97963001a
commit
afda0642b4
4 changed files with 122 additions and 79 deletions
|
@ -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)
|
## 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.
|
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.
|
||||||
|
|
39
src/App.tsx
39
src/App.tsx
|
@ -2,8 +2,9 @@ import { Route, Router } from "@solidjs/router";
|
||||||
import { ThemeProvider } from "@suid/material";
|
import { ThemeProvider } from "@suid/material";
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
|
createEffect,
|
||||||
|
createMemo,
|
||||||
createRenderEffect,
|
createRenderEffect,
|
||||||
createSignal,
|
|
||||||
ErrorBoundary,
|
ErrorBoundary,
|
||||||
lazy,
|
lazy,
|
||||||
onCleanup,
|
onCleanup,
|
||||||
|
@ -12,9 +13,8 @@ import { useRootTheme } from "./material/mui.js";
|
||||||
import {
|
import {
|
||||||
Provider as ClientProvider,
|
Provider as ClientProvider,
|
||||||
createMastoClientFor,
|
createMastoClientFor,
|
||||||
type Session,
|
|
||||||
} from "./masto/clients.js";
|
} from "./masto/clients.js";
|
||||||
import { $accounts } from "./accounts/stores.js";
|
import { $accounts, updateAcctInf } from "./accounts/stores.js";
|
||||||
import { useStore } from "@nanostores/solid";
|
import { useStore } from "@nanostores/solid";
|
||||||
import { DateFnScope, useLanguage } from "./platform/i18n.jsx";
|
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 MotionSettings = lazy(() => import("./settings/Motions.js"));
|
||||||
const LanguageSettings = lazy(() => import("./settings/Language.js"));
|
const LanguageSettings = lazy(() => import("./settings/Language.js"));
|
||||||
const RegionSettings = lazy(() => import("./settings/Region.jsx"));
|
const RegionSettings = lazy(() => import("./settings/Region.jsx"));
|
||||||
|
const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
|
||||||
|
|
||||||
const Routing: Component = () => {
|
const Routing: Component = () => {
|
||||||
return (
|
return (
|
||||||
|
@ -56,14 +57,30 @@ const Routing: Component = () => {
|
||||||
const App: Component = () => {
|
const App: Component = () => {
|
||||||
const theme = useRootTheme();
|
const theme = useRootTheme();
|
||||||
const accts = useStore($accounts);
|
const accts = useStore($accounts);
|
||||||
const clientStore = createSignal<Session[]>([]);
|
|
||||||
const lang = useLanguage();
|
const lang = useLanguage();
|
||||||
|
|
||||||
createRenderEffect(() => {
|
const clients = createMemo(() => {
|
||||||
const [, setClients] = clientStore;
|
return accts().map((x) => ({
|
||||||
setClients(
|
account: x,
|
||||||
accts().map((x) => ({ account: x, client: createMastoClientFor(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(() => {
|
createRenderEffect(() => {
|
||||||
|
@ -76,8 +93,6 @@ const App: Component = () => {
|
||||||
root.removeAttribute("lang");
|
root.removeAttribute("lang");
|
||||||
});
|
});
|
||||||
|
|
||||||
const UnexpectedError = lazy(() => import("./UnexpectedError.js"));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
fallback={(err, reset) => {
|
fallback={(err, reset) => {
|
||||||
|
@ -87,7 +102,7 @@ const App: Component = () => {
|
||||||
>
|
>
|
||||||
<ThemeProvider theme={theme()}>
|
<ThemeProvider theme={theme()}>
|
||||||
<DateFnScope>
|
<DateFnScope>
|
||||||
<ClientProvider value={clientStore}>
|
<ClientProvider value={clients}>
|
||||||
<Routing />
|
<Routing />
|
||||||
</ClientProvider>
|
</ClientProvider>
|
||||||
</DateFnScope>
|
</DateFnScope>
|
||||||
|
|
|
@ -59,32 +59,32 @@ async function oauth2TokenViaAuthCode(app: RegisteredApp, authCode: string) {
|
||||||
export async function acceptAccountViaAuthCode(site: string, authCode: string) {
|
export async function acceptAccountViaAuthCode(site: string, authCode: string) {
|
||||||
const $store = $accounts;
|
const $store = $accounts;
|
||||||
const app = $registeredApps.get()[site];
|
const app = $registeredApps.get()[site];
|
||||||
if (!app) {
|
if (!app) {
|
||||||
throw TypeError("application not found");
|
throw TypeError("application not found");
|
||||||
}
|
}
|
||||||
const token = await oauth2TokenViaAuthCode(app, authCode);
|
const token = await oauth2TokenViaAuthCode(app, authCode);
|
||||||
|
|
||||||
const acct = {
|
const acct = {
|
||||||
site: app.site,
|
site: app.site,
|
||||||
accessToken: token.access_token,
|
accessToken: token.access_token,
|
||||||
tokenType: token.token_type,
|
tokenType: token.token_type,
|
||||||
scope: token.scope,
|
scope: token.scope,
|
||||||
createdAt: token.created_at * 1000,
|
createdAt: token.created_at * 1000,
|
||||||
};
|
};
|
||||||
|
|
||||||
const all = [...$store.get(), acct];
|
const all = [...$store.get(), acct];
|
||||||
$store.set(all);
|
$store.set(all);
|
||||||
|
|
||||||
return acct;
|
return acct;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateAcctInf(idx: number) {
|
export async function updateAcctInf(idx: number) {
|
||||||
const o = $accounts.get();
|
const o = $accounts.get();
|
||||||
const client = createMastoClientFor(o[idx]);
|
const client = createMastoClientFor(o[idx]);
|
||||||
const inf = await client.v1.accounts.verifyCredentials();
|
const inf = await client.v1.accounts.verifyCredentials();
|
||||||
o[idx].inf = inf;
|
o[idx] = Object.assign({}, o[idx], { inf });
|
||||||
$accounts.set(o);
|
$accounts.set(Array.from(o));
|
||||||
return inf;
|
return inf;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function signOut(predicate: (acct: Account) => boolean) {
|
export function signOut(predicate: (acct: Account) => boolean) {
|
||||||
|
@ -131,57 +131,57 @@ async function getAppAccessToken(app: RegisteredApp) {
|
||||||
export async function getOrRegisterApp(site: string, redirectUrl: string) {
|
export async function getOrRegisterApp(site: string, redirectUrl: string) {
|
||||||
const $store = $registeredApps;
|
const $store = $registeredApps;
|
||||||
const all = $store.get();
|
const all = $store.get();
|
||||||
const savedApp = all[site];
|
const savedApp = all[site];
|
||||||
if (savedApp && savedApp.redirectUrl === redirectUrl) {
|
if (savedApp && savedApp.redirectUrl === redirectUrl) {
|
||||||
const appAccessToken = await getAppAccessToken(savedApp);
|
const appAccessToken = await getAppAccessToken(savedApp);
|
||||||
if (appAccessToken) {
|
if (appAccessToken) {
|
||||||
const client = createRestAPIClient({
|
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,
|
url: site,
|
||||||
accessToken: appAccessToken,
|
accessToken: appAccessToken,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const verify = await client.v1.apps.verifyCredentials();
|
await oauthClient.revoke({
|
||||||
Object.assign(savedApp, {
|
clientId: savedApp.clientId,
|
||||||
vapidKey: verify.vapidKey,
|
clientSecret: savedApp.clientSecret,
|
||||||
|
token: appAccessToken,
|
||||||
});
|
});
|
||||||
const oauthClient = createOAuthAPIClient({
|
} catch {}
|
||||||
url: site,
|
return savedApp;
|
||||||
accessToken: appAccessToken,
|
} finally {
|
||||||
});
|
$store.set(all);
|
||||||
try {
|
|
||||||
await oauthClient.revoke({
|
|
||||||
clientId: savedApp.clientId,
|
|
||||||
clientSecret: savedApp.clientSecret,
|
|
||||||
token: appAccessToken,
|
|
||||||
});
|
|
||||||
} catch {}
|
|
||||||
return savedApp;
|
|
||||||
} finally {
|
|
||||||
$store.set(all);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const client = createRestAPIClient({
|
const client = createRestAPIClient({
|
||||||
url: site,
|
url: site,
|
||||||
});
|
});
|
||||||
const app = await client.v1.apps.create({
|
const app = await client.v1.apps.create({
|
||||||
clientName: "TuTu",
|
clientName: "TuTu",
|
||||||
website: "https://github.com/thislight/tutu",
|
website: "https://github.com/thislight/tutu",
|
||||||
redirectUris: redirectUrl,
|
redirectUris: redirectUrl,
|
||||||
scopes: "read write push",
|
scopes: "read write push",
|
||||||
});
|
});
|
||||||
if (!app.clientId || !app.clientSecret) {
|
if (!app.clientId || !app.clientSecret) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
all[site] = {
|
all[site] = {
|
||||||
site,
|
site,
|
||||||
clientId: app.clientId,
|
clientId: app.clientId,
|
||||||
clientSecret: app.clientSecret,
|
clientSecret: app.clientSecret,
|
||||||
vapidKey: app.vapidKey ?? undefined,
|
vapidKey: app.vapidKey ?? undefined,
|
||||||
redirectUrl: redirectUrl,
|
redirectUrl: redirectUrl,
|
||||||
scope: "read write push",
|
scope: "read write push",
|
||||||
};
|
};
|
||||||
$store.set(all);
|
$store.set(all);
|
||||||
return all[site];
|
return all[site];
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,12 +49,12 @@ export type Session = {
|
||||||
client: mastodon.rest.Client;
|
client: mastodon.rest.Client;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Context = /* @__PURE__ */ createContext<Signal<Session[]>>();
|
const Context = /* @__PURE__ */ createContext<Accessor<readonly Readonly<Session>[]>>();
|
||||||
|
|
||||||
export const Provider = Context.Provider;
|
export const Provider = Context.Provider;
|
||||||
|
|
||||||
export function useSessions() {
|
export function useSessions() {
|
||||||
const [sessions] = useSessionsRw();
|
const sessions = useSessionsRaw();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ export function useSessions() {
|
||||||
return sessions;
|
return sessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useSessionsRw() {
|
function useSessionsRaw() {
|
||||||
const store = useContext(Context);
|
const store = useContext(Context);
|
||||||
if (!store) {
|
if (!store) {
|
||||||
throw new TypeError("sessions are not provided");
|
throw new TypeError("sessions are not provided");
|
||||||
|
|
Loading…
Reference in a new issue