Merge remote-tracking branch 'origin/develop' into feat/update-get-started-template

This commit is contained in:
alt0
2022-08-12 18:10:07 +08:00
19 changed files with 358 additions and 239 deletions
@@ -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(),
});
+23 -19
View File
@@ -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');
+56 -41
View File
@@ -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;
}
}
},
};
+1
View File
@@ -618,6 +618,7 @@ export class BlockClient<
const instance = await YjsAdapter.init(workspace, {
provider: getYjsProviders({
enabled: [],
backend: BucketBackend.YjsWebSocketAffine,
importData,
exportData,
+18 -3
View File
@@ -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']