mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-14 08:36:22 +08:00
Merge remote-tracking branch 'origin/develop' into feat/update-get-started-template
This commit is contained in:
@@ -37,6 +37,11 @@ async function _getCurrentToken() {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const _enabled = {
|
||||
demo: [],
|
||||
AFFiNE: ['sqlite'],
|
||||
} as any;
|
||||
|
||||
async function _getBlockDatabase(
|
||||
workspace: string,
|
||||
options?: BlockInitOptions
|
||||
@@ -45,16 +50,10 @@ async function _getBlockDatabase(
|
||||
await waitLoading(workspace);
|
||||
}
|
||||
|
||||
// if (
|
||||
// options?.userId &&
|
||||
// workspaces[workspace]?.getUserId() !== options?.userId
|
||||
// ) {
|
||||
// delete workspaces[workspace];
|
||||
// }
|
||||
|
||||
if (!workspaces[workspace]) {
|
||||
loading.add(workspace);
|
||||
workspaces[workspace] = await BlockClient.init(workspace, {
|
||||
enabled: _enabled[workspace] || ['idb'],
|
||||
...options,
|
||||
token: await _getCurrentToken(),
|
||||
});
|
||||
|
||||
@@ -18,8 +18,6 @@ import {
|
||||
snapshot,
|
||||
} from 'yjs';
|
||||
|
||||
import { IndexedDBProvider } from '@toeverything/datasource/jwt-rpc';
|
||||
|
||||
import {
|
||||
AsyncDatabaseAdapter,
|
||||
BlockListener,
|
||||
@@ -48,15 +46,17 @@ type ConnectivityListener = (
|
||||
workspace: string,
|
||||
connectivity: Connectivity
|
||||
) => void;
|
||||
|
||||
type YjsProviders = {
|
||||
awareness: Awareness;
|
||||
idb: IndexedDBProvider;
|
||||
binariesIdb: IndexedDBProvider;
|
||||
binaries: Doc;
|
||||
doc: Doc;
|
||||
gatekeeper: GateKeeper;
|
||||
connListener: { listeners?: ConnectivityListener };
|
||||
userId: string;
|
||||
remoteToken?: string; // remote storage token
|
||||
};
|
||||
|
||||
const _yjsDatabaseInstance = new Map<string, YjsProviders>();
|
||||
|
||||
const _asyncInitLoading = new Set<string>();
|
||||
@@ -90,13 +90,9 @@ async function _initYjsDatabase(
|
||||
const { userId, token } = options;
|
||||
|
||||
const doc = new Doc({ autoLoad: true, shouldLoad: true });
|
||||
const idb = await new IndexedDBProvider(workspace, doc).whenSynced;
|
||||
// const idb = await new IndexedDBProvider(workspace, doc).whenSynced;
|
||||
|
||||
const binaries = new Doc({ autoLoad: true, shouldLoad: true });
|
||||
const binariesIdb = await new IndexedDBProvider(
|
||||
`${workspace}_binaries`,
|
||||
binaries
|
||||
).whenSynced;
|
||||
|
||||
const awareness = new Awareness(doc);
|
||||
|
||||
@@ -114,16 +110,24 @@ async function _initYjsDatabase(
|
||||
const emitState = (c: Connectivity) =>
|
||||
connListener.listeners?.(workspace, c);
|
||||
await Promise.all(
|
||||
Object.entries(options.provider).map(async ([, p]) =>
|
||||
p({ awareness, doc, token, workspace, emitState })
|
||||
)
|
||||
Object.entries(options.provider).flatMap(async ([, p]) => [
|
||||
p({ awareness, doc, token, workspace, emitState }),
|
||||
p({
|
||||
awareness,
|
||||
doc: binaries,
|
||||
token,
|
||||
workspace: `${workspace}_binaries`,
|
||||
emitState,
|
||||
}),
|
||||
])
|
||||
);
|
||||
}
|
||||
const newInstance = {
|
||||
awareness,
|
||||
idb,
|
||||
binariesIdb,
|
||||
binaries,
|
||||
doc,
|
||||
gatekeeper,
|
||||
|
||||
connListener,
|
||||
userId,
|
||||
remoteToken: token,
|
||||
@@ -183,7 +187,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
|
||||
|
||||
private constructor(providers: YjsProviders) {
|
||||
this._provider = providers;
|
||||
this._doc = providers.idb.doc;
|
||||
this._doc = providers.doc;
|
||||
this._awareness = providers.awareness;
|
||||
this._gatekeeper = providers.gatekeeper;
|
||||
this._reload = () => {
|
||||
@@ -201,7 +205,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
|
||||
});
|
||||
// @ts-ignore
|
||||
this._binaries = new YjsRemoteBinaries(
|
||||
providers.binariesIdb.doc.getMap(),
|
||||
providers.binaries.getMap(),
|
||||
providers.remoteToken
|
||||
);
|
||||
// @ts-ignore
|
||||
@@ -336,7 +340,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
|
||||
});
|
||||
const [file] = (await fromEvent(handles)) as File[];
|
||||
const binary = await file.arrayBuffer();
|
||||
await this._provider.idb.clearData();
|
||||
// await this._provider.idb.clearData();
|
||||
const doc = new Doc({ autoLoad: true, shouldLoad: true });
|
||||
let updated = 0;
|
||||
let isUpdated = false;
|
||||
@@ -357,8 +361,8 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
|
||||
};
|
||||
check();
|
||||
});
|
||||
await new IndexedDBProvider(this._provider.idb.name, doc)
|
||||
.whenSynced;
|
||||
// await new IndexedDBProvider(this._provider.idb.name, doc)
|
||||
// .whenSynced;
|
||||
applyUpdate(doc, new Uint8Array(binary));
|
||||
await update_check;
|
||||
console.log('load success');
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Doc } from 'yjs';
|
||||
import { Awareness } from 'y-protocols/awareness.js';
|
||||
|
||||
import {
|
||||
IndexedDBProvider,
|
||||
SQLiteProvider,
|
||||
WebsocketProvider,
|
||||
} from '@toeverything/datasource/jwt-rpc';
|
||||
@@ -19,7 +20,10 @@ type YjsDefaultInstances = {
|
||||
|
||||
export type YjsProvider = (instances: YjsDefaultInstances) => Promise<void>;
|
||||
|
||||
type ProviderType = 'idb' | 'sqlite' | 'ws';
|
||||
|
||||
export type YjsProviderOptions = {
|
||||
enabled: ProviderType[];
|
||||
backend: typeof BucketBackend[keyof typeof BucketBackend];
|
||||
params?: Record<string, string>;
|
||||
importData?: () => Promise<Uint8Array> | Uint8Array | undefined;
|
||||
@@ -30,53 +34,64 @@ export type YjsProviderOptions = {
|
||||
export const getYjsProviders = (
|
||||
options: YjsProviderOptions
|
||||
): Record<string, YjsProvider> => {
|
||||
console.log('getYjsProviders', options);
|
||||
return {
|
||||
indexeddb: async (instances: YjsDefaultInstances) => {
|
||||
if (options.enabled.includes('idb')) {
|
||||
await new IndexedDBProvider(instances.workspace, instances.doc)
|
||||
.whenSynced;
|
||||
}
|
||||
},
|
||||
sqlite: async (instances: YjsDefaultInstances) => {
|
||||
const fsHandle = setInterval(async () => {
|
||||
if (options.hasExporter?.()) {
|
||||
clearInterval(fsHandle);
|
||||
const fs = new SQLiteProvider(
|
||||
instances.workspace,
|
||||
instances.doc,
|
||||
await options.importData?.()
|
||||
);
|
||||
if (options.exportData) {
|
||||
fs.registerExporter(options.exportData);
|
||||
if (options.enabled.includes('sqlite')) {
|
||||
const fsHandle = setInterval(async () => {
|
||||
if (options.hasExporter?.()) {
|
||||
clearInterval(fsHandle);
|
||||
const fs = new SQLiteProvider(
|
||||
instances.workspace,
|
||||
instances.doc,
|
||||
await options.importData?.()
|
||||
);
|
||||
if (options.exportData) {
|
||||
fs.registerExporter(options.exportData);
|
||||
}
|
||||
await fs.whenSynced;
|
||||
}
|
||||
await fs.whenSynced;
|
||||
}
|
||||
}, 500);
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
ws: async (instances: YjsDefaultInstances) => {
|
||||
if (instances.token) {
|
||||
const ws = new WebsocketProvider(
|
||||
instances.token,
|
||||
options.backend,
|
||||
instances.workspace,
|
||||
instances.doc,
|
||||
{
|
||||
awareness: instances.awareness,
|
||||
params: options.params,
|
||||
}
|
||||
) as any; // TODO: type is erased after cascading references
|
||||
if (options.enabled.includes('ws')) {
|
||||
if (instances.token) {
|
||||
const ws = new WebsocketProvider(
|
||||
instances.token,
|
||||
options.backend,
|
||||
instances.workspace,
|
||||
instances.doc,
|
||||
{
|
||||
awareness: instances.awareness,
|
||||
params: options.params,
|
||||
}
|
||||
) as any; // TODO: type is erased after cascading references
|
||||
|
||||
// Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// TODO: synced will also be triggered on reconnection after losing sync
|
||||
// There needs to be an event mechanism to emit the synchronization state to the upper layer
|
||||
ws.once('synced', () => resolve());
|
||||
ws.once('lost-connection', () => resolve());
|
||||
ws.once('connection-error', () => reject());
|
||||
ws.on('synced', () => instances.emitState('connected'));
|
||||
ws.on('lost-connection', () =>
|
||||
instances.emitState('retry')
|
||||
);
|
||||
ws.on('connection-error', () =>
|
||||
instances.emitState('retry')
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
// Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
// TODO: synced will also be triggered on reconnection after losing sync
|
||||
// There needs to be an event mechanism to emit the synchronization state to the upper layer
|
||||
ws.once('synced', () => resolve());
|
||||
ws.once('lost-connection', () => resolve());
|
||||
ws.once('connection-error', () => reject());
|
||||
ws.on('synced', () => instances.emitState('connected'));
|
||||
ws.on('lost-connection', () =>
|
||||
instances.emitState('retry')
|
||||
);
|
||||
ws.on('connection-error', () =>
|
||||
instances.emitState('retry')
|
||||
);
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -618,6 +618,7 @@ export class BlockClient<
|
||||
|
||||
const instance = await YjsAdapter.init(workspace, {
|
||||
provider: getYjsProviders({
|
||||
enabled: [],
|
||||
backend: BucketBackend.YjsWebSocketAffine,
|
||||
importData,
|
||||
exportData,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useNavigate } from 'react-router';
|
||||
import {
|
||||
getAuth,
|
||||
onAuthStateChanged,
|
||||
@@ -62,11 +63,17 @@ const BRAND_ID = 'AFFiNE';
|
||||
|
||||
const _localTrigger = atom<boolean>(false);
|
||||
const _useUserAndSpacesForFreeLogin = () => {
|
||||
const navigate = useNavigate();
|
||||
const [user, setUser] = useAtom(_userAtom);
|
||||
const [loading, setLoading] = useAtom(_loadingAtom);
|
||||
const [localTrigger] = useAtom(_localTrigger);
|
||||
|
||||
useEffect(() => setLoading(false), []);
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
navigate('/demo');
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (localTrigger) {
|
||||
@@ -77,6 +84,14 @@ const _useUserAndSpacesForFreeLogin = () => {
|
||||
nickname: BRAND_ID,
|
||||
email: '',
|
||||
});
|
||||
} else {
|
||||
setUser({
|
||||
photo: '',
|
||||
id: 'demo',
|
||||
username: 'demo',
|
||||
nickname: 'demo',
|
||||
email: '',
|
||||
});
|
||||
}
|
||||
}, [localTrigger, setLoading, setUser]);
|
||||
|
||||
@@ -86,8 +101,8 @@ const _useUserAndSpacesForFreeLogin = () => {
|
||||
};
|
||||
|
||||
export const useLocalTrigger = () => {
|
||||
const [, setTrigger] = useAtom(_localTrigger);
|
||||
return () => setTrigger(true);
|
||||
const [trigger, setTrigger] = useAtom(_localTrigger);
|
||||
return [trigger, () => setTrigger(true)] as [boolean, () => void];
|
||||
};
|
||||
|
||||
export const useUserAndSpaces = process.env['NX_LOCAL']
|
||||
|
||||
Reference in New Issue
Block a user