feat: store local data to local db (#2037)
This commit is contained in:
@@ -1,8 +0,0 @@
|
|||||||
cacheFolder: '../../.yarn/cache'
|
|
||||||
deferredVersionFolder: '../../.yarn/versions'
|
|
||||||
globalFolder: '../../.yarn/global'
|
|
||||||
installStatePath: '../../.yarn/install-state.gz'
|
|
||||||
patchFolder: '../../.yarn/patches'
|
|
||||||
pnpUnpluggedFolder: '../../.yarn/unplugged'
|
|
||||||
yarnPath: '../../.yarn/releases/yarn-3.5.0.cjs'
|
|
||||||
virtualFolder: '../../.yarn/__virtual__'
|
|
||||||
@@ -3,8 +3,10 @@ const {
|
|||||||
utils: { fromBuildIdentifier },
|
utils: { fromBuildIdentifier },
|
||||||
} = require('@electron-forge/core');
|
} = require('@electron-forge/core');
|
||||||
|
|
||||||
const isCanary = process.env.BUILD_TYPE === 'canary';
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const isCanary = process.env.BUILD_TYPE === 'canary';
|
||||||
|
const buildType = isCanary ? 'canary' : 'stable';
|
||||||
const productName = isCanary ? 'AFFiNE-Canary' : 'AFFiNE';
|
const productName = isCanary ? 'AFFiNE-Canary' : 'AFFiNE';
|
||||||
const icoPath = isCanary
|
const icoPath = isCanary
|
||||||
? './resources/icons/icon_canary.ico'
|
? './resources/icons/icon_canary.ico'
|
||||||
@@ -13,6 +15,11 @@ const icnsPath = isCanary
|
|||||||
? './resources/icons/icon_canary.icns'
|
? './resources/icons/icon_canary.icns'
|
||||||
: './resources/icons/icon.icns';
|
: './resources/icons/icon.icns';
|
||||||
|
|
||||||
|
const arch =
|
||||||
|
process.argv.indexOf('--arch') > 0
|
||||||
|
? process.argv[process.argv.indexOf('--arch') + 1]
|
||||||
|
: process.arch;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@electron-forge/shared-types').ForgeConfig}
|
* @type {import('@electron-forge/shared-types').ForgeConfig}
|
||||||
*/
|
*/
|
||||||
@@ -25,10 +32,10 @@ module.exports = {
|
|||||||
stable: 'pro.affine.app',
|
stable: 'pro.affine.app',
|
||||||
}),
|
}),
|
||||||
icon: icnsPath,
|
icon: icnsPath,
|
||||||
osxSign: {
|
// osxSign: {
|
||||||
identity: 'Developer ID Application: TOEVERYTHING PTE. LTD.',
|
// identity: 'Developer ID Application: TOEVERYTHING PTE. LTD.',
|
||||||
'hardened-runtime': true,
|
// 'hardened-runtime': true,
|
||||||
},
|
// },
|
||||||
osxNotarize: process.env.APPLE_ID
|
osxNotarize: process.env.APPLE_ID
|
||||||
? {
|
? {
|
||||||
tool: 'notarytool',
|
tool: 'notarytool',
|
||||||
@@ -42,9 +49,25 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
name: '@electron-forge/maker-dmg',
|
name: '@electron-forge/maker-dmg',
|
||||||
config: {
|
config: {
|
||||||
format: 'ULFO',
|
|
||||||
icon: icnsPath,
|
icon: icnsPath,
|
||||||
name: 'AFFiNE',
|
name: 'AFFiNE',
|
||||||
|
'icon-size': 128,
|
||||||
|
background: './resources/icons/dmg-background.png',
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
x: 176,
|
||||||
|
y: 192,
|
||||||
|
type: 'file',
|
||||||
|
path: path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'out',
|
||||||
|
buildType,
|
||||||
|
`${productName}-darwin-${arch}`,
|
||||||
|
`${productName}.app`
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ x: 432, y: 192, type: 'link', path: '/Applications' },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
3
apps/electron/layers/logger.ts
Normal file
3
apps/electron/layers/logger.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import log from 'electron-log';
|
||||||
|
|
||||||
|
export const logger = log;
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import * as os from 'node:os';
|
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
import { app, shell } from 'electron';
|
|
||||||
import { BrowserWindow, ipcMain, nativeTheme } from 'electron';
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import { parse } from 'url';
|
|
||||||
|
|
||||||
import { isMacOS } from '../../../utils';
|
|
||||||
import { getExchangeTokenParams, oauthEndpoint } from './google-auth';
|
|
||||||
|
|
||||||
const AFFINE_ROOT = path.join(os.homedir(), '.affine');
|
|
||||||
|
|
||||||
fs.ensureDirSync(AFFINE_ROOT);
|
|
||||||
|
|
||||||
const logger = console;
|
|
||||||
|
|
||||||
export const registerHandlers = () => {
|
|
||||||
ipcMain.handle('ui:theme-change', async (_, theme) => {
|
|
||||||
nativeTheme.themeSource = theme;
|
|
||||||
logger.info('theme change', theme);
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('ui:sidebar-visibility-change', async (_, visible) => {
|
|
||||||
// todo
|
|
||||||
// detect if os is macos
|
|
||||||
if (isMacOS()) {
|
|
||||||
const windows = BrowserWindow.getAllWindows();
|
|
||||||
windows.forEach(w => {
|
|
||||||
// hide window buttons when sidebar is not visible
|
|
||||||
w.setWindowButtonVisibility(visible);
|
|
||||||
});
|
|
||||||
logger.info('sidebar visibility change', visible);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('ui:get-google-oauth-code', async () => {
|
|
||||||
logger.info('starting google sign in ...');
|
|
||||||
shell.openExternal(oauthEndpoint);
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const handleOpenUrl = async (_: any, url: string) => {
|
|
||||||
const mainWindow = BrowserWindow.getAllWindows().find(
|
|
||||||
w => !w.isDestroyed()
|
|
||||||
);
|
|
||||||
const urlObj = parse(url.replace('??', '?'), true);
|
|
||||||
if (!mainWindow || !url.startsWith('affine://auth-callback')) return;
|
|
||||||
const code = urlObj.query['code'] as string;
|
|
||||||
if (!code) return;
|
|
||||||
|
|
||||||
logger.info('google sign in code received from callback', code);
|
|
||||||
|
|
||||||
app.removeListener('open-url', handleOpenUrl);
|
|
||||||
resolve(getExchangeTokenParams(code));
|
|
||||||
};
|
|
||||||
|
|
||||||
app.on('open-url', handleOpenUrl);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
reject(new Error('Timed out'));
|
|
||||||
app.removeListener('open-url', handleOpenUrl);
|
|
||||||
}, 30000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('main:env-update', async (_, env, value) => {
|
|
||||||
process.env[env] = value;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
9
apps/electron/layers/main/src/context.ts
Normal file
9
apps/electron/layers/main/src/context.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { app } from 'electron';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export const appContext = {
|
||||||
|
appName: app.name,
|
||||||
|
appDataPath: path.join(app.getPath('appData'), app.name),
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AppContext = typeof appContext;
|
||||||
34
apps/electron/layers/main/src/data/export.ts
Normal file
34
apps/electron/layers/main/src/data/export.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
|
import { logger } from '../../../logger';
|
||||||
|
import type { WorkspaceDatabase } from './sqlite';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a backup of the database to the given destination.
|
||||||
|
*/
|
||||||
|
export async function exportDatabase(db: WorkspaceDatabase, dest: string) {
|
||||||
|
await fs.copyFile(db.path, dest);
|
||||||
|
logger.log('export: ', dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// export async function startBackup(db: WorkspaceDatabase, dest: string) {
|
||||||
|
// let timeout: NodeJS.Timeout | null;
|
||||||
|
// async function backup() {
|
||||||
|
// await fs.copyFile(db.path, dest);
|
||||||
|
// logger.log('backup: ', dest);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// backup();
|
||||||
|
|
||||||
|
// const _db = await db.sqliteDB$;
|
||||||
|
|
||||||
|
// _db.on('change', () => {
|
||||||
|
// if (timeout) {
|
||||||
|
// clearTimeout(timeout);
|
||||||
|
// }
|
||||||
|
// timeout = setTimeout(async () => {
|
||||||
|
// await backup();
|
||||||
|
// timeout = null;
|
||||||
|
// }, 1000);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
216
apps/electron/layers/main/src/data/sqlite.ts
Normal file
216
apps/electron/layers/main/src/data/sqlite.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
import type { Database } from 'sqlite3';
|
||||||
|
import sqlite3Default from 'sqlite3';
|
||||||
|
import * as Y from 'yjs';
|
||||||
|
|
||||||
|
import { logger } from '../../../logger';
|
||||||
|
import type { AppContext } from '../context';
|
||||||
|
|
||||||
|
const sqlite3 = sqlite3Default.verbose();
|
||||||
|
|
||||||
|
const schemas = [
|
||||||
|
`CREATE TABLE IF NOT EXISTS "updates" (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
data BLOB NOT NULL,
|
||||||
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS "blobs" (
|
||||||
|
key TEXT PRIMARY KEY NOT NULL,
|
||||||
|
data BLOB NOT NULL,
|
||||||
|
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
|
||||||
|
)`,
|
||||||
|
];
|
||||||
|
|
||||||
|
sqlite3.verbose();
|
||||||
|
|
||||||
|
interface UpdateRow {
|
||||||
|
id: number;
|
||||||
|
data: Buffer;
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BlobRow {
|
||||||
|
key: string;
|
||||||
|
data: Buffer;
|
||||||
|
timestamp: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class WorkspaceDatabase {
|
||||||
|
sqliteDB$: Promise<Database>;
|
||||||
|
ydoc = new Y.Doc();
|
||||||
|
_db: Database | null = null;
|
||||||
|
|
||||||
|
ready: Promise<Uint8Array>;
|
||||||
|
|
||||||
|
constructor(public path: string) {
|
||||||
|
this.sqliteDB$ = this.reconnectDB();
|
||||||
|
logger.log('open db', path);
|
||||||
|
|
||||||
|
this.ydoc.on('update', update => {
|
||||||
|
this.addUpdateToSQLite(update);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ready = (async () => {
|
||||||
|
const updates = await this.getUpdates();
|
||||||
|
updates.forEach(update => {
|
||||||
|
Y.applyUpdate(this.ydoc, update.data);
|
||||||
|
});
|
||||||
|
return this.getEncodedDocUpdates();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// release resources
|
||||||
|
destroy = () => {
|
||||||
|
this._db?.close();
|
||||||
|
this.ydoc.destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
reconnectDB = async () => {
|
||||||
|
logger.log('open db', this.path);
|
||||||
|
if (this._db) {
|
||||||
|
const _db = this._db;
|
||||||
|
await new Promise<void>(res =>
|
||||||
|
_db.close(() => {
|
||||||
|
res();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (this.sqliteDB$ = new Promise(res => {
|
||||||
|
// use cached version?
|
||||||
|
const db = new sqlite3.Database(this.path, error => {
|
||||||
|
if (error) {
|
||||||
|
logger.error('open db error', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._db = db;
|
||||||
|
|
||||||
|
db.exec(schemas.join(';'), () => {
|
||||||
|
res(db);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
getEncodedDocUpdates = () => {
|
||||||
|
return Y.encodeStateAsUpdate(this.ydoc);
|
||||||
|
};
|
||||||
|
|
||||||
|
// non-blocking and use yDoc to validate the update
|
||||||
|
// after that, the update is added to the db
|
||||||
|
applyUpdate = (data: Uint8Array) => {
|
||||||
|
Y.applyUpdate(this.ydoc, data);
|
||||||
|
// todo: trim the updates when the number of records is too large
|
||||||
|
// 1. store the current ydoc state in the db
|
||||||
|
// 2. then delete the old updates
|
||||||
|
// yjs-idb will always trim the db for the first time after DB is loaded
|
||||||
|
};
|
||||||
|
|
||||||
|
addBlob = async (key: string, data: Uint8Array) => {
|
||||||
|
const db = await this.sqliteDB$;
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
db.run(
|
||||||
|
'INSERT INTO blobs (key, data) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET data = ?',
|
||||||
|
[key, data, data],
|
||||||
|
err => {
|
||||||
|
if (err) {
|
||||||
|
logger.error('addBlob', err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getBlob = async (key: string) => {
|
||||||
|
const db = await this.sqliteDB$;
|
||||||
|
return new Promise<Uint8Array | null>((resolve, reject) => {
|
||||||
|
db.get<BlobRow>(
|
||||||
|
'SELECT data FROM blobs WHERE key = ?',
|
||||||
|
[key],
|
||||||
|
(err, row) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error('getBlob', err);
|
||||||
|
reject(err);
|
||||||
|
} else if (!row) {
|
||||||
|
logger.error('getBlob', 'not found');
|
||||||
|
resolve(null);
|
||||||
|
} else {
|
||||||
|
resolve(row.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
deleteBlob = async (key: string) => {
|
||||||
|
const db = await this.sqliteDB$;
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
db.run('DELETE FROM blobs WHERE key = ?', [key], err => {
|
||||||
|
if (err) {
|
||||||
|
logger.error('deleteBlob', err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
getPersistentBlobKeys = async () => {
|
||||||
|
const db = await this.sqliteDB$;
|
||||||
|
return new Promise<string[]>((resolve, reject) => {
|
||||||
|
db.all<BlobRow>('SELECT key FROM blobs', (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error('getPersistentBlobKeys', err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(rows.map(row => row.key));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private getUpdates = async () => {
|
||||||
|
const db = await this.sqliteDB$;
|
||||||
|
return new Promise<{ id: number; data: any }[]>((resolve, reject) => {
|
||||||
|
// do we need to order by id?
|
||||||
|
db.all<UpdateRow>('SELECT * FROM updates', (err, rows) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error('getUpdates', err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(rows);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
private addUpdateToSQLite = async (data: Uint8Array) => {
|
||||||
|
const db = await this.sqliteDB$;
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
db.run('INSERT INTO updates (data) VALUES (?)', [data], err => {
|
||||||
|
if (err) {
|
||||||
|
logger.error('addUpdateToSQLite', err);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openWorkspaceDatabase(
|
||||||
|
context: AppContext,
|
||||||
|
workspaceId: string
|
||||||
|
) {
|
||||||
|
const basePath = path.join(context.appDataPath, 'workspaces', workspaceId);
|
||||||
|
// hmmm.... blocking api but it should be fine, right?
|
||||||
|
await fs.ensureDir(basePath);
|
||||||
|
const dbPath = path.join(basePath, 'storage.db');
|
||||||
|
|
||||||
|
return new WorkspaceDatabase(dbPath);
|
||||||
|
}
|
||||||
20
apps/electron/layers/main/src/data/workspace.ts
Normal file
20
apps/electron/layers/main/src/data/workspace.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
|
import type { AppContext } from '../context';
|
||||||
|
|
||||||
|
export async function listWorkspaces(context: AppContext) {
|
||||||
|
const basePath = path.join(context.appDataPath, 'workspaces');
|
||||||
|
return fs.readdir(basePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteWorkspace(context: AppContext, id: string) {
|
||||||
|
const basePath = path.join(context.appDataPath, 'workspaces', id);
|
||||||
|
const movedPath = path.join(
|
||||||
|
context.appDataPath,
|
||||||
|
'delete-workspaces',
|
||||||
|
`${id}`
|
||||||
|
);
|
||||||
|
return fs.move(basePath, movedPath);
|
||||||
|
}
|
||||||
184
apps/electron/layers/main/src/handlers.ts
Normal file
184
apps/electron/layers/main/src/handlers.ts
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
import {
|
||||||
|
app,
|
||||||
|
BrowserWindow,
|
||||||
|
dialog,
|
||||||
|
ipcMain,
|
||||||
|
nativeTheme,
|
||||||
|
shell,
|
||||||
|
} from 'electron';
|
||||||
|
import { parse } from 'url';
|
||||||
|
|
||||||
|
import { logger } from '../../logger';
|
||||||
|
import { isMacOS } from '../../utils';
|
||||||
|
import { appContext } from './context';
|
||||||
|
import { exportDatabase } from './data/export';
|
||||||
|
import type { WorkspaceDatabase } from './data/sqlite';
|
||||||
|
import { openWorkspaceDatabase } from './data/sqlite';
|
||||||
|
import { deleteWorkspace, listWorkspaces } from './data/workspace';
|
||||||
|
import { getExchangeTokenParams, oauthEndpoint } from './google-auth';
|
||||||
|
|
||||||
|
let currentWorkspaceId = '';
|
||||||
|
|
||||||
|
const dbMapping = new Map<string, WorkspaceDatabase>();
|
||||||
|
|
||||||
|
async function ensureWorkspaceDB(id: string) {
|
||||||
|
let workspaceDB = dbMapping.get(id);
|
||||||
|
if (!workspaceDB) {
|
||||||
|
// hmm... potential race condition?
|
||||||
|
workspaceDB = await openWorkspaceDatabase(appContext, id);
|
||||||
|
dbMapping.set(id, workspaceDB);
|
||||||
|
}
|
||||||
|
await workspaceDB.ready;
|
||||||
|
return workspaceDB;
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerWorkspaceHandlers() {
|
||||||
|
ipcMain.handle('workspace:list', async _ => {
|
||||||
|
logger.info('list workspaces');
|
||||||
|
return listWorkspaces(appContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('workspace:delete', async (_, id) => {
|
||||||
|
logger.info('delete workspace', id);
|
||||||
|
return deleteWorkspace(appContext, id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerUIHandlers() {
|
||||||
|
ipcMain.handle('ui:theme-change', async (_, theme) => {
|
||||||
|
nativeTheme.themeSource = theme;
|
||||||
|
logger.info('theme change', theme);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('ui:sidebar-visibility-change', async (_, visible) => {
|
||||||
|
// todo
|
||||||
|
// detect if os is macos
|
||||||
|
if (isMacOS()) {
|
||||||
|
const windows = BrowserWindow.getAllWindows();
|
||||||
|
windows.forEach(w => {
|
||||||
|
// hide window buttons when sidebar is not visible
|
||||||
|
w.setWindowButtonVisibility(visible);
|
||||||
|
});
|
||||||
|
logger.info('sidebar visibility change', visible);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('ui:workspace-change', async (_, workspaceId) => {
|
||||||
|
logger.info('workspace change', workspaceId);
|
||||||
|
currentWorkspaceId = workspaceId;
|
||||||
|
});
|
||||||
|
|
||||||
|
// @deprecated
|
||||||
|
ipcMain.handle('ui:get-google-oauth-code', async () => {
|
||||||
|
logger.info('starting google sign in ...');
|
||||||
|
shell.openExternal(oauthEndpoint);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const handleOpenUrl = async (_: any, url: string) => {
|
||||||
|
const mainWindow = BrowserWindow.getAllWindows().find(
|
||||||
|
w => !w.isDestroyed()
|
||||||
|
);
|
||||||
|
const urlObj = parse(url.replace('??', '?'), true);
|
||||||
|
if (!mainWindow || !url.startsWith('affine://auth-callback')) return;
|
||||||
|
const code = urlObj.query['code'] as string;
|
||||||
|
if (!code) return;
|
||||||
|
|
||||||
|
logger.info('google sign in code received from callback', code);
|
||||||
|
|
||||||
|
app.removeListener('open-url', handleOpenUrl);
|
||||||
|
resolve(getExchangeTokenParams(code));
|
||||||
|
};
|
||||||
|
|
||||||
|
app.on('open-url', handleOpenUrl);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
reject(new Error('Timed out'));
|
||||||
|
app.removeListener('open-url', handleOpenUrl);
|
||||||
|
}, 30000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('main:env-update', async (_, env, value) => {
|
||||||
|
process.env[env] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerDBHandlers() {
|
||||||
|
app.on('activate', () => {
|
||||||
|
for (const [_, workspaceDB] of dbMapping) {
|
||||||
|
workspaceDB.reconnectDB();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('db:get-doc', async (_, id) => {
|
||||||
|
logger.log('main: get doc', id);
|
||||||
|
const workspaceDB = await ensureWorkspaceDB(id);
|
||||||
|
return workspaceDB.getEncodedDocUpdates();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('db:apply-doc-update', async (_, id, update) => {
|
||||||
|
logger.log('main: apply doc update', id);
|
||||||
|
const workspaceDB = await ensureWorkspaceDB(id);
|
||||||
|
return workspaceDB.applyUpdate(update);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('db:add-blob', async (_, workspaceId, key, data) => {
|
||||||
|
logger.log('main: add blob', workspaceId, key);
|
||||||
|
const workspaceDB = await ensureWorkspaceDB(workspaceId);
|
||||||
|
return workspaceDB.addBlob(key, data);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('db:get-blob', async (_, workspaceId, key) => {
|
||||||
|
logger.log('main: get blob', workspaceId, key);
|
||||||
|
const workspaceDB = await ensureWorkspaceDB(workspaceId);
|
||||||
|
return workspaceDB.getBlob(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('db:get-persisted-blobs', async (_, workspaceId) => {
|
||||||
|
logger.log('main: get persisted blob keys', workspaceId);
|
||||||
|
const workspaceDB = await ensureWorkspaceDB(workspaceId);
|
||||||
|
return workspaceDB.getPersistentBlobKeys();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('db:delete-blob', async (_, workspaceId, key) => {
|
||||||
|
logger.log('main: delete blob', workspaceId, key);
|
||||||
|
const workspaceDB = await ensureWorkspaceDB(workspaceId);
|
||||||
|
return workspaceDB.deleteBlob(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('ui:open-db-folder', async _ => {
|
||||||
|
const workspaceDB = await ensureWorkspaceDB(currentWorkspaceId);
|
||||||
|
logger.log('main: open db folder', workspaceDB.path);
|
||||||
|
shell.showItemInFolder(workspaceDB.path);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('ui:open-load-db-file-dialog', async () => {
|
||||||
|
// todo
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('ui:open-save-db-file-dialog', async () => {
|
||||||
|
logger.log('main: open save db file dialog', currentWorkspaceId);
|
||||||
|
const workspaceDB = await ensureWorkspaceDB(currentWorkspaceId);
|
||||||
|
const ret = await dialog.showSaveDialog({
|
||||||
|
properties: ['showOverwriteConfirmation'],
|
||||||
|
title: 'Save Workspace',
|
||||||
|
buttonLabel: 'Save',
|
||||||
|
defaultPath: currentWorkspaceId + '.db',
|
||||||
|
message: 'Save Workspace as SQLite Database',
|
||||||
|
});
|
||||||
|
const filePath = ret.filePath;
|
||||||
|
if (ret.canceled || !filePath) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await exportDatabase(workspaceDB, filePath);
|
||||||
|
shell.showItemInFolder(filePath);
|
||||||
|
return filePath;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const registerHandlers = () => {
|
||||||
|
registerWorkspaceHandlers();
|
||||||
|
registerUIHandlers();
|
||||||
|
registerDBHandlers();
|
||||||
|
};
|
||||||
@@ -3,7 +3,8 @@ import './security-restrictions';
|
|||||||
import { app } from 'electron';
|
import { app } from 'electron';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
import { registerHandlers } from './app-state';
|
import { logger } from '../../logger';
|
||||||
|
import { registerHandlers } from './handlers';
|
||||||
import { restoreOrCreateWindow } from './main-window';
|
import { restoreOrCreateWindow } from './main-window';
|
||||||
import { registerProtocol } from './protocol';
|
import { registerProtocol } from './protocol';
|
||||||
|
|
||||||
@@ -22,6 +23,7 @@ if (process.defaultApp) {
|
|||||||
*/
|
*/
|
||||||
const isSingleInstance = app.requestSingleInstanceLock();
|
const isSingleInstance = app.requestSingleInstanceLock();
|
||||||
if (!isSingleInstance) {
|
if (!isSingleInstance) {
|
||||||
|
logger.info('Another instance is running, exiting...');
|
||||||
app.quit();
|
app.quit();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { BrowserWindow, nativeTheme } from 'electron';
|
|||||||
import electronWindowState from 'electron-window-state';
|
import electronWindowState from 'electron-window-state';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
|
import { logger } from '../../logger';
|
||||||
import { isMacOS } from '../../utils';
|
import { isMacOS } from '../../utils';
|
||||||
|
|
||||||
const IS_DEV = process.env.NODE_ENV === 'development';
|
const IS_DEV = process.env.NODE_ENV === 'development';
|
||||||
@@ -87,7 +88,7 @@ export async function restoreOrCreateWindow() {
|
|||||||
browserWindow.restore();
|
browserWindow.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
browserWindow.focus();
|
logger.info('Create main window');
|
||||||
|
|
||||||
return browserWindow;
|
return browserWindow;
|
||||||
}
|
}
|
||||||
|
|||||||
2
apps/electron/layers/preload/preload.d.ts
vendored
2
apps/electron/layers/preload/preload.d.ts
vendored
@@ -7,6 +7,6 @@ interface Window {
|
|||||||
*
|
*
|
||||||
* @see https://github.com/cawa-93/dts-for-context-bridge
|
* @see https://github.com/cawa-93/dts-for-context-bridge
|
||||||
*/
|
*/
|
||||||
readonly apis: { workspaceSync: (id: string) => Promise<any>; onThemeChange: (theme: string) => Promise<any>; onSidebarVisibilityChange: (visible: boolean) => Promise<any>; getGoogleOauthCode: () => Promise<{ requestInit: RequestInit; url: string; }>; updateEnv: (env: string, value: string) => void; };
|
readonly apis: { db: { getDoc: (id: string) => Promise<Uint8Array>; applyDocUpdate: (id: string, update: Uint8Array) => Promise<any>; addBlob: (workspaceId: string, key: string, data: Uint8Array) => Promise<any>; getBlob: (workspaceId: string, key: string) => Promise<Uint8Array>; deleteBlob: (workspaceId: string, key: string) => Promise<any>; getPersistedBlobs: (workspaceId: string) => Promise<string[]>; }; workspace: { list: () => Promise<string[]>; delete: (id: string) => Promise<void>; }; openLoadDBFileDialog: () => Promise<any>; openSaveDBFileDialog: () => Promise<any>; onThemeChange: (theme: string) => Promise<any>; onSidebarVisibilityChange: (visible: boolean) => Promise<any>; onWorkspaceChange: (workspaceId: string) => Promise<any>; openDBFolder: () => Promise<any>; getGoogleOauthCode: () => Promise<{ requestInit: RequestInit; url: string; }>; updateEnv: (env: string, value: string) => void; };
|
||||||
readonly appInfo: { electron: boolean; isMacOS: boolean; };
|
readonly appInfo: { electron: boolean; isMacOS: boolean; };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,32 @@ import { isMacOS } from '../../utils';
|
|||||||
* @see https://github.com/cawa-93/dts-for-context-bridge
|
* @see https://github.com/cawa-93/dts-for-context-bridge
|
||||||
*/
|
*/
|
||||||
contextBridge.exposeInMainWorld('apis', {
|
contextBridge.exposeInMainWorld('apis', {
|
||||||
workspaceSync: (id: string) => ipcRenderer.invoke('octo:workspace-sync', id),
|
db: {
|
||||||
|
// TODO: do we need to store the workspace list locally?
|
||||||
|
// workspace providers
|
||||||
|
getDoc: (id: string): Promise<Uint8Array | null> =>
|
||||||
|
ipcRenderer.invoke('db:get-doc', id),
|
||||||
|
applyDocUpdate: (id: string, update: Uint8Array) =>
|
||||||
|
ipcRenderer.invoke('db:apply-doc-update', id, update),
|
||||||
|
addBlob: (workspaceId: string, key: string, data: Uint8Array) =>
|
||||||
|
ipcRenderer.invoke('db:add-blob', workspaceId, key, data),
|
||||||
|
getBlob: (workspaceId: string, key: string): Promise<Uint8Array | null> =>
|
||||||
|
ipcRenderer.invoke('db:get-blob', workspaceId, key),
|
||||||
|
deleteBlob: (workspaceId: string, key: string) =>
|
||||||
|
ipcRenderer.invoke('db:delete-blob', workspaceId, key),
|
||||||
|
getPersistedBlobs: (workspaceId: string): Promise<string[]> =>
|
||||||
|
ipcRenderer.invoke('db:get-persisted-blobs', workspaceId),
|
||||||
|
},
|
||||||
|
|
||||||
|
workspace: {
|
||||||
|
list: (): Promise<string[]> => ipcRenderer.invoke('workspace:list'),
|
||||||
|
delete: (id: string): Promise<void> =>
|
||||||
|
ipcRenderer.invoke('workspace:delete', id),
|
||||||
|
// create will be implicitly called by db functions
|
||||||
|
},
|
||||||
|
|
||||||
|
openLoadDBFileDialog: () => ipcRenderer.invoke('ui:open-load-db-file-dialog'),
|
||||||
|
openSaveDBFileDialog: () => ipcRenderer.invoke('ui:open-save-db-file-dialog'),
|
||||||
|
|
||||||
// ui
|
// ui
|
||||||
onThemeChange: (theme: string) =>
|
onThemeChange: (theme: string) =>
|
||||||
@@ -31,6 +56,11 @@ contextBridge.exposeInMainWorld('apis', {
|
|||||||
onSidebarVisibilityChange: (visible: boolean) =>
|
onSidebarVisibilityChange: (visible: boolean) =>
|
||||||
ipcRenderer.invoke('ui:sidebar-visibility-change', visible),
|
ipcRenderer.invoke('ui:sidebar-visibility-change', visible),
|
||||||
|
|
||||||
|
onWorkspaceChange: (workspaceId: string) =>
|
||||||
|
ipcRenderer.invoke('ui:workspace-change', workspaceId),
|
||||||
|
|
||||||
|
openDBFolder: () => ipcRenderer.invoke('ui:open-db-folder'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Try sign in using Google and return a request object to exchange the code for a token
|
* Try sign in using Google and return a request object to exchange the code for a token
|
||||||
* Not exchange in Node side because it is easier to do it in the renderer with VPN
|
* Not exchange in Node side because it is easier to do it in the renderer with VPN
|
||||||
|
|||||||
@@ -31,6 +31,8 @@
|
|||||||
"@electron-forge/shared-types": "^6.1.1",
|
"@electron-forge/shared-types": "^6.1.1",
|
||||||
"@electron/rebuild": "^3.2.12",
|
"@electron/rebuild": "^3.2.12",
|
||||||
"@electron/remote": "2.0.9",
|
"@electron/remote": "2.0.9",
|
||||||
|
"@types/fs-extra": "^11.0.1",
|
||||||
|
"cross-env": "7.0.3",
|
||||||
"dts-for-context-bridge": "^0.7.1",
|
"dts-for-context-bridge": "^0.7.1",
|
||||||
"electron": "24.1.2",
|
"electron": "24.1.2",
|
||||||
"electron-squirrel-startup": "1.0.0",
|
"electron-squirrel-startup": "1.0.0",
|
||||||
@@ -38,11 +40,12 @@
|
|||||||
"zx": "^7.2.1"
|
"zx": "^7.2.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-env": "7.0.3",
|
"electron-log": "^5.0.0-beta.22",
|
||||||
"electron-window-state": "^5.0.3",
|
"electron-window-state": "^5.0.3",
|
||||||
"firebase": "^9.19.1",
|
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"undici": "^5.21.2"
|
"sqlite3": "^5.1.6",
|
||||||
|
"undici": "^5.21.2",
|
||||||
|
"yjs": "^13.5.53"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"protocols": [
|
"protocols": [
|
||||||
@@ -54,6 +57,8 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.5.0",
|
"stableVersion": "0.5.3",
|
||||||
"stableVersion": "0.5.3"
|
"installConfig": {
|
||||||
|
"hoistingLimits": "workspaces"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
apps/electron/resources/icons/dmg-background.png
Normal file
BIN
apps/electron/resources/icons/dmg-background.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
BIN
apps/electron/resources/icons/dmg-background@2x.png
Normal file
BIN
apps/electron/resources/icons/dmg-background@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 342 KiB |
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
const NODE_MAJOR_VERSION = 18;
|
const NODE_MAJOR_VERSION = 18;
|
||||||
|
|
||||||
const nativeNodeModulesPlugin = {
|
const nativeNodeModulesPlugin = {
|
||||||
@@ -29,7 +28,7 @@ export default () => {
|
|||||||
bundle: true,
|
bundle: true,
|
||||||
target: `node${NODE_MAJOR_VERSION}`,
|
target: `node${NODE_MAJOR_VERSION}`,
|
||||||
platform: 'node',
|
platform: 'node',
|
||||||
external: ['electron'],
|
external: ['electron', 'sqlite3'],
|
||||||
plugins: [nativeNodeModulesPlugin],
|
plugins: [nativeNodeModulesPlugin],
|
||||||
define: define,
|
define: define,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,39 +33,44 @@ if (process.platform === 'win32') {
|
|||||||
$.shell = 'powershell.exe';
|
$.shell = 'powershell.exe';
|
||||||
$.prefix = '';
|
$.prefix = '';
|
||||||
}
|
}
|
||||||
// step 1: build web (nextjs) dist
|
|
||||||
process.env.ENABLE_LEGACY_PROVIDER = 'false';
|
|
||||||
cd(repoRootDir);
|
cd(repoRootDir);
|
||||||
await $`yarn add`;
|
|
||||||
await $`yarn build`;
|
|
||||||
await $`yarn export`;
|
|
||||||
|
|
||||||
// step 1.5: amend sourceMappingURL to allow debugging in devtools
|
// step 1: build electron resources
|
||||||
await glob('**/*.{js,css}', { cwd: affineWebOutDir }).then(files => {
|
|
||||||
return files.map(async file => {
|
|
||||||
const dir = path.dirname(file);
|
|
||||||
const fullpath = path.join(affineWebOutDir, file);
|
|
||||||
let content = await fs.readFile(fullpath, 'utf-8');
|
|
||||||
// replace # sourceMappingURL=76-6370cd185962bc89.js.map
|
|
||||||
// to # sourceMappingURL=assets://./{dir}/76-6370cd185962bc89.js.map
|
|
||||||
content = content.replace(/# sourceMappingURL=(.*)\.map/g, (_, p1) => {
|
|
||||||
return `# sourceMappingURL=assets://./${dir}/${p1}.map`;
|
|
||||||
});
|
|
||||||
await fs.writeFile(fullpath, content);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
await fs.move(affineWebOutDir, publicAffineOutDir, { overwrite: true });
|
|
||||||
|
|
||||||
// step 2: build electron resources
|
|
||||||
await buildLayers();
|
await buildLayers();
|
||||||
echo('Build layers done');
|
echo('Build layers done');
|
||||||
|
|
||||||
|
// step 2: build web (nextjs) dist
|
||||||
|
if (!process.env.SKIP_WEB_BUILD) {
|
||||||
|
process.env.ENABLE_LEGACY_PROVIDER = 'false';
|
||||||
|
await $`yarn build`;
|
||||||
|
await $`yarn export`;
|
||||||
|
|
||||||
|
// step 1.5: amend sourceMappingURL to allow debugging in devtools
|
||||||
|
await glob('**/*.{js,css}', { cwd: affineWebOutDir }).then(files => {
|
||||||
|
return files.map(async file => {
|
||||||
|
const dir = path.dirname(file);
|
||||||
|
const fullpath = path.join(affineWebOutDir, file);
|
||||||
|
let content = await fs.readFile(fullpath, 'utf-8');
|
||||||
|
// replace # sourceMappingURL=76-6370cd185962bc89.js.map
|
||||||
|
// to # sourceMappingURL=assets://./{dir}/76-6370cd185962bc89.js.map
|
||||||
|
content = content.replace(/# sourceMappingURL=(.*)\.map/g, (_, p1) => {
|
||||||
|
return `# sourceMappingURL=assets://./${dir}/${p1}.map`;
|
||||||
|
});
|
||||||
|
await fs.writeFile(fullpath, content);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await fs.move(affineWebOutDir, publicAffineOutDir, { overwrite: true });
|
||||||
|
}
|
||||||
|
|
||||||
/// --------
|
/// --------
|
||||||
/// --------
|
/// --------
|
||||||
/// --------
|
/// --------
|
||||||
async function cleanup() {
|
async function cleanup() {
|
||||||
await fs.emptyDir(publicAffineOutDir);
|
if (!process.env.SKIP_WEB_BUILD) {
|
||||||
|
await fs.emptyDir(publicAffineOutDir);
|
||||||
|
}
|
||||||
await fs.emptyDir(path.join(electronRootDir, 'layers', 'main', 'dist'));
|
await fs.emptyDir(path.join(electronRootDir, 'layers', 'main', 'dist'));
|
||||||
await fs.emptyDir(path.join(electronRootDir, 'layers', 'preload', 'dist'));
|
await fs.emptyDir(path.join(electronRootDir, 'layers', 'preload', 'dist'));
|
||||||
await fs.remove(path.join(electronRootDir, 'out'));
|
await fs.remove(path.join(electronRootDir, 'out'));
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
rootCurrentWorkspaceIdAtom,
|
rootCurrentWorkspaceIdAtom,
|
||||||
rootWorkspacesMetadataAtom,
|
rootWorkspacesMetadataAtom,
|
||||||
} from '@affine/workspace/atom';
|
} from '@affine/workspace/atom';
|
||||||
|
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { atom } from 'jotai';
|
import { atom } from 'jotai';
|
||||||
|
|
||||||
@@ -46,6 +47,21 @@ rootWorkspacesMetadataAtom.onMount = setAtom => {
|
|||||||
}
|
}
|
||||||
return metadata;
|
return metadata;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (environment.isDesktop) {
|
||||||
|
window.apis.workspace.list().then(workspaceIDs => {
|
||||||
|
const newMetadata = workspaceIDs.map(w => ({
|
||||||
|
id: w,
|
||||||
|
flavour: WorkspaceFlavour.LOCAL,
|
||||||
|
}));
|
||||||
|
setAtom(metadata => {
|
||||||
|
return [
|
||||||
|
...metadata,
|
||||||
|
...newMetadata.filter(m => !metadata.find(m2 => m2.id === m.id)),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,14 @@ export const ExportPanel = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Wrapper marginBottom="42px"> {t('Export Description')}</Wrapper>
|
<Wrapper marginBottom="42px"> {t('Export Description')}</Wrapper>
|
||||||
<Button type="light" shape="circle" disabled>
|
<Button
|
||||||
|
type="light"
|
||||||
|
shape="circle"
|
||||||
|
disabled={!environment.isDesktop}
|
||||||
|
onClick={() => {
|
||||||
|
window.apis.openSaveDBFileDialog();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t('Export AFFiNE backup file')}
|
{t('Export AFFiNE backup file')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -158,7 +158,13 @@ export const GeneralPanel: React.FC<PanelProps> = ({
|
|||||||
{/* </StyledRow>*/}
|
{/* </StyledRow>*/}
|
||||||
{/*)}*/}
|
{/*)}*/}
|
||||||
|
|
||||||
<StyledRow>
|
<StyledRow
|
||||||
|
onClick={() => {
|
||||||
|
if (environment.isDesktop) {
|
||||||
|
window.apis.openDBFolder();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<StyledSettingKey>{t('Workspace Type')}</StyledSettingKey>
|
<StyledSettingKey>{t('Workspace Type')}</StyledSettingKey>
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
workspace.flavour === WorkspaceFlavour.LOCAL ? (
|
workspace.flavour === WorkspaceFlavour.LOCAL ? (
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ export const ThemeModeSwitch = () => {
|
|||||||
const { setTheme, resolvedTheme } = useTheme();
|
const { setTheme, resolvedTheme } = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.apis?.onThemeChange(resolvedTheme === 'dark' ? 'dark' : 'light');
|
if (environment.isDesktop) {
|
||||||
|
window.apis?.onThemeChange(resolvedTheme === 'dark' ? 'dark' : 'light');
|
||||||
|
}
|
||||||
}, [resolvedTheme]);
|
}, [resolvedTheme]);
|
||||||
|
|
||||||
const [isHover, setIsHover] = useState(false);
|
const [isHover, setIsHover] = useState(false);
|
||||||
|
|||||||
@@ -95,7 +95,9 @@ export const WorkSpaceSliderBar: React.FC<WorkSpaceSliderBarProps> = ({
|
|||||||
const show = isPublicWorkspace ? false : sidebarOpen;
|
const show = isPublicWorkspace ? false : sidebarOpen;
|
||||||
const actualWidth = floatingSlider ? 'calc(10vw + 400px)' : sliderWidth;
|
const actualWidth = floatingSlider ? 'calc(10vw + 400px)' : sliderWidth;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.apis?.onSidebarVisibilityChange(sidebarOpen);
|
if (environment.isDesktop) {
|
||||||
|
window.apis?.onSidebarVisibilityChange(sidebarOpen);
|
||||||
|
}
|
||||||
}, [sidebarOpen]);
|
}, [sidebarOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ export function useSyncRouterWithCurrentWorkspaceId(router: NextRouter) {
|
|||||||
if (targetWorkspace) {
|
if (targetWorkspace) {
|
||||||
console.log('set workspace id', workspaceId);
|
console.log('set workspace id', workspaceId);
|
||||||
setCurrentWorkspaceId(targetWorkspace.id);
|
setCurrentWorkspaceId(targetWorkspace.id);
|
||||||
|
if (environment.isDesktop) {
|
||||||
|
window.apis?.onWorkspaceChange(targetWorkspace.id);
|
||||||
|
}
|
||||||
void router.push({
|
void router.push({
|
||||||
pathname: '/workspace/[workspaceId]/all',
|
pathname: '/workspace/[workspaceId]/all',
|
||||||
query: {
|
query: {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"It takes up more space on your device": {
|
"It takes up more space on your device": {
|
||||||
"": "It takes up more space on your device."
|
"": "It takes up more space on your device."
|
||||||
},
|
},
|
||||||
"Export AFFiNE backup file": "Export AFFiNE backup file (coming soon)",
|
"Export AFFiNE backup file": "Export AFFiNE backup file",
|
||||||
"Saved then enable AFFiNE Cloud": "All changes are saved locally, click to enable AFFiNE Cloud.",
|
"Saved then enable AFFiNE Cloud": "All changes are saved locally, click to enable AFFiNE Cloud.",
|
||||||
"Not now": "Not now",
|
"Not now": "Not now",
|
||||||
"Export Description": "You can export the entire Workspace data for backup, and the exported data can be re-imported.",
|
"Export Description": "You can export the entire Workspace data for backup, and the exported data can be re-imported.",
|
||||||
|
|||||||
@@ -178,7 +178,7 @@
|
|||||||
"Owner": "所有者",
|
"Owner": "所有者",
|
||||||
"Published to Web": "公开到互联网",
|
"Published to Web": "公开到互联网",
|
||||||
"Data sync mode": "数据同步模式",
|
"Data sync mode": "数据同步模式",
|
||||||
"Export AFFiNE backup file": "导出 AFFiNE 备份文件(即将到来)",
|
"Export AFFiNE backup file": "导出 AFFiNE 备份文件",
|
||||||
"Export Description": "您可以导出整个工作区数据进行备份,导出的数据可以重新被导入。",
|
"Export Description": "您可以导出整个工作区数据进行备份,导出的数据可以重新被导入。",
|
||||||
"It takes up little space on your device": {
|
"It takes up little space on your device": {
|
||||||
"": "此操作会在你的设备上占用少许空间。"
|
"": "此操作会在你的设备上占用少许空间。"
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { atomWithSyncStorage } from '@affine/jotai';
|
import { atomWithSyncStorage } from '@affine/jotai';
|
||||||
import type { WorkspaceFlavour } from '@affine/workspace/type';
|
|
||||||
import type { EditorContainer } from '@blocksuite/editor';
|
import type { EditorContainer } from '@blocksuite/editor';
|
||||||
import { atom, createStore } from 'jotai';
|
import { atom, createStore } from 'jotai';
|
||||||
import { atomWithStorage, createJSONStorage } from 'jotai/utils';
|
import { atomWithStorage, createJSONStorage } from 'jotai/utils';
|
||||||
|
|
||||||
|
import type { WorkspaceFlavour } from './type';
|
||||||
|
|
||||||
export type RootWorkspaceMetadata = {
|
export type RootWorkspaceMetadata = {
|
||||||
id: string;
|
id: string;
|
||||||
flavour: WorkspaceFlavour;
|
flavour: WorkspaceFlavour;
|
||||||
@@ -45,5 +46,13 @@ export const rootCurrentEditorAtom = atom<Readonly<EditorContainer> | null>(
|
|||||||
);
|
);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
const getStorage = () => createJSONStorage(() => localStorage);
|
||||||
|
|
||||||
|
export const getStoredWorkspaceMeta = () => {
|
||||||
|
const storage = getStorage();
|
||||||
|
const data = storage.getItem('jotai-workspaces') as RootWorkspaceMetadata[];
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
// global store
|
// global store
|
||||||
export const rootStore = createStore();
|
export const rootStore = createStore();
|
||||||
|
|||||||
25
packages/workspace/src/blob/sqlite-blob-storage.ts
Normal file
25
packages/workspace/src/blob/sqlite-blob-storage.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { BlobStorage } from '@blocksuite/store';
|
||||||
|
|
||||||
|
export const createSQLiteStorage = (workspaceId: string): BlobStorage => {
|
||||||
|
return {
|
||||||
|
crud: {
|
||||||
|
get: async (key: string) => {
|
||||||
|
const buffer = await window.apis.db.getBlob(workspaceId, key);
|
||||||
|
return buffer ? new Blob([buffer]) : null;
|
||||||
|
},
|
||||||
|
set: async (key: string, value: Blob) => {
|
||||||
|
return window.apis.db.addBlob(
|
||||||
|
workspaceId,
|
||||||
|
key,
|
||||||
|
new Uint8Array(await value.arrayBuffer())
|
||||||
|
);
|
||||||
|
},
|
||||||
|
delete: async (key: string) => {
|
||||||
|
return window.apis.db.deleteBlob(workspaceId, key);
|
||||||
|
},
|
||||||
|
list: async () => {
|
||||||
|
return window.apis.db.getPersistedBlobs(workspaceId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -85,18 +85,32 @@ export const CRUD: WorkspaceCRUD<WorkspaceFlavour.LOCAL> = {
|
|||||||
}
|
}
|
||||||
data.splice(idx, 1);
|
data.splice(idx, 1);
|
||||||
storage.setItem(kStoreKey, [...data]);
|
storage.setItem(kStoreKey, [...data]);
|
||||||
|
// flywire
|
||||||
|
if (window.apis && environment.isDesktop) {
|
||||||
|
await window.apis.workspace.delete(workspace.id);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
list: async () => {
|
list: async () => {
|
||||||
logger.debug('list');
|
logger.debug('list');
|
||||||
const storage = getStorage();
|
const storage = getStorage();
|
||||||
!Array.isArray(storage.getItem(kStoreKey)) &&
|
let allWorkspaceIDs: string[] = Array.isArray(storage.getItem(kStoreKey))
|
||||||
storage.setItem(kStoreKey, []);
|
? (storage.getItem(kStoreKey) as z.infer<typeof schema>)
|
||||||
return (
|
: [];
|
||||||
await Promise.all(
|
|
||||||
(storage.getItem(kStoreKey) as z.infer<typeof schema>).map(id =>
|
// workspaces in desktop
|
||||||
CRUD.get(id)
|
if (window.apis && environment.isDesktop) {
|
||||||
)
|
const desktopIds = await window.apis.workspace.list();
|
||||||
)
|
// the ids maybe a subset of the local storage
|
||||||
|
const moreWorkspaces = desktopIds.filter(
|
||||||
|
id => !allWorkspaceIDs.includes(id)
|
||||||
|
);
|
||||||
|
allWorkspaceIDs = [...allWorkspaceIDs, ...moreWorkspaces];
|
||||||
|
storage.setItem(kStoreKey, allWorkspaceIDs);
|
||||||
|
}
|
||||||
|
const workspaces = (
|
||||||
|
await Promise.all(allWorkspaceIDs.map(id => CRUD.get(id)))
|
||||||
).filter(item => item !== null) as LocalWorkspace[];
|
).filter(item => item !== null) as LocalWorkspace[];
|
||||||
|
|
||||||
|
return workspaces;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import type { SQLiteProvider } from '@affine/workspace/type';
|
||||||
|
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||||
|
import type { Y as YType } from '@blocksuite/store';
|
||||||
|
import { uuidv4, Workspace } from '@blocksuite/store';
|
||||||
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { createSQLiteProvider } from '../index';
|
||||||
|
|
||||||
|
const Y = Workspace.Y;
|
||||||
|
|
||||||
|
let id: string;
|
||||||
|
let workspace: Workspace;
|
||||||
|
let provider: SQLiteProvider;
|
||||||
|
|
||||||
|
let offlineYdoc: YType.Doc;
|
||||||
|
|
||||||
|
vi.stubGlobal('window', {
|
||||||
|
apis: {
|
||||||
|
db: {
|
||||||
|
getDoc: async (id: string) => {
|
||||||
|
return Y.encodeStateAsUpdate(offlineYdoc);
|
||||||
|
},
|
||||||
|
applyDocUpdate: async (id: string, update: Uint8Array) => {
|
||||||
|
Y.applyUpdate(offlineYdoc, update, 'sqlite');
|
||||||
|
},
|
||||||
|
getPersistedBlobs: async (id: string) => {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
} satisfies Partial<typeof window.apis.db>,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.stubGlobal('environment', {
|
||||||
|
isDesktop: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
id = uuidv4();
|
||||||
|
workspace = new Workspace({
|
||||||
|
id,
|
||||||
|
isSSR: true,
|
||||||
|
});
|
||||||
|
workspace.register(AffineSchemas).register(__unstableSchemas);
|
||||||
|
provider = createSQLiteProvider(workspace);
|
||||||
|
offlineYdoc = new Y.Doc();
|
||||||
|
offlineYdoc.getText('text').insert(0, '');
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SQLite provider', () => {
|
||||||
|
test('connect', async () => {
|
||||||
|
// on connect, the updates from sqlite should be sync'ed to the existing ydoc
|
||||||
|
// and ydoc should be sync'ed back to sqlite
|
||||||
|
// Workspace.Y.applyUpdate(workspace.doc);
|
||||||
|
workspace.doc.getText('text').insert(0, 'mem-hello');
|
||||||
|
|
||||||
|
expect(offlineYdoc.getText('text').toString()).toBe('');
|
||||||
|
|
||||||
|
await provider.connect();
|
||||||
|
|
||||||
|
expect(offlineYdoc.getText('text').toString()).toBe('mem-hello');
|
||||||
|
expect(workspace.doc.getText('text').toString()).toBe('mem-hello');
|
||||||
|
|
||||||
|
workspace.doc.getText('text').insert(0, 'world');
|
||||||
|
|
||||||
|
// check if the data are sync'ed
|
||||||
|
expect(offlineYdoc.getText('text').toString()).toBe('worldmem-hello');
|
||||||
|
});
|
||||||
|
|
||||||
|
// todo: test disconnect
|
||||||
|
// todo: test blob sync
|
||||||
|
});
|
||||||
@@ -4,15 +4,13 @@ import {
|
|||||||
getLoginStorage,
|
getLoginStorage,
|
||||||
storageChangeSlot,
|
storageChangeSlot,
|
||||||
} from '@affine/workspace/affine/login';
|
} from '@affine/workspace/affine/login';
|
||||||
import type { Provider } from '@affine/workspace/type';
|
import type { Provider, SQLiteProvider } from '@affine/workspace/type';
|
||||||
import type {
|
import type {
|
||||||
AffineWebSocketProvider,
|
AffineWebSocketProvider,
|
||||||
LocalIndexedDBProvider,
|
LocalIndexedDBProvider,
|
||||||
} from '@affine/workspace/type';
|
} from '@affine/workspace/type';
|
||||||
import type {
|
import type { BlobManager, Disposable } from '@blocksuite/store';
|
||||||
Disposable,
|
import { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
|
||||||
Workspace as BlockSuiteWorkspace,
|
|
||||||
} from '@blocksuite/store';
|
|
||||||
import { assertExists } from '@blocksuite/store';
|
import { assertExists } from '@blocksuite/store';
|
||||||
import {
|
import {
|
||||||
createIndexedDBProvider as create,
|
createIndexedDBProvider as create,
|
||||||
@@ -20,7 +18,9 @@ import {
|
|||||||
} from '@toeverything/y-indexeddb';
|
} from '@toeverything/y-indexeddb';
|
||||||
|
|
||||||
import { createBroadCastChannelProvider } from './broad-cast-channel';
|
import { createBroadCastChannelProvider } from './broad-cast-channel';
|
||||||
import { localProviderLogger } from './logger';
|
import { localProviderLogger as logger } from './logger';
|
||||||
|
|
||||||
|
const Y = BlockSuiteWorkspace.Y;
|
||||||
|
|
||||||
const createAffineWebSocketProvider = (
|
const createAffineWebSocketProvider = (
|
||||||
blockSuiteWorkspace: BlockSuiteWorkspace
|
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||||
@@ -54,12 +54,12 @@ const createAffineWebSocketProvider = (
|
|||||||
connect: false,
|
connect: false,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
localProviderLogger.info('connect', webSocketProvider.url);
|
logger.info('connect', webSocketProvider.url);
|
||||||
webSocketProvider.connect();
|
webSocketProvider.connect();
|
||||||
},
|
},
|
||||||
disconnect: () => {
|
disconnect: () => {
|
||||||
assertExists(webSocketProvider);
|
assertExists(webSocketProvider);
|
||||||
localProviderLogger.info('disconnect', webSocketProvider.url);
|
logger.info('disconnect', webSocketProvider.url);
|
||||||
webSocketProvider.destroy();
|
webSocketProvider.destroy();
|
||||||
webSocketProvider = null;
|
webSocketProvider = null;
|
||||||
dispose?.dispose();
|
dispose?.dispose();
|
||||||
@@ -119,10 +119,7 @@ const createIndexedDBProvider = (
|
|||||||
// todo: cleanup data
|
// todo: cleanup data
|
||||||
},
|
},
|
||||||
connect: () => {
|
connect: () => {
|
||||||
localProviderLogger.info(
|
logger.info('connect indexeddb provider', blockSuiteWorkspace.id);
|
||||||
'connect indexeddb provider',
|
|
||||||
blockSuiteWorkspace.id
|
|
||||||
);
|
|
||||||
indexeddbProvider.connect();
|
indexeddbProvider.connect();
|
||||||
indexeddbProvider.whenSynced
|
indexeddbProvider.whenSynced
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -139,20 +136,94 @@ const createIndexedDBProvider = (
|
|||||||
},
|
},
|
||||||
disconnect: () => {
|
disconnect: () => {
|
||||||
assertExists(indexeddbProvider);
|
assertExists(indexeddbProvider);
|
||||||
localProviderLogger.info(
|
logger.info('disconnect indexeddb provider', blockSuiteWorkspace.id);
|
||||||
'disconnect indexeddb provider',
|
|
||||||
blockSuiteWorkspace.id
|
|
||||||
);
|
|
||||||
indexeddbProvider.disconnect();
|
indexeddbProvider.disconnect();
|
||||||
callbacks.ready = false;
|
callbacks.ready = false;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createSQLiteProvider = (
|
||||||
|
blockSuiteWorkspace: BlockSuiteWorkspace
|
||||||
|
): SQLiteProvider => {
|
||||||
|
const sqliteOrigin = Symbol('sqlite-provider-origin');
|
||||||
|
// make sure it is being used in Electron with APIs
|
||||||
|
assertExists(environment.isDesktop && window.apis);
|
||||||
|
|
||||||
|
function handleUpdate(update: Uint8Array, origin: unknown) {
|
||||||
|
if (origin === sqliteOrigin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.apis.db.applyDocUpdate(blockSuiteWorkspace.id, update);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncBlobIntoSQLite(bs: BlobManager) {
|
||||||
|
const persistedKeys = await window.apis.db.getPersistedBlobs(
|
||||||
|
blockSuiteWorkspace.id
|
||||||
|
);
|
||||||
|
|
||||||
|
const allKeys = await bs.list();
|
||||||
|
const keysToPersist = allKeys.filter(k => !persistedKeys.includes(k));
|
||||||
|
|
||||||
|
logger.info('persisting blobs', keysToPersist, 'to sqlite');
|
||||||
|
keysToPersist.forEach(async k => {
|
||||||
|
const blob = await bs.get(k);
|
||||||
|
if (!blob) {
|
||||||
|
logger.warn('blob url not found', k);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.apis.db.addBlob(
|
||||||
|
blockSuiteWorkspace.id,
|
||||||
|
k,
|
||||||
|
new Uint8Array(await blob.arrayBuffer())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = {
|
||||||
|
flavour: 'sqlite',
|
||||||
|
background: true,
|
||||||
|
cleanup: () => {
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
},
|
||||||
|
connect: async () => {
|
||||||
|
logger.info('connecting sqlite provider', blockSuiteWorkspace.id);
|
||||||
|
const updates = await window.apis.db.getDoc(blockSuiteWorkspace.id);
|
||||||
|
|
||||||
|
if (updates) {
|
||||||
|
Y.applyUpdate(blockSuiteWorkspace.doc, updates, sqliteOrigin);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mergeUpdates = Y.encodeStateAsUpdate(blockSuiteWorkspace.doc);
|
||||||
|
|
||||||
|
// also apply updates to sqlite
|
||||||
|
window.apis.db.applyDocUpdate(blockSuiteWorkspace.id, mergeUpdates);
|
||||||
|
|
||||||
|
blockSuiteWorkspace.doc.on('update', handleUpdate);
|
||||||
|
|
||||||
|
const bs = blockSuiteWorkspace.blobs;
|
||||||
|
|
||||||
|
if (bs) {
|
||||||
|
// this can be non-blocking
|
||||||
|
syncBlobIntoSQLite(bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// blockSuiteWorkspace.doc.on('destroy', ...);
|
||||||
|
logger.info('connecting sqlite done', blockSuiteWorkspace.id);
|
||||||
|
},
|
||||||
|
disconnect: () => {
|
||||||
|
// todo: not implemented
|
||||||
|
},
|
||||||
|
} satisfies SQLiteProvider;
|
||||||
|
|
||||||
|
return provider;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createAffineWebSocketProvider,
|
createAffineWebSocketProvider,
|
||||||
createBroadCastChannelProvider,
|
createBroadCastChannelProvider,
|
||||||
createIndexedDBProvider,
|
createIndexedDBProvider,
|
||||||
|
createSQLiteProvider,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createLocalProviders = (
|
export const createLocalProviders = (
|
||||||
@@ -163,6 +234,7 @@ export const createLocalProviders = (
|
|||||||
config.enableBroadCastChannelProvider &&
|
config.enableBroadCastChannelProvider &&
|
||||||
createBroadCastChannelProvider(blockSuiteWorkspace),
|
createBroadCastChannelProvider(blockSuiteWorkspace),
|
||||||
createIndexedDBProvider(blockSuiteWorkspace),
|
createIndexedDBProvider(blockSuiteWorkspace),
|
||||||
|
environment.isDesktop && createSQLiteProvider(blockSuiteWorkspace),
|
||||||
] as any[]
|
] as any[]
|
||||||
).filter(v => Boolean(v));
|
).filter(v => Boolean(v));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ export interface LocalIndexedDBProvider extends BackgroundProvider {
|
|||||||
whenSynced: Promise<void>;
|
whenSynced: Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SQLiteProvider extends BaseProvider {
|
||||||
|
flavour: 'sqlite';
|
||||||
|
}
|
||||||
|
|
||||||
export interface AffineWebSocketProvider extends BaseProvider {
|
export interface AffineWebSocketProvider extends BaseProvider {
|
||||||
flavour: 'affine-websocket';
|
flavour: 'affine-websocket';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import type { createWorkspaceApis } from '@affine/workspace/affine/api';
|
import type { createWorkspaceApis } from '@affine/workspace/affine/api';
|
||||||
import { createAffineBlobStorage } from '@affine/workspace/blob';
|
import { createAffineBlobStorage } from '@affine/workspace/blob';
|
||||||
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
|
||||||
import type { Generator } from '@blocksuite/store';
|
import type { Generator, StoreOptions } from '@blocksuite/store';
|
||||||
import { createIndexeddbStorage, Workspace } from '@blocksuite/store';
|
import { createIndexeddbStorage, Workspace } from '@blocksuite/store';
|
||||||
|
|
||||||
|
import { createSQLiteStorage } from './blob/sqlite-blob-storage';
|
||||||
import { WorkspaceFlavour } from './type';
|
import { WorkspaceFlavour } from './type';
|
||||||
|
|
||||||
const hashMap = new Map<string, Workspace>();
|
const hashMap = new Map<string, Workspace>();
|
||||||
@@ -48,15 +49,26 @@ export function createEmptyBlockSuiteWorkspace(
|
|||||||
return hashMap.get(cacheKey) as Workspace;
|
return hashMap.get(cacheKey) as Workspace;
|
||||||
}
|
}
|
||||||
const idGenerator = config?.idGenerator;
|
const idGenerator = config?.idGenerator;
|
||||||
|
|
||||||
|
const blobStorages: StoreOptions['blobStorages'] = [];
|
||||||
|
|
||||||
|
if (flavour === WorkspaceFlavour.AFFINE) {
|
||||||
|
blobStorages.push(id =>
|
||||||
|
createAffineBlobStorage(id, config!.workspaceApis!)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
blobStorages.push(createIndexeddbStorage);
|
||||||
|
if (environment.isDesktop) {
|
||||||
|
blobStorages.push(createSQLiteStorage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const workspace = new Workspace({
|
const workspace = new Workspace({
|
||||||
id,
|
id,
|
||||||
isSSR: typeof window === 'undefined',
|
isSSR: typeof window === 'undefined',
|
||||||
blobStorages:
|
blobStorages: blobStorages,
|
||||||
flavour === WorkspaceFlavour.AFFINE
|
|
||||||
? [id => createAffineBlobStorage(id, config!.workspaceApis!)]
|
|
||||||
: typeof window === 'undefined'
|
|
||||||
? []
|
|
||||||
: [createIndexeddbStorage],
|
|
||||||
idGenerator,
|
idGenerator,
|
||||||
})
|
})
|
||||||
.register(AffineSchemas)
|
.register(AffineSchemas)
|
||||||
|
|||||||
261
yarn.lock
261
yarn.lock
@@ -122,15 +122,18 @@ __metadata:
|
|||||||
"@electron-forge/shared-types": ^6.1.1
|
"@electron-forge/shared-types": ^6.1.1
|
||||||
"@electron/rebuild": ^3.2.12
|
"@electron/rebuild": ^3.2.12
|
||||||
"@electron/remote": 2.0.9
|
"@electron/remote": 2.0.9
|
||||||
|
"@types/fs-extra": ^11.0.1
|
||||||
cross-env: 7.0.3
|
cross-env: 7.0.3
|
||||||
dts-for-context-bridge: ^0.7.1
|
dts-for-context-bridge: ^0.7.1
|
||||||
electron: 24.1.2
|
electron: 24.1.2
|
||||||
|
electron-log: ^5.0.0-beta.22
|
||||||
electron-squirrel-startup: 1.0.0
|
electron-squirrel-startup: 1.0.0
|
||||||
electron-window-state: ^5.0.3
|
electron-window-state: ^5.0.3
|
||||||
esbuild: ^0.17.17
|
esbuild: ^0.17.17
|
||||||
firebase: ^9.19.1
|
|
||||||
fs-extra: ^11.1.1
|
fs-extra: ^11.1.1
|
||||||
|
sqlite3: ^5.1.6
|
||||||
undici: ^5.21.2
|
undici: ^5.21.2
|
||||||
|
yjs: ^13.5.53
|
||||||
zx: ^7.2.1
|
zx: ^7.2.1
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
@@ -3867,7 +3870,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@gar/promisify@npm:^1.1.3":
|
"@gar/promisify@npm:^1.0.1, @gar/promisify@npm:^1.1.3":
|
||||||
version: 1.1.3
|
version: 1.1.3
|
||||||
resolution: "@gar/promisify@npm:1.1.3"
|
resolution: "@gar/promisify@npm:1.1.3"
|
||||||
checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1
|
checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1
|
||||||
@@ -4603,6 +4606,25 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@mapbox/node-pre-gyp@npm:^1.0.0":
|
||||||
|
version: 1.0.10
|
||||||
|
resolution: "@mapbox/node-pre-gyp@npm:1.0.10"
|
||||||
|
dependencies:
|
||||||
|
detect-libc: ^2.0.0
|
||||||
|
https-proxy-agent: ^5.0.0
|
||||||
|
make-dir: ^3.1.0
|
||||||
|
node-fetch: ^2.6.7
|
||||||
|
nopt: ^5.0.0
|
||||||
|
npmlog: ^5.0.1
|
||||||
|
rimraf: ^3.0.2
|
||||||
|
semver: ^7.3.5
|
||||||
|
tar: ^6.1.11
|
||||||
|
bin:
|
||||||
|
node-pre-gyp: bin/node-pre-gyp
|
||||||
|
checksum: 1a98db05d955b74dad3814679593df293b9194853698f3f5f1ed00ecd93128cdd4b14fb8767fe44ac6981ef05c23effcfdc88710e7c1de99ccb6f647890597c8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@mdx-js/react@npm:^2.1.5":
|
"@mdx-js/react@npm:^2.1.5":
|
||||||
version: 2.3.0
|
version: 2.3.0
|
||||||
resolution: "@mdx-js/react@npm:2.3.0"
|
resolution: "@mdx-js/react@npm:2.3.0"
|
||||||
@@ -5189,6 +5211,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@npmcli/fs@npm:^1.0.0":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "@npmcli/fs@npm:1.1.1"
|
||||||
|
dependencies:
|
||||||
|
"@gar/promisify": ^1.0.1
|
||||||
|
semver: ^7.3.5
|
||||||
|
checksum: f5ad92f157ed222e4e31c352333d0901df02c7c04311e42a81d8eb555d4ec4276ea9c635011757de20cc476755af33e91622838de573b17e52e2e7703f0a9965
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@npmcli/fs@npm:^2.1.0":
|
"@npmcli/fs@npm:^2.1.0":
|
||||||
version: 2.1.2
|
version: 2.1.2
|
||||||
resolution: "@npmcli/fs@npm:2.1.2"
|
resolution: "@npmcli/fs@npm:2.1.2"
|
||||||
@@ -5199,6 +5231,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@npmcli/move-file@npm:^1.0.1":
|
||||||
|
version: 1.1.2
|
||||||
|
resolution: "@npmcli/move-file@npm:1.1.2"
|
||||||
|
dependencies:
|
||||||
|
mkdirp: ^1.0.4
|
||||||
|
rimraf: ^3.0.2
|
||||||
|
checksum: c96381d4a37448ea280951e46233f7e541058cf57a57d4094dd4bdcaae43fa5872b5f2eb6bfb004591a68e29c5877abe3cdc210cb3588cbf20ab2877f31a7de7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@npmcli/move-file@npm:^2.0.0":
|
"@npmcli/move-file@npm:^2.0.0":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "@npmcli/move-file@npm:2.0.1"
|
resolution: "@npmcli/move-file@npm:2.0.1"
|
||||||
@@ -7592,6 +7634,13 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@tootallnate/once@npm:1":
|
||||||
|
version: 1.1.2
|
||||||
|
resolution: "@tootallnate/once@npm:1.1.2"
|
||||||
|
checksum: e1fb1bbbc12089a0cb9433dc290f97bddd062deadb6178ce9bcb93bb7c1aecde5e60184bc7065aec42fe1663622a213493c48bbd4972d931aae48315f18e1be9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@tootallnate/once@npm:2":
|
"@tootallnate/once@npm:2":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "@tootallnate/once@npm:2.0.0"
|
resolution: "@tootallnate/once@npm:2.0.0"
|
||||||
@@ -9042,7 +9091,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"agentkeepalive@npm:^4.2.1":
|
"agentkeepalive@npm:^4.1.3, agentkeepalive@npm:^4.2.1":
|
||||||
version: 4.3.0
|
version: 4.3.0
|
||||||
resolution: "agentkeepalive@npm:4.3.0"
|
resolution: "agentkeepalive@npm:4.3.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -10108,6 +10157,32 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"cacache@npm:^15.2.0":
|
||||||
|
version: 15.3.0
|
||||||
|
resolution: "cacache@npm:15.3.0"
|
||||||
|
dependencies:
|
||||||
|
"@npmcli/fs": ^1.0.0
|
||||||
|
"@npmcli/move-file": ^1.0.1
|
||||||
|
chownr: ^2.0.0
|
||||||
|
fs-minipass: ^2.0.0
|
||||||
|
glob: ^7.1.4
|
||||||
|
infer-owner: ^1.0.4
|
||||||
|
lru-cache: ^6.0.0
|
||||||
|
minipass: ^3.1.1
|
||||||
|
minipass-collect: ^1.0.2
|
||||||
|
minipass-flush: ^1.0.5
|
||||||
|
minipass-pipeline: ^1.2.2
|
||||||
|
mkdirp: ^1.0.3
|
||||||
|
p-map: ^4.0.0
|
||||||
|
promise-inflight: ^1.0.1
|
||||||
|
rimraf: ^3.0.2
|
||||||
|
ssri: ^8.0.1
|
||||||
|
tar: ^6.0.2
|
||||||
|
unique-filename: ^1.1.1
|
||||||
|
checksum: a07327c27a4152c04eb0a831c63c00390d90f94d51bb80624a66f4e14a6b6360bbf02a84421267bd4d00ca73ac9773287d8d7169e8d2eafe378d2ce140579db8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"cacache@npm:^16.1.0":
|
"cacache@npm:^16.1.0":
|
||||||
version: 16.1.3
|
version: 16.1.3
|
||||||
resolution: "cacache@npm:16.1.3"
|
resolution: "cacache@npm:16.1.3"
|
||||||
@@ -11550,7 +11625,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"detect-libc@npm:^2.0.1":
|
"detect-libc@npm:^2.0.0, detect-libc@npm:^2.0.1":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "detect-libc@npm:2.0.1"
|
resolution: "detect-libc@npm:2.0.1"
|
||||||
checksum: ccb05fcabbb555beb544d48080179c18523a343face9ee4e1a86605a8715b4169f94d663c21a03c310ac824592f2ba9a5270218819bb411ad7be578a527593d7
|
checksum: ccb05fcabbb555beb544d48080179c18523a343face9ee4e1a86605a8715b4169f94d663c21a03c310ac824592f2ba9a5270218819bb411ad7be578a527593d7
|
||||||
@@ -11949,6 +12024,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"electron-log@npm:^5.0.0-beta.22":
|
||||||
|
version: 5.0.0-beta.22
|
||||||
|
resolution: "electron-log@npm:5.0.0-beta.22"
|
||||||
|
checksum: 762bf8524079d6960be9fd186fa8b435e9900c2b8522ba38a760368e710a63f8917daef56ed2524c6678d58b692ae8290761ca00828890cb89c474e8c4a29907
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"electron-packager@npm:^17.1.1":
|
"electron-packager@npm:^17.1.1":
|
||||||
version: 17.1.1
|
version: 17.1.1
|
||||||
resolution: "electron-packager@npm:17.1.1"
|
resolution: "electron-packager@npm:17.1.1"
|
||||||
@@ -12079,7 +12161,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"encoding@npm:^0.1.13":
|
"encoding@npm:^0.1.12, encoding@npm:^0.1.13":
|
||||||
version: 0.1.13
|
version: 0.1.13
|
||||||
resolution: "encoding@npm:0.1.13"
|
resolution: "encoding@npm:0.1.13"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14705,6 +14787,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"http-proxy-agent@npm:^4.0.1":
|
||||||
|
version: 4.0.1
|
||||||
|
resolution: "http-proxy-agent@npm:4.0.1"
|
||||||
|
dependencies:
|
||||||
|
"@tootallnate/once": 1
|
||||||
|
agent-base: 6
|
||||||
|
debug: 4
|
||||||
|
checksum: c6a5da5a1929416b6bbdf77b1aca13888013fe7eb9d59fc292e25d18e041bb154a8dfada58e223fc7b76b9b2d155a87e92e608235201f77d34aa258707963a82
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"http-proxy-agent@npm:^5.0.0":
|
"http-proxy-agent@npm:^5.0.0":
|
||||||
version: 5.0.0
|
version: 5.0.0
|
||||||
resolution: "http-proxy-agent@npm:5.0.0"
|
resolution: "http-proxy-agent@npm:5.0.0"
|
||||||
@@ -17292,7 +17385,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2":
|
"make-dir@npm:^3.0.0, make-dir@npm:^3.0.2, make-dir@npm:^3.1.0":
|
||||||
version: 3.1.0
|
version: 3.1.0
|
||||||
resolution: "make-dir@npm:3.1.0"
|
resolution: "make-dir@npm:3.1.0"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17332,6 +17425,30 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"make-fetch-happen@npm:^9.1.0":
|
||||||
|
version: 9.1.0
|
||||||
|
resolution: "make-fetch-happen@npm:9.1.0"
|
||||||
|
dependencies:
|
||||||
|
agentkeepalive: ^4.1.3
|
||||||
|
cacache: ^15.2.0
|
||||||
|
http-cache-semantics: ^4.1.0
|
||||||
|
http-proxy-agent: ^4.0.1
|
||||||
|
https-proxy-agent: ^5.0.0
|
||||||
|
is-lambda: ^1.0.1
|
||||||
|
lru-cache: ^6.0.0
|
||||||
|
minipass: ^3.1.3
|
||||||
|
minipass-collect: ^1.0.2
|
||||||
|
minipass-fetch: ^1.3.2
|
||||||
|
minipass-flush: ^1.0.5
|
||||||
|
minipass-pipeline: ^1.2.4
|
||||||
|
negotiator: ^0.6.2
|
||||||
|
promise-retry: ^2.0.1
|
||||||
|
socks-proxy-agent: ^6.0.0
|
||||||
|
ssri: ^8.0.0
|
||||||
|
checksum: 0eb371c85fdd0b1584fcfdf3dc3c62395761b3c14658be02620c310305a9a7ecf1617a5e6fb30c1d081c5c8aaf177fa133ee225024313afabb7aa6a10f1e3d04
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"makeerror@npm:1.0.12":
|
"makeerror@npm:1.0.12":
|
||||||
version: 1.0.12
|
version: 1.0.12
|
||||||
resolution: "makeerror@npm:1.0.12"
|
resolution: "makeerror@npm:1.0.12"
|
||||||
@@ -17667,6 +17784,21 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"minipass-fetch@npm:^1.3.2":
|
||||||
|
version: 1.4.1
|
||||||
|
resolution: "minipass-fetch@npm:1.4.1"
|
||||||
|
dependencies:
|
||||||
|
encoding: ^0.1.12
|
||||||
|
minipass: ^3.1.0
|
||||||
|
minipass-sized: ^1.0.3
|
||||||
|
minizlib: ^2.0.0
|
||||||
|
dependenciesMeta:
|
||||||
|
encoding:
|
||||||
|
optional: true
|
||||||
|
checksum: ec93697bdb62129c4e6c0104138e681e30efef8c15d9429dd172f776f83898471bc76521b539ff913248cc2aa6d2b37b652c993504a51cc53282563640f29216
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"minipass-fetch@npm:^2.0.3":
|
"minipass-fetch@npm:^2.0.3":
|
||||||
version: 2.1.2
|
version: 2.1.2
|
||||||
resolution: "minipass-fetch@npm:2.1.2"
|
resolution: "minipass-fetch@npm:2.1.2"
|
||||||
@@ -17691,7 +17823,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"minipass-pipeline@npm:^1.2.4":
|
"minipass-pipeline@npm:^1.2.2, minipass-pipeline@npm:^1.2.4":
|
||||||
version: 1.2.4
|
version: 1.2.4
|
||||||
resolution: "minipass-pipeline@npm:1.2.4"
|
resolution: "minipass-pipeline@npm:1.2.4"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17709,7 +17841,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6":
|
"minipass@npm:^3.0.0, minipass@npm:^3.1.0, minipass@npm:^3.1.1, minipass@npm:^3.1.3, minipass@npm:^3.1.6":
|
||||||
version: 3.3.6
|
version: 3.3.6
|
||||||
resolution: "minipass@npm:3.3.6"
|
resolution: "minipass@npm:3.3.6"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17725,7 +17857,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2":
|
"minizlib@npm:^2.0.0, minizlib@npm:^2.1.1, minizlib@npm:^2.1.2":
|
||||||
version: 2.1.2
|
version: 2.1.2
|
||||||
resolution: "minizlib@npm:2.1.2"
|
resolution: "minizlib@npm:2.1.2"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -17934,7 +18066,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"negotiator@npm:0.6.3, negotiator@npm:^0.6.3":
|
"negotiator@npm:0.6.3, negotiator@npm:^0.6.2, negotiator@npm:^0.6.3":
|
||||||
version: 0.6.3
|
version: 0.6.3
|
||||||
resolution: "negotiator@npm:0.6.3"
|
resolution: "negotiator@npm:0.6.3"
|
||||||
checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9
|
checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9
|
||||||
@@ -18070,6 +18202,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"node-addon-api@npm:^4.2.0":
|
||||||
|
version: 4.3.0
|
||||||
|
resolution: "node-addon-api@npm:4.3.0"
|
||||||
|
dependencies:
|
||||||
|
node-gyp: latest
|
||||||
|
checksum: 3de396e23cc209f539c704583e8e99c148850226f6e389a641b92e8967953713228109f919765abc1f4355e801e8f41842f96210b8d61c7dcc10a477002dcf00
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"node-api-version@npm:^0.1.4":
|
"node-api-version@npm:^0.1.4":
|
||||||
version: 0.1.4
|
version: 0.1.4
|
||||||
resolution: "node-api-version@npm:0.1.4"
|
resolution: "node-api-version@npm:0.1.4"
|
||||||
@@ -18141,6 +18282,26 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"node-gyp@npm:8.x":
|
||||||
|
version: 8.4.1
|
||||||
|
resolution: "node-gyp@npm:8.4.1"
|
||||||
|
dependencies:
|
||||||
|
env-paths: ^2.2.0
|
||||||
|
glob: ^7.1.4
|
||||||
|
graceful-fs: ^4.2.6
|
||||||
|
make-fetch-happen: ^9.1.0
|
||||||
|
nopt: ^5.0.0
|
||||||
|
npmlog: ^6.0.0
|
||||||
|
rimraf: ^3.0.2
|
||||||
|
semver: ^7.3.5
|
||||||
|
tar: ^6.1.2
|
||||||
|
which: ^2.0.2
|
||||||
|
bin:
|
||||||
|
node-gyp: bin/node-gyp.js
|
||||||
|
checksum: 341710b5da39d3660e6a886b37e210d33f8282047405c2e62c277bcc744c7552c5b8b972ebc3a7d5c2813794e60cc48c3ebd142c46d6e0321db4db6c92dd0355
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"node-gyp@npm:^9.0.0, node-gyp@npm:latest":
|
"node-gyp@npm:^9.0.0, node-gyp@npm:latest":
|
||||||
version: 9.3.1
|
version: 9.3.1
|
||||||
resolution: "node-gyp@npm:9.3.1"
|
resolution: "node-gyp@npm:9.3.1"
|
||||||
@@ -18204,6 +18365,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"nopt@npm:^5.0.0":
|
||||||
|
version: 5.0.0
|
||||||
|
resolution: "nopt@npm:5.0.0"
|
||||||
|
dependencies:
|
||||||
|
abbrev: 1
|
||||||
|
bin:
|
||||||
|
nopt: bin/nopt.js
|
||||||
|
checksum: d35fdec187269503843924e0114c0c6533fb54bbf1620d0f28b4b60ba01712d6687f62565c55cc20a504eff0fbe5c63e22340c3fad549ad40469ffb611b04f2f
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"nopt@npm:^6.0.0":
|
"nopt@npm:^6.0.0":
|
||||||
version: 6.0.0
|
version: 6.0.0
|
||||||
resolution: "nopt@npm:6.0.0"
|
resolution: "nopt@npm:6.0.0"
|
||||||
@@ -21057,6 +21229,17 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"socks-proxy-agent@npm:^6.0.0":
|
||||||
|
version: 6.2.1
|
||||||
|
resolution: "socks-proxy-agent@npm:6.2.1"
|
||||||
|
dependencies:
|
||||||
|
agent-base: ^6.0.2
|
||||||
|
debug: ^4.3.3
|
||||||
|
socks: ^2.6.2
|
||||||
|
checksum: 9ca089d489e5ee84af06741135c4b0d2022977dad27ac8d649478a114cdce87849e8d82b7c22b51501a4116e231241592946fc7fae0afc93b65030ee57084f58
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"socks-proxy-agent@npm:^7.0.0":
|
"socks-proxy-agent@npm:^7.0.0":
|
||||||
version: 7.0.0
|
version: 7.0.0
|
||||||
resolution: "socks-proxy-agent@npm:7.0.0"
|
resolution: "socks-proxy-agent@npm:7.0.0"
|
||||||
@@ -21232,6 +21415,35 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"sqlite3@npm:^5.1.6":
|
||||||
|
version: 5.1.6
|
||||||
|
resolution: "sqlite3@npm:5.1.6"
|
||||||
|
dependencies:
|
||||||
|
"@mapbox/node-pre-gyp": ^1.0.0
|
||||||
|
node-addon-api: ^4.2.0
|
||||||
|
node-gyp: 8.x
|
||||||
|
tar: ^6.1.11
|
||||||
|
peerDependencies:
|
||||||
|
node-gyp: 8.x
|
||||||
|
dependenciesMeta:
|
||||||
|
node-gyp:
|
||||||
|
optional: true
|
||||||
|
peerDependenciesMeta:
|
||||||
|
node-gyp:
|
||||||
|
optional: true
|
||||||
|
checksum: ea640628843e37a63dfb4bd2c8429dbd7aab845c1a8204574dca3aac61486ab65bc0abfd99b48f1cead1f783171c6111c0cc4115335d5b95bb0b4eb44db162d5
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"ssri@npm:^8.0.0, ssri@npm:^8.0.1":
|
||||||
|
version: 8.0.1
|
||||||
|
resolution: "ssri@npm:8.0.1"
|
||||||
|
dependencies:
|
||||||
|
minipass: ^3.1.1
|
||||||
|
checksum: bc447f5af814fa9713aa201ec2522208ae0f4d8f3bda7a1f445a797c7b929a02720436ff7c478fb5edc4045adb02b1b88d2341b436a80798734e2494f1067b36
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ssri@npm:^9.0.0":
|
"ssri@npm:^9.0.0":
|
||||||
version: 9.0.1
|
version: 9.0.1
|
||||||
resolution: "ssri@npm:9.0.1"
|
resolution: "ssri@npm:9.0.1"
|
||||||
@@ -21841,7 +22053,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.13, tar@npm:^6.1.2":
|
"tar@npm:^6.0.2, tar@npm:^6.0.5, tar@npm:^6.1.11, tar@npm:^6.1.13, tar@npm:^6.1.2":
|
||||||
version: 6.1.13
|
version: 6.1.13
|
||||||
resolution: "tar@npm:6.1.13"
|
resolution: "tar@npm:6.1.13"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -22616,6 +22828,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"unique-filename@npm:^1.1.1":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "unique-filename@npm:1.1.1"
|
||||||
|
dependencies:
|
||||||
|
unique-slug: ^2.0.0
|
||||||
|
checksum: cf4998c9228cc7647ba7814e255dec51be43673903897b1786eff2ac2d670f54d4d733357eb08dea969aa5e6875d0e1bd391d668fbdb5a179744e7c7551a6f80
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"unique-filename@npm:^2.0.0":
|
"unique-filename@npm:^2.0.0":
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
resolution: "unique-filename@npm:2.0.1"
|
resolution: "unique-filename@npm:2.0.1"
|
||||||
@@ -22625,6 +22846,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"unique-slug@npm:^2.0.0":
|
||||||
|
version: 2.0.2
|
||||||
|
resolution: "unique-slug@npm:2.0.2"
|
||||||
|
dependencies:
|
||||||
|
imurmurhash: ^0.1.4
|
||||||
|
checksum: 5b6876a645da08d505dedb970d1571f6cebdf87044cb6b740c8dbb24f0d6e1dc8bdbf46825fd09f994d7cf50760e6f6e063cfa197d51c5902c00a861702eb75a
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"unique-slug@npm:^3.0.0":
|
"unique-slug@npm:^3.0.0":
|
||||||
version: 3.0.0
|
version: 3.0.0
|
||||||
resolution: "unique-slug@npm:3.0.0"
|
resolution: "unique-slug@npm:3.0.0"
|
||||||
@@ -23900,6 +24130,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"yjs@npm:^13.5.53":
|
||||||
|
version: 13.5.53
|
||||||
|
resolution: "yjs@npm:13.5.53"
|
||||||
|
dependencies:
|
||||||
|
lib0: ^0.2.72
|
||||||
|
checksum: be04e185d694c0c9de93d15d710d2789587226928dc2e66638ad8c075d825cced96727b43d5c50800ac4ec16120d08a273fa538116f751d48653365877e54422
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"yn@npm:3.1.1":
|
"yn@npm:3.1.1":
|
||||||
version: 3.1.1
|
version: 3.1.1
|
||||||
resolution: "yn@npm:3.1.1"
|
resolution: "yn@npm:3.1.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user