mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-24 22:09:08 +08:00
feat(core): improve mobile perf (#15317)
#### PR Dependency Tree * **PR #15317** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Virtualized mobile navigation with shell navigation and interactive swipe menus; coordinated mobile back handling with interactive phases/state restoration. * Added shared auth request proxy and message-port based token handling across mobile and worker flows. * **Bug Fixes** * Hydrated remote worker error stacks for calls and observable errors. * Improved SQLite FTS/indexer and nbstore optional text handling; refined docs-search ref parsing and notification loading/retry. * **Refactor / UX** * Modal focus-preservation and pointer behavior updates; improved mobile menu controls and back gesture plugins. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -2,9 +2,10 @@ import { notify } from '@affine/component';
|
||||
import { getStoreManager } from '@affine/core/blocksuite/manager/store';
|
||||
import { AffineContext } from '@affine/core/components/context';
|
||||
import { AppFallback } from '@affine/core/mobile/components/app-fallback';
|
||||
import { MobileModalConfigProvider } from '@affine/core/mobile/components/mobile-modal-config-provider';
|
||||
import { configureMobileModules } from '@affine/core/mobile/modules';
|
||||
import { MobileBackCoordinator } from '@affine/core/mobile/modules/back-coordinator';
|
||||
import { HapticProvider } from '@affine/core/mobile/modules/haptics';
|
||||
import { NavigationGestureProvider } from '@affine/core/mobile/modules/navigation-gesture';
|
||||
import { VirtualKeyboardProvider } from '@affine/core/mobile/modules/virtual-keyboard';
|
||||
import { router } from '@affine/core/mobile/router';
|
||||
import { configureCommonModules } from '@affine/core/modules';
|
||||
@@ -46,6 +47,7 @@ import {
|
||||
requestApplySubscriptionMutation,
|
||||
} from '@affine/graphql';
|
||||
import { I18n } from '@affine/i18n';
|
||||
import { serveAuthRequests } from '@affine/mobile-shared/auth/channel';
|
||||
import { StoreManagerClient } from '@affine/nbstore/worker/client';
|
||||
import { setTelemetryTransport } from '@affine/track';
|
||||
import { Container } from '@blocksuite/affine/global/di';
|
||||
@@ -61,7 +63,13 @@ import { Browser } from '@capacitor/browser';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { Haptics } from '@capacitor/haptics';
|
||||
import { Keyboard, KeyboardStyle } from '@capacitor/keyboard';
|
||||
import { Framework, FrameworkRoot, getCurrentStore } from '@toeverything/infra';
|
||||
import {
|
||||
Framework,
|
||||
FrameworkRoot,
|
||||
getCurrentStore,
|
||||
useLiveData,
|
||||
useService,
|
||||
} from '@toeverything/infra';
|
||||
import { OpClient } from '@toeverything/infra/op';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
import { AppTrackingTransparency } from 'capacitor-plugin-app-tracking-transparency';
|
||||
@@ -70,16 +78,19 @@ import { Suspense, useEffect } from 'react';
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
|
||||
import { BlocksuiteMenuConfigProvider } from './bs-menu-config';
|
||||
import { ModalConfigProvider } from './modal-config';
|
||||
import { AffineTheme } from './plugins/affine-theme';
|
||||
import { Auth } from './plugins/auth';
|
||||
import { Hashcash } from './plugins/hashcash';
|
||||
import { ImagePicker } from './plugins/image-picker';
|
||||
import { NavigationGesture } from './plugins/navigation-gesture';
|
||||
import { NbStoreNativeDBApis } from './plugins/nbstore';
|
||||
import { PayWall } from './plugins/paywall';
|
||||
import { Preview } from './plugins/preview';
|
||||
import { clearEndpointSession, getValidAccessToken } from './proxy';
|
||||
import { enableNavigationGesture$ } from './web-navigation-control';
|
||||
import {
|
||||
authRequestProvider,
|
||||
clearEndpointSession,
|
||||
getValidAccessToken,
|
||||
} from './proxy';
|
||||
|
||||
const storeManagerClient = createStoreManagerClient();
|
||||
setTelemetryTransport(storeManagerClient.telemetry);
|
||||
@@ -165,11 +176,6 @@ framework.impl(VirtualKeyboardProvider, {
|
||||
};
|
||||
},
|
||||
});
|
||||
framework.impl(NavigationGestureProvider, {
|
||||
isEnabled: () => enableNavigationGesture$.value,
|
||||
enable: () => enableNavigationGesture$.next(true),
|
||||
disable: () => enableNavigationGesture$.next(false),
|
||||
});
|
||||
framework.impl(HapticProvider, {
|
||||
impact: options => Haptics.impact(options as any),
|
||||
vibrate: options => Haptics.vibrate(options as any),
|
||||
@@ -578,14 +584,49 @@ const KeyboardThemeProvider = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const IOSBackAdapter = () => {
|
||||
const coordinator = useService(MobileBackCoordinator);
|
||||
const enabled = useLiveData(coordinator.canInteractivePop$);
|
||||
|
||||
useEffect(() => {
|
||||
(enabled ? NavigationGesture.enable() : NavigationGesture.disable()).catch(
|
||||
console.error
|
||||
);
|
||||
}, [enabled]);
|
||||
|
||||
useEffect(() => {
|
||||
let disposed = false;
|
||||
let remove = () => {};
|
||||
NavigationGesture.addListener('gesture', event => {
|
||||
coordinator.handleInteractivePhase(event.phase);
|
||||
})
|
||||
.then(handle => {
|
||||
if (disposed) handle.remove().catch(console.error);
|
||||
else
|
||||
remove = () => {
|
||||
handle.remove().catch(console.error);
|
||||
};
|
||||
})
|
||||
.catch(console.error);
|
||||
return () => {
|
||||
disposed = true;
|
||||
remove();
|
||||
NavigationGesture.disable().catch(console.error);
|
||||
};
|
||||
}, [coordinator]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<Suspense>
|
||||
<FrameworkRoot framework={frameworkProvider}>
|
||||
<I18nProvider>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<KeyboardThemeProvider />
|
||||
<ModalConfigProvider>
|
||||
<MobileModalConfigProvider>
|
||||
<AffineContext store={getCurrentStore()}>
|
||||
<KeyboardThemeProvider />
|
||||
<IOSBackAdapter />
|
||||
<BlocksuiteMenuConfigProvider>
|
||||
<RouterProvider
|
||||
fallbackElement={<AppFallback />}
|
||||
@@ -593,8 +634,8 @@ export function App() {
|
||||
future={future}
|
||||
/>
|
||||
</BlocksuiteMenuConfigProvider>
|
||||
</ModalConfigProvider>
|
||||
</AffineContext>
|
||||
</AffineContext>
|
||||
</MobileModalConfigProvider>
|
||||
</I18nProvider>
|
||||
</FrameworkRoot>
|
||||
</Suspense>
|
||||
@@ -626,22 +667,7 @@ function createStoreManagerClient() {
|
||||
|
||||
const { port1: authTokenChannelServer, port2: authTokenChannelClient } =
|
||||
new MessageChannel();
|
||||
authTokenChannelServer.addEventListener('message', event => {
|
||||
const { id, endpoint } = event.data as { id?: string; endpoint?: string };
|
||||
if (!id || !endpoint) return;
|
||||
getValidAccessToken(endpoint)
|
||||
.then(token => authTokenChannelServer.postMessage({ id, token }))
|
||||
.catch(error =>
|
||||
authTokenChannelServer.postMessage({
|
||||
id,
|
||||
error:
|
||||
typeof error === 'object' && error && 'code' in error
|
||||
? error.code
|
||||
: 'AUTH_SESSION_TEMPORARILY_UNAVAILABLE',
|
||||
})
|
||||
);
|
||||
});
|
||||
authTokenChannelServer.start();
|
||||
serveAuthRequests(authTokenChannelServer, authRequestProvider);
|
||||
worker.postMessage(
|
||||
{ type: 'auth-access-token-channel', port: authTokenChannelClient },
|
||||
[authTokenChannelClient]
|
||||
|
||||
@@ -1,41 +1,21 @@
|
||||
import { NavigationGestureService } from '@affine/core/mobile/modules/navigation-gesture';
|
||||
import { MobileBackCoordinator } from '@affine/core/mobile/modules/back-coordinator';
|
||||
import { onMenuOpen } from '@blocksuite/affine/components/context-menu';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { type PropsWithChildren, useCallback, useEffect, useRef } from 'react';
|
||||
import { type PropsWithChildren, useEffect } from 'react';
|
||||
|
||||
export const BlocksuiteMenuConfigProvider = ({
|
||||
children,
|
||||
}: PropsWithChildren) => {
|
||||
const navigationGesture = useService(NavigationGestureService);
|
||||
const menuCountRef = useRef(0);
|
||||
const prevEnabledRef = useRef(false);
|
||||
|
||||
const handleMenuState = useCallback(() => {
|
||||
const currentCount = menuCountRef.current + 1;
|
||||
menuCountRef.current = currentCount;
|
||||
|
||||
if (currentCount === 1) {
|
||||
prevEnabledRef.current = navigationGesture.enabled$.value;
|
||||
if (prevEnabledRef.current) {
|
||||
navigationGesture.setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const currentCount = menuCountRef.current - 1;
|
||||
menuCountRef.current = currentCount;
|
||||
|
||||
if (currentCount === 0 && prevEnabledRef.current) {
|
||||
navigationGesture.setEnabled(true);
|
||||
}
|
||||
};
|
||||
}, [navigationGesture]);
|
||||
const coordinator = useService(MobileBackCoordinator);
|
||||
|
||||
useEffect(() => {
|
||||
return onMenuOpen(() => {
|
||||
return handleMenuState();
|
||||
return coordinator.registerVisual({
|
||||
interactive: false,
|
||||
handle: () => false,
|
||||
}).dispose;
|
||||
});
|
||||
}, [handleMenuState]);
|
||||
}, [coordinator]);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { ModalConfigContext } from '@affine/component';
|
||||
import { NavigationGestureService } from '@affine/core/mobile/modules/navigation-gesture';
|
||||
import { globalVars } from '@affine/core/mobile/styles/variables.css';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
export const ModalConfigProvider = ({ children }: React.PropsWithChildren) => {
|
||||
const navigationGesture = useService(NavigationGestureService);
|
||||
|
||||
const onOpen = useCallback(() => {
|
||||
const prev = navigationGesture.enabled$.value;
|
||||
if (prev) {
|
||||
navigationGesture.setEnabled(false);
|
||||
return () => {
|
||||
navigationGesture.setEnabled(prev);
|
||||
};
|
||||
}
|
||||
return;
|
||||
}, [navigationGesture]);
|
||||
const modalConfigValue = useMemo(
|
||||
() => ({ onOpen, dynamicKeyboardHeight: globalVars.appKeyboardHeight }),
|
||||
[onOpen]
|
||||
);
|
||||
|
||||
return (
|
||||
<ModalConfigContext.Provider value={modalConfigValue}>
|
||||
{children}
|
||||
</ModalConfigContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,7 @@
|
||||
import './setup-worker';
|
||||
|
||||
import { MessagePortAuthProvider } from '@affine/mobile-shared/auth/channel';
|
||||
import { installAuthRequestProxy } from '@affine/mobile-shared/auth/request';
|
||||
import { broadcastChannelStorages } from '@affine/nbstore/broadcast-channel';
|
||||
import {
|
||||
cloudStorages,
|
||||
@@ -18,53 +20,19 @@ import {
|
||||
import { type MessageCommunicapable, OpConsumer } from '@toeverything/infra/op';
|
||||
import { AsyncCall } from 'async-call-rpc';
|
||||
|
||||
let authTokenPort: MessagePort | undefined;
|
||||
const terminalAuthErrors = new Set([
|
||||
'ACCESS_TOKEN_INVALID',
|
||||
'AUTH_SESSION_EXPIRED',
|
||||
'AUTH_SESSION_REVOKED',
|
||||
'REFRESH_TOKEN_INVALID',
|
||||
'REFRESH_TOKEN_REUSED',
|
||||
'UNSUPPORTED_CLIENT_VERSION',
|
||||
'AUTH_SESSION_EMPTY',
|
||||
]);
|
||||
const pendingTokenRequests = new Map<
|
||||
string,
|
||||
{
|
||||
resolve: (token: string | null) => void;
|
||||
reject: (error: Error) => void;
|
||||
}
|
||||
>();
|
||||
const authProvider = new MessagePortAuthProvider();
|
||||
installAuthRequestProxy(authProvider);
|
||||
|
||||
configureSocketAuthMethod((endpoint, cb) => {
|
||||
getValidAccessToken(endpoint)
|
||||
authProvider
|
||||
.getValidAccessToken(endpoint)
|
||||
.then(token => cb(token ? { token, tokenType: 'jwt' } : {}))
|
||||
.catch(() => cb({ error: 'AUTH_SESSION_TEMPORARILY_UNAVAILABLE' }));
|
||||
});
|
||||
|
||||
globalThis.addEventListener('message', e => {
|
||||
if (e.data.type === 'auth-access-token-channel') {
|
||||
authTokenPort = e.ports[0] as MessagePort;
|
||||
authTokenPort.addEventListener('message', e => {
|
||||
const { id, token, error } = e.data as {
|
||||
id?: string;
|
||||
token?: string | null;
|
||||
error?: string;
|
||||
};
|
||||
if (!id) return;
|
||||
const pending = pendingTokenRequests.get(id);
|
||||
if (error) {
|
||||
if (terminalAuthErrors.has(error)) {
|
||||
pending?.resolve(null);
|
||||
} else {
|
||||
pending?.reject(new Error(error));
|
||||
}
|
||||
} else {
|
||||
pending?.resolve(token ?? null);
|
||||
}
|
||||
pendingTokenRequests.delete(id);
|
||||
});
|
||||
authTokenPort.start();
|
||||
authProvider.setPort(e.ports[0] as MessagePort);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -94,31 +62,6 @@ globalThis.addEventListener('message', e => {
|
||||
}
|
||||
});
|
||||
|
||||
function getValidAccessToken(endpoint: string) {
|
||||
if (!authTokenPort) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const id = `${Date.now()}:${Math.random()}`;
|
||||
return new Promise<string | null>((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
pendingTokenRequests.delete(id);
|
||||
reject(new Error('AUTH_SESSION_TEMPORARILY_UNAVAILABLE'));
|
||||
}, 5000);
|
||||
pendingTokenRequests.set(id, {
|
||||
resolve: token => {
|
||||
clearTimeout(timeout);
|
||||
resolve(token);
|
||||
},
|
||||
reject: error => {
|
||||
clearTimeout(timeout);
|
||||
reject(error);
|
||||
},
|
||||
});
|
||||
authTokenPort?.postMessage({ id, endpoint });
|
||||
});
|
||||
}
|
||||
|
||||
const consumer = new OpConsumer<WorkerManagerOps>(
|
||||
globalThis as MessageCommunicapable
|
||||
);
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import type { PluginListenerHandle } from '@capacitor/core';
|
||||
|
||||
export interface NavigationGesturePlugin {
|
||||
isEnabled: () => Promise<boolean>;
|
||||
enable: () => Promise<void>;
|
||||
disable: () => Promise<void>;
|
||||
addListener(
|
||||
event: 'gesture',
|
||||
listener: (event: {
|
||||
phase: 'begin' | 'progress' | 'commit' | 'cancel';
|
||||
progress: number;
|
||||
}) => void
|
||||
): Promise<PluginListenerHandle>;
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ export interface NbStorePlugin {
|
||||
id: string;
|
||||
indexName: string;
|
||||
docId: string;
|
||||
}) => Promise<{ text: string | null }>;
|
||||
}) => Promise<{ text?: string | null }>;
|
||||
ftsGetMatches: (options: {
|
||||
id: string;
|
||||
indexName: string;
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
base64ToUint8Array,
|
||||
uint8ArrayToBase64,
|
||||
} from '@affine/core/modules/workspace-engine';
|
||||
import { normalizeNativeOptional } from '@affine/mobile-shared/nbstore/optional';
|
||||
import {
|
||||
decodePayload,
|
||||
MOBILE_BLOB_FILE_PREFIX,
|
||||
@@ -405,7 +406,7 @@ export const NbStoreNativeDBApis: NativeDBApis = {
|
||||
indexName,
|
||||
docId,
|
||||
});
|
||||
return result.text;
|
||||
return normalizeNativeOptional(result.text);
|
||||
},
|
||||
ftsGetMatches: async function (
|
||||
id: string,
|
||||
|
||||
@@ -1,196 +1,30 @@
|
||||
import { canonicalAuthEndpoint } from '@affine/mobile-shared/auth/endpoint';
|
||||
import {
|
||||
type AuthRequestProvider,
|
||||
installAuthRequestProxy,
|
||||
} from '@affine/mobile-shared/auth/request';
|
||||
|
||||
import { Auth } from './plugins/auth';
|
||||
|
||||
function authEndpointForUrl(url: string | URL) {
|
||||
try {
|
||||
const parsed = new URL(url, globalThis.location.origin);
|
||||
return parsed.protocol === 'http:' || parsed.protocol === 'https:'
|
||||
? parsed.origin
|
||||
: null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the below code includes the custom fetch and xmlhttprequest implementation for ios webview.
|
||||
* should be included in the entry file of the app or webworker.
|
||||
*/
|
||||
const rawFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
|
||||
const request = new Request(input, init);
|
||||
const retry = request.clone();
|
||||
|
||||
const origin = authEndpointForUrl(request.url);
|
||||
|
||||
const token = origin ? await getValidAccessToken(origin) : null;
|
||||
if (token) {
|
||||
request.headers.set('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
|
||||
const response = await rawFetch(request);
|
||||
if (response.status !== 401 || !origin) return response;
|
||||
const body = await response
|
||||
.clone()
|
||||
.json()
|
||||
.catch(() => null);
|
||||
if (body?.code !== 'ACCESS_TOKEN_EXPIRED') return response;
|
||||
const { token: refreshed } = await Auth.refreshAccessToken({
|
||||
endpoint: origin,
|
||||
});
|
||||
retry.headers.set('Authorization', `Bearer ${refreshed}`);
|
||||
return rawFetch(retry);
|
||||
export const authRequestProvider: AuthRequestProvider = {
|
||||
async getValidAccessToken(endpoint) {
|
||||
const { token } = await Auth.getValidAccessToken({
|
||||
endpoint: canonicalAuthEndpoint(endpoint),
|
||||
});
|
||||
return token ?? null;
|
||||
},
|
||||
async refreshAccessToken(endpoint) {
|
||||
const { token } = await Auth.refreshAccessToken({
|
||||
endpoint: canonicalAuthEndpoint(endpoint),
|
||||
});
|
||||
return token;
|
||||
},
|
||||
};
|
||||
|
||||
const rawXMLHttpRequest = globalThis.XMLHttpRequest;
|
||||
const xhrRequestUrls = new WeakMap<XMLHttpRequest, string>();
|
||||
globalThis.XMLHttpRequest = class extends rawXMLHttpRequest {
|
||||
private request:
|
||||
| {
|
||||
method: string;
|
||||
url: string | URL;
|
||||
async: boolean;
|
||||
username?: string | null;
|
||||
password?: string | null;
|
||||
}
|
||||
| undefined;
|
||||
private readonly headers = new Map<string, string>();
|
||||
private requestBody?: Document | XMLHttpRequestBodyInit | null;
|
||||
private replaying = false;
|
||||
private hasReplayed = false;
|
||||
installAuthRequestProxy(authRequestProvider);
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const suppressExpiredResponse = (event: Event) => {
|
||||
if (this.replaying) event.stopImmediatePropagation();
|
||||
};
|
||||
this.addEventListener('load', suppressExpiredResponse, true);
|
||||
this.addEventListener('loadend', suppressExpiredResponse, true);
|
||||
this.addEventListener(
|
||||
'readystatechange',
|
||||
event => {
|
||||
if (
|
||||
this.readyState !== rawXMLHttpRequest.DONE ||
|
||||
this.status !== 401 ||
|
||||
this.replaying ||
|
||||
this.hasReplayed ||
|
||||
!this.request?.async
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let code: unknown;
|
||||
try {
|
||||
code =
|
||||
this.responseType === 'json'
|
||||
? this.response?.code
|
||||
: JSON.parse(this.responseText)?.code;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (code !== 'ACCESS_TOKEN_EXPIRED') return;
|
||||
event.stopImmediatePropagation();
|
||||
this.replaying = true;
|
||||
this.hasReplayed = true;
|
||||
this.replayWithFreshToken().catch(() => {});
|
||||
},
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
override open(
|
||||
method: string,
|
||||
url: string | URL,
|
||||
async: boolean = true,
|
||||
username?: string | null,
|
||||
password?: string | null
|
||||
): void {
|
||||
this.request = { method, url, async, username, password };
|
||||
this.headers.clear();
|
||||
this.requestBody = undefined;
|
||||
this.replaying = false;
|
||||
this.hasReplayed = false;
|
||||
xhrRequestUrls.set(this, url.toString());
|
||||
return super.open(
|
||||
method,
|
||||
url,
|
||||
async,
|
||||
username ?? undefined,
|
||||
password ?? undefined
|
||||
);
|
||||
}
|
||||
|
||||
override setRequestHeader(name: string, value: string): void {
|
||||
this.headers.set(name, value);
|
||||
super.setRequestHeader(name, value);
|
||||
}
|
||||
|
||||
override send(body?: Document | XMLHttpRequestBodyInit | null): void {
|
||||
this.requestBody = body;
|
||||
const requestUrl = xhrRequestUrls.get(this);
|
||||
const origin = authEndpointForUrl(requestUrl ?? globalThis.location.href);
|
||||
|
||||
(origin ? getValidAccessToken(origin) : Promise.resolve(null))
|
||||
.then(token => {
|
||||
if (token) {
|
||||
super.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
}
|
||||
return super.send(body);
|
||||
})
|
||||
.catch(() => {
|
||||
this.dispatchEvent(new Event('error'));
|
||||
this.dispatchEvent(new Event('loadend'));
|
||||
});
|
||||
}
|
||||
|
||||
private async replayWithFreshToken() {
|
||||
const request = this.request;
|
||||
if (!request) return this.failReplay();
|
||||
const origin = authEndpointForUrl(request.url);
|
||||
if (!origin) return this.failReplay();
|
||||
try {
|
||||
const { token } = await Auth.refreshAccessToken({ endpoint: origin });
|
||||
const responseType = this.responseType;
|
||||
const timeout = this.timeout;
|
||||
const withCredentials = this.withCredentials;
|
||||
super.open(
|
||||
request.method,
|
||||
request.url,
|
||||
true,
|
||||
request.username ?? undefined,
|
||||
request.password ?? undefined
|
||||
);
|
||||
this.replaying = false;
|
||||
this.headers.forEach((value, name) => {
|
||||
if (name.toLowerCase() !== 'authorization') {
|
||||
super.setRequestHeader(name, value);
|
||||
}
|
||||
});
|
||||
super.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
this.responseType = responseType;
|
||||
this.timeout = timeout;
|
||||
this.withCredentials = withCredentials;
|
||||
super.send(this.requestBody);
|
||||
} catch {
|
||||
this.failReplay();
|
||||
}
|
||||
}
|
||||
|
||||
private failReplay() {
|
||||
this.replaying = false;
|
||||
this.dispatchEvent(new Event('readystatechange'));
|
||||
this.dispatchEvent(new Event('error'));
|
||||
this.dispatchEvent(new Event('loadend'));
|
||||
}
|
||||
};
|
||||
|
||||
export async function getValidAccessToken(
|
||||
endpoint: string
|
||||
): Promise<string | null> {
|
||||
const { token } = await Auth.getValidAccessToken({
|
||||
endpoint: canonicalAuthEndpoint(endpoint),
|
||||
});
|
||||
return token ?? null;
|
||||
export function getValidAccessToken(endpoint: string) {
|
||||
return authRequestProvider.getValidAccessToken(endpoint);
|
||||
}
|
||||
|
||||
export async function clearEndpointSession(endpoint: string) {
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
import '@affine/core/bootstrap/browser';
|
||||
import './proxy';
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { LiveData } from '@toeverything/infra';
|
||||
|
||||
export const enableNavigationGesture$ = new LiveData(false);
|
||||
|
||||
const onTouchStart = (e: TouchEvent) => {
|
||||
if (enableNavigationGesture$.value) return;
|
||||
|
||||
const clientX = e.changedTouches[0].clientX;
|
||||
if (clientX <= 25) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
document.body.addEventListener('touchstart', onTouchStart, { passive: false });
|
||||
Reference in New Issue
Block a user