mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-06 08:50:50 +08:00
feat: auth & workspace load
This commit is contained in:
@@ -19,6 +19,7 @@ const nextConfig = {
|
|||||||
EDITOR_VERSION: dependencies['@blocksuite/editor'],
|
EDITOR_VERSION: dependencies['@blocksuite/editor'],
|
||||||
},
|
},
|
||||||
webpack: config => {
|
webpack: config => {
|
||||||
|
config.experiments = { ...config.experiments, topLevelAwait: true };
|
||||||
config.resolve.alias['yjs'] = require.resolve('yjs');
|
config.resolve.alias['yjs'] = require.resolve('yjs');
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.md$/i,
|
test: /\.md$/i,
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ export const AffineIcon = () => {
|
|||||||
fill="#FFF"
|
fill="#FFF"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M18.6303 8.79688L11.2559 29.8393H15.5752L20.2661 15.2858L24.959 29.8393H29.2637L21.8881 8.79688H18.6303Z"
|
d="M18.6303 8.79688L11.2559 29.8393H15.5752L20.2661 15.2858L24.959 29.8393H29.2637L21.8881 8.79688H18.6303Z"
|
||||||
fill="#6880FF"
|
fill="#6880FF"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export const useEnsureWorkspace = () => {
|
|||||||
meta => meta.id.toString() === router.query.workspaceId
|
meta => meta.id.toString() === router.query.workspaceId
|
||||||
) === -1
|
) === -1
|
||||||
) {
|
) {
|
||||||
|
debugger;
|
||||||
router.push('/404');
|
router.push('/404');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -35,6 +36,7 @@ export const useEnsureWorkspace = () => {
|
|||||||
router.query.workspaceId &&
|
router.query.workspaceId &&
|
||||||
router.query.workspaceId !== defaultOutLineWorkspaceId
|
router.query.workspaceId !== defaultOutLineWorkspaceId
|
||||||
) {
|
) {
|
||||||
|
debugger;
|
||||||
router.push('/404');
|
router.push('/404');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useEffect } from 'react';
|
|||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import '@blocksuite/blocks';
|
import '@blocksuite/blocks';
|
||||||
import { EditorContainer } from '@blocksuite/editor';
|
import { EditorContainer } from '@blocksuite/editor';
|
||||||
import { BlockSchema } from '@blocksuite/blocks/models';
|
|
||||||
import type { LoadWorkspaceHandler, CreateEditorHandler } from './context';
|
import type { LoadWorkspaceHandler, CreateEditorHandler } from './context';
|
||||||
import { getDataCenter } from '@affine/datacenter';
|
import { getDataCenter } from '@affine/datacenter';
|
||||||
|
|
||||||
@@ -19,54 +18,15 @@ const DynamicBlocksuite = ({
|
|||||||
const openWorkspace: LoadWorkspaceHandler = (workspaceId: string, user) =>
|
const openWorkspace: LoadWorkspaceHandler = (workspaceId: string, user) =>
|
||||||
// eslint-disable-next-line no-async-promise-executor
|
// eslint-disable-next-line no-async-promise-executor
|
||||||
new Promise(async resolve => {
|
new Promise(async resolve => {
|
||||||
const workspace = await getDataCenter()
|
if (workspaceId) {
|
||||||
.then(dc => dc.initWorkspace(workspaceId))
|
const workspace = await getDataCenter().then(dc =>
|
||||||
.then(w => w.register(BlockSchema));
|
dc.initWorkspace(workspaceId, 'affine')
|
||||||
|
);
|
||||||
|
|
||||||
// console.log('websocket', websocket);
|
resolve(workspace);
|
||||||
console.log('user', user);
|
} else {
|
||||||
|
resolve(null);
|
||||||
// if (websocket && token.refresh) {
|
|
||||||
// // FIXME: if add websocket provider, the first page will be blank
|
|
||||||
// const ws = new WebsocketProvider(
|
|
||||||
// `ws${window.location.protocol === 'https:' ? 's' : ''}://${
|
|
||||||
// window.location.host
|
|
||||||
// }/api/sync/`,
|
|
||||||
// workspaceId,
|
|
||||||
// workspace.doc,
|
|
||||||
// {
|
|
||||||
// params: {
|
|
||||||
// token: token.refresh,
|
|
||||||
// },
|
|
||||||
// awareness: workspace.meta.awareness.awareness,
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
// ws.shouldConnect = false;
|
|
||||||
//
|
|
||||||
// // FIXME: there needs some method to destroy websocket.
|
|
||||||
// // Or we need a manager to manage websocket.
|
|
||||||
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
// // @ts-expect-error
|
|
||||||
// workspace.__ws__ = ws;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const indexDBProvider = workspace.providers.find(
|
|
||||||
// p => p instanceof IndexedDBDocProvider
|
|
||||||
// );
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
// if after update, the space:meta is empty, then we need to get map with doc
|
|
||||||
workspace.doc.getMap('space:meta');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (indexDBProvider) {
|
|
||||||
// (indexDBProvider as IndexedDBDocProvider).whenSynced.then(() => {
|
|
||||||
// resolve(workspace);
|
|
||||||
// });
|
|
||||||
// } else {
|
|
||||||
resolve(workspace);
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
setLoadWorkspaceHandler(openWorkspace);
|
setLoadWorkspaceHandler(openWorkspace);
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import { AccessTokenMessage, getWorkspaces, token } from '@affine/datacenter';
|
|
||||||
import { LoadWorkspaceHandler } from '../context';
|
|
||||||
|
|
||||||
export const useSyncData = ({
|
|
||||||
loadWorkspaceHandler,
|
|
||||||
}: {
|
|
||||||
loadWorkspaceHandler: LoadWorkspaceHandler;
|
|
||||||
}) => {
|
|
||||||
useEffect(() => {
|
|
||||||
if (!loadWorkspaceHandler) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const start = async () => {
|
|
||||||
const isLogin = await token.refreshToken().catch(() => false);
|
|
||||||
return isLogin;
|
|
||||||
};
|
|
||||||
start();
|
|
||||||
|
|
||||||
const callback = async (user: AccessTokenMessage | null) => {
|
|
||||||
const workspacesMeta = user
|
|
||||||
? await getWorkspaces().catch(() => {
|
|
||||||
return [];
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
// setState(state => ({
|
|
||||||
// ...state,
|
|
||||||
// user: user,
|
|
||||||
// workspacesMeta,
|
|
||||||
// synced: true,
|
|
||||||
// }));
|
|
||||||
return workspacesMeta;
|
|
||||||
};
|
|
||||||
|
|
||||||
token.onChange(callback);
|
|
||||||
token.refreshToken().catch(err => {
|
|
||||||
// FIXME: should resolve invalid refresh token
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
return () => {
|
|
||||||
token.offChange(callback);
|
|
||||||
};
|
|
||||||
}, [loadWorkspaceHandler]);
|
|
||||||
};
|
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"typescript": "^4.8.4"
|
"typescript": "^4.8.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@blocksuite/blocks": "0.3.0-20221228162706-9576a3a",
|
||||||
"@blocksuite/store": "0.3.0-20221228162706-9576a3a",
|
"@blocksuite/store": "0.3.0-20221228162706-9576a3a",
|
||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"firebase": "^9.15.0",
|
"firebase": "^9.15.0",
|
||||||
|
|||||||
@@ -1,17 +1,9 @@
|
|||||||
import { initializeApp } from 'firebase/app';
|
import { initializeApp } from 'firebase/app';
|
||||||
import {
|
import { getAuth, GoogleAuthProvider, signInWithPopup } from 'firebase/auth';
|
||||||
getAuth,
|
|
||||||
createUserWithEmailAndPassword,
|
|
||||||
signInWithEmailAndPassword,
|
|
||||||
GoogleAuthProvider,
|
|
||||||
signInWithPopup,
|
|
||||||
} from 'firebase/auth';
|
|
||||||
import type { User } from 'firebase/auth';
|
import type { User } from 'firebase/auth';
|
||||||
import { token } from './request';
|
// TODO: temporary reference, move all api into affine provider
|
||||||
|
import { token } from './datacenter/provider/affine/token';
|
||||||
|
|
||||||
/**
|
|
||||||
* firebaseConfig reference: https://firebase.google.com/docs/web/setup#add_firebase_to_your_app
|
|
||||||
*/
|
|
||||||
const app = initializeApp({
|
const app = initializeApp({
|
||||||
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
|
||||||
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
|
||||||
@@ -24,14 +16,6 @@ const app = initializeApp({
|
|||||||
|
|
||||||
export const firebaseAuth = getAuth(app);
|
export const firebaseAuth = getAuth(app);
|
||||||
|
|
||||||
const signUp = (email: string, password: string) => {
|
|
||||||
return createUserWithEmailAndPassword(firebaseAuth, email, password);
|
|
||||||
};
|
|
||||||
|
|
||||||
const signIn = (email: string, password: string) => {
|
|
||||||
return signInWithEmailAndPassword(firebaseAuth, email, password);
|
|
||||||
};
|
|
||||||
|
|
||||||
const googleAuthProvider = new GoogleAuthProvider();
|
const googleAuthProvider = new GoogleAuthProvider();
|
||||||
export const signInWithGoogle = async () => {
|
export const signInWithGoogle = async () => {
|
||||||
const user = await signInWithPopup(firebaseAuth, googleAuthProvider);
|
const user = await signInWithPopup(firebaseAuth, googleAuthProvider);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Workspace } from '@blocksuite/store';
|
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
import { BlockSchema } from '@blocksuite/blocks/models';
|
||||||
|
import { Workspace } from '@blocksuite/store';
|
||||||
|
|
||||||
import { AffineProvider, BaseProvider } from './provider/index.js';
|
import { AffineProvider, BaseProvider } from './provider/index.js';
|
||||||
import { MemoryProvider } from './provider/index.js';
|
import { MemoryProvider } from './provider/index.js';
|
||||||
@@ -27,7 +28,8 @@ export class DataCenter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _initWithProvider(id: string, providerId: string) {
|
private async _initWithProvider(id: string, providerId: string) {
|
||||||
const workspace = new Workspace({ room: id });
|
// init workspace & register block schema
|
||||||
|
const workspace = new Workspace({ room: id }).register(BlockSchema);
|
||||||
|
|
||||||
const Provider = this._providers.get(providerId);
|
const Provider = this._providers.get(providerId);
|
||||||
assert(Provider);
|
assert(Provider);
|
||||||
@@ -59,13 +61,20 @@ export class DataCenter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async initWorkspace(id: string, provider = 'memory'): Promise<Workspace> {
|
async initWorkspace(
|
||||||
if (!this._workspaces.has(id)) {
|
id: string,
|
||||||
this._workspaces.set(id, this._initWorkspace(id, provider));
|
provider = 'memory'
|
||||||
|
): Promise<Workspace | null> {
|
||||||
|
if (id) {
|
||||||
|
console.log('initWorkspace', id);
|
||||||
|
if (!this._workspaces.has(id)) {
|
||||||
|
this._workspaces.set(id, this._initWorkspace(id, provider));
|
||||||
|
}
|
||||||
|
const workspace = this._workspaces.get(id);
|
||||||
|
assert(workspace);
|
||||||
|
return workspace.then(w => w.workspace);
|
||||||
}
|
}
|
||||||
const workspace = this._workspaces.get(id);
|
return null;
|
||||||
assert(workspace);
|
|
||||||
return workspace.then(w => w.workspace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setWorkspaceConfig(workspace: string, key: string, value: any) {
|
setWorkspaceConfig(workspace: string, key: string, value: any) {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { client } from './request.js';
|
||||||
|
|
||||||
|
export async function downloadWorkspace(
|
||||||
|
workspaceId: string
|
||||||
|
): Promise<ArrayBuffer> {
|
||||||
|
return client.get(`api/workspace/${workspaceId}/doc`).arrayBuffer();
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { AccessTokenMessage } from './token';
|
|
||||||
|
|
||||||
export type Callback = (user: AccessTokenMessage | null) => void;
|
|
||||||
|
|
||||||
export class AuthorizationEvent {
|
|
||||||
private callbacks: Callback[] = [];
|
|
||||||
private lastState: AccessTokenMessage | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback will execute when call this function.
|
|
||||||
*/
|
|
||||||
onChange(callback: Callback) {
|
|
||||||
this.callbacks.push(callback);
|
|
||||||
callback(this.lastState);
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerChange(user: AccessTokenMessage | null) {
|
|
||||||
this.lastState = user;
|
|
||||||
this.callbacks.forEach(callback => callback(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
removeCallback(callback: Callback) {
|
|
||||||
const index = this.callbacks.indexOf(callback);
|
|
||||||
if (index > -1) {
|
|
||||||
this.callbacks.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { Workspace } from '@blocksuite/store';
|
import { Workspace } from '@blocksuite/store';
|
||||||
import { BaseProvider } from '../base.js';
|
import { BaseProvider, ConfigStore } from '../index.js';
|
||||||
import { ConfigStore } from '../index.js';
|
import { downloadWorkspace } from './apis.js';
|
||||||
import { token } from './token.js';
|
import { token, Callback } from './token.js';
|
||||||
import { Callback } from './events.js';
|
|
||||||
|
|
||||||
export class AffineProvider extends BaseProvider {
|
export class AffineProvider extends BaseProvider {
|
||||||
static id = 'affine';
|
static id = 'affine';
|
||||||
@@ -24,9 +23,23 @@ export class AffineProvider extends BaseProvider {
|
|||||||
assert(this._onTokenRefresh);
|
assert(this._onTokenRefresh);
|
||||||
|
|
||||||
token.onChange(this._onTokenRefresh);
|
token.onChange(this._onTokenRefresh);
|
||||||
|
|
||||||
|
// initial login token
|
||||||
if (token.isExpired) {
|
if (token.isExpired) {
|
||||||
const refreshToken = await this._config.get('token');
|
try {
|
||||||
await token.refreshToken(refreshToken);
|
const refreshToken = await this._config.get('token');
|
||||||
|
await token.refreshToken(refreshToken);
|
||||||
|
|
||||||
|
if (token.refresh) {
|
||||||
|
this._config.set('token', token.refresh);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(token.isLogin);
|
||||||
|
} catch (_) {
|
||||||
|
console.warn('authorization failed, fallback to local mode');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._config.set('token', token.refresh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +50,25 @@ export class AffineProvider extends BaseProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async initData() {
|
async initData() {
|
||||||
console.log('initData', token.isLogin);
|
const workspace = this._workspace;
|
||||||
|
const doc = workspace.doc;
|
||||||
|
|
||||||
|
console.log(workspace.room, token.isLogin);
|
||||||
|
|
||||||
|
if (workspace.room && token.isLogin) {
|
||||||
|
try {
|
||||||
|
const updates = await downloadWorkspace(workspace.room);
|
||||||
|
if (updates) {
|
||||||
|
Workspace.Y.applyUpdate(doc, new Uint8Array(updates));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Failed to init cloud workspace', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if after update, the space:meta is empty
|
||||||
|
// then we need to get map with doc
|
||||||
|
// just a workaround for yjs
|
||||||
|
doc.getMap('space:meta');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import kyOrigin from 'ky';
|
||||||
import ky from 'ky-universal';
|
import ky from 'ky-universal';
|
||||||
import { token } from './token.js';
|
import { token } from './token.js';
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export const bareClient = ky.extend({
|
|||||||
// ],
|
// ],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const client = bareClient.extend({
|
export const client = bareClient.extend({
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeRequest: [
|
beforeRequest: [
|
||||||
@@ -41,6 +43,3 @@ export const client = bareClient.extend({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export type { AccessTokenMessage } from './token';
|
|
||||||
export { token };
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { bareClient } from './request.js';
|
import { bareClient } from './request.js';
|
||||||
import { AuthorizationEvent, Callback } from './events.js';
|
|
||||||
|
|
||||||
export interface AccessTokenMessage {
|
export interface AccessTokenMessage {
|
||||||
create_at: number;
|
create_at: number;
|
||||||
@@ -10,6 +9,8 @@ export interface AccessTokenMessage {
|
|||||||
avatar_url: string;
|
avatar_url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Callback = (user: AccessTokenMessage | null) => void;
|
||||||
|
|
||||||
type LoginParams = {
|
type LoginParams = {
|
||||||
type: 'Google' | 'Refresh';
|
type: 'Google' | 'Refresh';
|
||||||
token: string;
|
token: string;
|
||||||
@@ -25,21 +26,7 @@ type LoginResponse = {
|
|||||||
const login = (params: LoginParams): Promise<LoginResponse> =>
|
const login = (params: LoginParams): Promise<LoginResponse> =>
|
||||||
bareClient.post('api/user/token', { json: params }).json();
|
bareClient.post('api/user/token', { json: params }).json();
|
||||||
|
|
||||||
function b64DecodeUnicode(str: string) {
|
|
||||||
// Going backwards: from byte stream, to percent-encoding, to original string.
|
|
||||||
return decodeURIComponent(
|
|
||||||
window
|
|
||||||
.atob(str)
|
|
||||||
.split('')
|
|
||||||
.map(function (c) {
|
|
||||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
||||||
})
|
|
||||||
.join('')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Token {
|
class Token {
|
||||||
private readonly _event: AuthorizationEvent;
|
|
||||||
private _accessToken!: string;
|
private _accessToken!: string;
|
||||||
private _refreshToken!: string;
|
private _refreshToken!: string;
|
||||||
|
|
||||||
@@ -47,16 +34,18 @@ class Token {
|
|||||||
private _padding?: Promise<LoginResponse>;
|
private _padding?: Promise<LoginResponse>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._event = new AuthorizationEvent();
|
|
||||||
this._setToken(); // fill with default value
|
this._setToken(); // fill with default value
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setToken(login?: LoginResponse) {
|
private _setToken(login?: LoginResponse) {
|
||||||
|
console.log('set login', login);
|
||||||
this._accessToken = login?.token || '';
|
this._accessToken = login?.token || '';
|
||||||
this._refreshToken = login?.refresh || '';
|
this._refreshToken = login?.refresh || '';
|
||||||
|
|
||||||
this._user = Token.parse(this._accessToken);
|
this._user = Token.parse(this._accessToken);
|
||||||
this._event.triggerChange(this._user);
|
if (login) {
|
||||||
|
this.triggerChange(this._user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async initToken(token: string) {
|
async initToken(token: string) {
|
||||||
@@ -96,21 +85,43 @@ class Token {
|
|||||||
|
|
||||||
static parse(token: string): AccessTokenMessage | null {
|
static parse(token: string): AccessTokenMessage | null {
|
||||||
try {
|
try {
|
||||||
const message: AccessTokenMessage = JSON.parse(
|
return JSON.parse(
|
||||||
b64DecodeUnicode(token.split('.')[1])
|
String.fromCharCode.apply(
|
||||||
|
null,
|
||||||
|
Array.from(
|
||||||
|
Uint8Array.from(
|
||||||
|
window.atob(
|
||||||
|
// split jwt
|
||||||
|
token.split('.')[1]
|
||||||
|
),
|
||||||
|
c => c.charCodeAt(0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
return message;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private callbacks: Callback[] = [];
|
||||||
|
private lastState: AccessTokenMessage | null = null;
|
||||||
|
|
||||||
|
triggerChange(user: AccessTokenMessage | null) {
|
||||||
|
this.lastState = user;
|
||||||
|
this.callbacks.forEach(callback => callback(user));
|
||||||
|
}
|
||||||
|
|
||||||
onChange(callback: Callback) {
|
onChange(callback: Callback) {
|
||||||
this._event.onChange(callback);
|
this.callbacks.push(callback);
|
||||||
|
callback(this.lastState);
|
||||||
}
|
}
|
||||||
|
|
||||||
offChange(callback: Callback) {
|
offChange(callback: Callback) {
|
||||||
this._event.removeCallback(callback);
|
const index = this.callbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
this.callbacks.splice(index, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
export { signInWithGoogle, onAuthStateChanged } from './auth';
|
export { signInWithGoogle, onAuthStateChanged } from './auth';
|
||||||
export * from './request';
|
|
||||||
export * from './sdks';
|
export * from './sdks';
|
||||||
export * from './websocket';
|
export * from './websocket';
|
||||||
|
|
||||||
export { getDataCenter } from './datacenter';
|
export { getDataCenter } from './datacenter';
|
||||||
|
|
||||||
|
// TODO: temporary reference, move all api into affine provider
|
||||||
|
export { token } from './datacenter/provider/affine/token';
|
||||||
|
export type { AccessTokenMessage } from './datacenter/provider/affine/token';
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { AccessTokenMessage } from './token';
|
|
||||||
|
|
||||||
export type Callback = (user: AccessTokenMessage | null) => void;
|
|
||||||
|
|
||||||
export class AuthorizationEvent {
|
|
||||||
private callbacks: Callback[] = [];
|
|
||||||
private lastState: AccessTokenMessage | null = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback will execute when call this function.
|
|
||||||
*/
|
|
||||||
onChange(callback: Callback) {
|
|
||||||
this.callbacks.push(callback);
|
|
||||||
callback(this.lastState);
|
|
||||||
}
|
|
||||||
|
|
||||||
triggerChange(user: AccessTokenMessage | null) {
|
|
||||||
this.lastState = user;
|
|
||||||
this.callbacks.forEach(callback => callback(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
removeCallback(callback: Callback) {
|
|
||||||
const index = this.callbacks.indexOf(callback);
|
|
||||||
if (index > -1) {
|
|
||||||
this.callbacks.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import ky from 'ky';
|
|
||||||
import { token } from './token';
|
|
||||||
|
|
||||||
export const bareClient = ky.extend({
|
|
||||||
retry: 1,
|
|
||||||
hooks: {
|
|
||||||
// afterResponse: [
|
|
||||||
// async (_request, _options, response) => {
|
|
||||||
// if (response.status === 200) {
|
|
||||||
// const data = await response.json();
|
|
||||||
// if (data.error) {
|
|
||||||
// return new Response(data.error.message, {
|
|
||||||
// status: data.error.code,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return response;
|
|
||||||
// },
|
|
||||||
// ],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
export const client = bareClient.extend({
|
|
||||||
hooks: {
|
|
||||||
beforeRequest: [
|
|
||||||
async request => {
|
|
||||||
if (token.isLogin) {
|
|
||||||
if (token.isExpired) await token.refreshToken();
|
|
||||||
request.headers.set('Authorization', token.token);
|
|
||||||
} else {
|
|
||||||
return new Response('Unauthorized', { status: 401 });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
beforeRetry: [
|
|
||||||
async ({ request }) => {
|
|
||||||
await token.refreshToken();
|
|
||||||
request.headers.set('Authorization', token.token);
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export type { AccessTokenMessage } from './token';
|
|
||||||
export { token };
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
import { bareClient } from '.';
|
|
||||||
import { AuthorizationEvent, Callback } from './events';
|
|
||||||
|
|
||||||
export interface AccessTokenMessage {
|
|
||||||
create_at: number;
|
|
||||||
exp: number;
|
|
||||||
email: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
avatar_url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TOKEN_KEY = 'affine_token';
|
|
||||||
|
|
||||||
type LoginParams = {
|
|
||||||
type: 'Google' | 'Refresh';
|
|
||||||
token: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type LoginResponse = {
|
|
||||||
// JWT, expires in a very short time
|
|
||||||
token: string;
|
|
||||||
// Refresh token
|
|
||||||
refresh: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const login = (params: LoginParams): Promise<LoginResponse> =>
|
|
||||||
bareClient.post('/api/user/token', { json: params }).json();
|
|
||||||
|
|
||||||
function b64DecodeUnicode(str: string) {
|
|
||||||
// Going backwards: from byte stream, to percent-encoding, to original string.
|
|
||||||
return decodeURIComponent(
|
|
||||||
window
|
|
||||||
.atob(str)
|
|
||||||
.split('')
|
|
||||||
.map(function (c) {
|
|
||||||
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
|
|
||||||
})
|
|
||||||
.join('')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRefreshToken() {
|
|
||||||
try {
|
|
||||||
return localStorage.getItem(TOKEN_KEY) || '';
|
|
||||||
} catch (_) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function setRefreshToken(token: string) {
|
|
||||||
try {
|
|
||||||
localStorage.setItem(TOKEN_KEY, token);
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Token {
|
|
||||||
private readonly _event: AuthorizationEvent;
|
|
||||||
private _accessToken: string;
|
|
||||||
private _refreshToken: string;
|
|
||||||
|
|
||||||
private _user: AccessTokenMessage | null;
|
|
||||||
private _padding?: Promise<LoginResponse>;
|
|
||||||
|
|
||||||
constructor(refreshToken?: string) {
|
|
||||||
this._accessToken = '';
|
|
||||||
this._refreshToken = refreshToken || getRefreshToken();
|
|
||||||
this._event = new AuthorizationEvent();
|
|
||||||
|
|
||||||
this._user = Token.parse(this._accessToken);
|
|
||||||
this._event.triggerChange(this._user);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _setToken(login: LoginResponse) {
|
|
||||||
this._accessToken = login.token;
|
|
||||||
this._refreshToken = login.refresh;
|
|
||||||
this._user = Token.parse(login.token);
|
|
||||||
|
|
||||||
this._event.triggerChange(this._user);
|
|
||||||
|
|
||||||
setRefreshToken(login.refresh);
|
|
||||||
}
|
|
||||||
|
|
||||||
async initToken(token: string) {
|
|
||||||
this._setToken(await login({ token, type: 'Google' }));
|
|
||||||
}
|
|
||||||
|
|
||||||
async refreshToken() {
|
|
||||||
if (!this._refreshToken) {
|
|
||||||
throw new Error('No authorization token.');
|
|
||||||
}
|
|
||||||
if (!this._padding) {
|
|
||||||
this._padding = login({
|
|
||||||
type: 'Refresh',
|
|
||||||
token: this._refreshToken,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this._setToken(await this._padding);
|
|
||||||
this._padding = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
get token() {
|
|
||||||
return this._accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
get refresh() {
|
|
||||||
return this._refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isLogin() {
|
|
||||||
return !!this._refreshToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
get isExpired() {
|
|
||||||
if (!this._user) return true;
|
|
||||||
return Date.now() - this._user.create_at > this._user.exp;
|
|
||||||
}
|
|
||||||
|
|
||||||
static parse(token: string): AccessTokenMessage | null {
|
|
||||||
try {
|
|
||||||
const message: AccessTokenMessage = JSON.parse(
|
|
||||||
b64DecodeUnicode(token.split('.')[1])
|
|
||||||
);
|
|
||||||
return message;
|
|
||||||
} catch (error) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onChange(callback: Callback) {
|
|
||||||
this._event.onChange(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
offChange(callback: Callback) {
|
|
||||||
this._event.removeCallback(callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const token = new Token();
|
|
||||||
@@ -1,4 +1,2 @@
|
|||||||
export * from './workspace';
|
export * from './workspace';
|
||||||
export * from './workspace.hook';
|
|
||||||
export * from './user';
|
export * from './user';
|
||||||
export * from './user.hook';
|
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
export type CommonError = { error: { code: string; message: string } };
|
|
||||||
export type MayError = Partial<CommonError>;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './common';
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import useSWR from 'swr';
|
|
||||||
import type { SWRConfiguration } from 'swr';
|
|
||||||
import { getUserByEmail } from './user';
|
|
||||||
import type { GetUserByEmailParams, User } from './user';
|
|
||||||
|
|
||||||
export const GET_USER_BY_EMAIL_SWR_TOKEN = 'user.getUserByEmail';
|
|
||||||
export function useGetUserByEmail(
|
|
||||||
params: GetUserByEmailParams,
|
|
||||||
config?: SWRConfiguration
|
|
||||||
) {
|
|
||||||
const { data, error, isLoading, mutate } = useSWR<User | null>(
|
|
||||||
[GET_USER_BY_EMAIL_SWR_TOKEN, params],
|
|
||||||
([_, params]) => getUserByEmail(params),
|
|
||||||
config
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
loading: isLoading,
|
|
||||||
data,
|
|
||||||
error,
|
|
||||||
mutate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { client } from '../request';
|
// TODO: temporary reference, move all api into affine provider
|
||||||
|
import { client } from '../datacenter/provider/affine/request';
|
||||||
|
|
||||||
export interface GetUserByEmailParams {
|
export interface GetUserByEmailParams {
|
||||||
email: string;
|
email: string;
|
||||||
@@ -17,5 +18,5 @@ export async function getUserByEmail(
|
|||||||
params: GetUserByEmailParams
|
params: GetUserByEmailParams
|
||||||
): Promise<User | null> {
|
): Promise<User | null> {
|
||||||
const searchParams = new URLSearchParams({ ...params });
|
const searchParams = new URLSearchParams({ ...params });
|
||||||
return client.get('/api/user', { searchParams }).json<User | null>();
|
return client.get('api/user', { searchParams }).json<User | null>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
import useSWR from 'swr';
|
|
||||||
import type { SWRConfiguration } from 'swr';
|
|
||||||
import {
|
|
||||||
getWorkspaceDetail,
|
|
||||||
updateWorkspace,
|
|
||||||
deleteWorkspace,
|
|
||||||
inviteMember,
|
|
||||||
Workspace,
|
|
||||||
} from './workspace';
|
|
||||||
import {
|
|
||||||
GetWorkspaceDetailParams,
|
|
||||||
WorkspaceDetail,
|
|
||||||
UpdateWorkspaceParams,
|
|
||||||
DeleteWorkspaceParams,
|
|
||||||
InviteMemberParams,
|
|
||||||
getWorkspaces,
|
|
||||||
} from './workspace';
|
|
||||||
|
|
||||||
export const GET_WORKSPACE_DETAIL_SWR_TOKEN = 'workspace.getWorkspaceDetail';
|
|
||||||
export function useGetWorkspaceDetail(
|
|
||||||
params: GetWorkspaceDetailParams,
|
|
||||||
config?: SWRConfiguration
|
|
||||||
) {
|
|
||||||
const { data, error, isLoading, mutate } = useSWR<WorkspaceDetail | null>(
|
|
||||||
[GET_WORKSPACE_DETAIL_SWR_TOKEN, params],
|
|
||||||
([_, params]) => getWorkspaceDetail(params),
|
|
||||||
config
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
error,
|
|
||||||
loading: isLoading,
|
|
||||||
mutate,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GET_WORKSPACES_SWR_TOKEN = 'workspace.getWorkspaces';
|
|
||||||
export function useGetWorkspaces(config?: SWRConfiguration) {
|
|
||||||
const { data, error, isLoading } = useSWR<Workspace[]>(
|
|
||||||
[GET_WORKSPACES_SWR_TOKEN],
|
|
||||||
() => getWorkspaces(),
|
|
||||||
config
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
error,
|
|
||||||
loading: isLoading,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const UPDATE_WORKSPACE_SWR_TOKEN = 'workspace.updateWorkspace';
|
|
||||||
/**
|
|
||||||
* I don't think a hook needed for update workspace.
|
|
||||||
* If you figure out the scene, please implement this function.
|
|
||||||
*/
|
|
||||||
export function useUpdateWorkspace() {}
|
|
||||||
|
|
||||||
export const DELETE_WORKSPACE_SWR_TOKEN = 'workspace.deleteWorkspace';
|
|
||||||
/**
|
|
||||||
* I don't think a hook needed for delete workspace.
|
|
||||||
* If you figure out the scene, please implement this function.
|
|
||||||
*/
|
|
||||||
export function useDeleteWorkspace() {}
|
|
||||||
|
|
||||||
export const INVITE_MEMBER_SWR_TOKEN = 'workspace.inviteMember';
|
|
||||||
/**
|
|
||||||
* I don't think a hook needed for invite member.
|
|
||||||
* If you figure out the scene, please implement this function.
|
|
||||||
*/
|
|
||||||
export function useInviteMember() {}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { client, bareClient } from '../request';
|
// TODO: temporary reference, move all api into affine provider
|
||||||
|
import { bareClient, client } from '../datacenter/provider/affine/request';
|
||||||
import { User } from './user';
|
import { User } from './user';
|
||||||
|
|
||||||
export interface GetWorkspaceDetailParams {
|
export interface GetWorkspaceDetailParams {
|
||||||
@@ -27,7 +28,7 @@ export interface Workspace {
|
|||||||
|
|
||||||
export async function getWorkspaces(): Promise<Workspace[]> {
|
export async function getWorkspaces(): Promise<Workspace[]> {
|
||||||
return client
|
return client
|
||||||
.get('/api/workspace', {
|
.get('api/workspace', {
|
||||||
headers: {
|
headers: {
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
},
|
},
|
||||||
@@ -43,7 +44,7 @@ export interface WorkspaceDetail extends Workspace {
|
|||||||
export async function getWorkspaceDetail(
|
export async function getWorkspaceDetail(
|
||||||
params: GetWorkspaceDetailParams
|
params: GetWorkspaceDetailParams
|
||||||
): Promise<WorkspaceDetail | null> {
|
): Promise<WorkspaceDetail | null> {
|
||||||
return client.get(`/api/workspace/${params.id}`).json();
|
return client.get(`api/workspace/${params.id}`).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Permission {
|
export interface Permission {
|
||||||
@@ -74,7 +75,7 @@ export interface GetWorkspaceMembersParams {
|
|||||||
export async function getWorkspaceMembers(
|
export async function getWorkspaceMembers(
|
||||||
params: GetWorkspaceDetailParams
|
params: GetWorkspaceDetailParams
|
||||||
): Promise<Member[]> {
|
): Promise<Member[]> {
|
||||||
return client.get(`/api/workspace/${params.id}/permission`).json();
|
return client.get(`api/workspace/${params.id}/permission`).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateWorkspaceParams {
|
export interface CreateWorkspaceParams {
|
||||||
@@ -85,7 +86,7 @@ export interface CreateWorkspaceParams {
|
|||||||
export async function createWorkspace(
|
export async function createWorkspace(
|
||||||
params: CreateWorkspaceParams
|
params: CreateWorkspaceParams
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return client.post('/api/workspace', { json: params }).json();
|
return client.post('api/workspace', { json: params }).json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateWorkspaceParams {
|
export interface UpdateWorkspaceParams {
|
||||||
@@ -97,7 +98,7 @@ export async function updateWorkspace(
|
|||||||
params: UpdateWorkspaceParams
|
params: UpdateWorkspaceParams
|
||||||
): Promise<{ public: boolean | null }> {
|
): Promise<{ public: boolean | null }> {
|
||||||
return client
|
return client
|
||||||
.post(`/api/workspace/${params.id}`, {
|
.post(`api/workspace/${params.id}`, {
|
||||||
json: {
|
json: {
|
||||||
public: params.public,
|
public: params.public,
|
||||||
},
|
},
|
||||||
@@ -112,7 +113,7 @@ export interface DeleteWorkspaceParams {
|
|||||||
export async function deleteWorkspace(
|
export async function deleteWorkspace(
|
||||||
params: DeleteWorkspaceParams
|
params: DeleteWorkspaceParams
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await client.delete(`/api/workspace/${params.id}`);
|
await client.delete(`api/workspace/${params.id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InviteMemberParams {
|
export interface InviteMemberParams {
|
||||||
@@ -125,7 +126,7 @@ export interface InviteMemberParams {
|
|||||||
*/
|
*/
|
||||||
export async function inviteMember(params: InviteMemberParams): Promise<void> {
|
export async function inviteMember(params: InviteMemberParams): Promise<void> {
|
||||||
return client
|
return client
|
||||||
.post(`/api/workspace/${params.id}/permission`, {
|
.post(`api/workspace/${params.id}/permission`, {
|
||||||
json: {
|
json: {
|
||||||
email: params.email,
|
email: params.email,
|
||||||
},
|
},
|
||||||
@@ -138,7 +139,7 @@ export interface RemoveMemberParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function removeMember(params: RemoveMemberParams): Promise<void> {
|
export async function removeMember(params: RemoveMemberParams): Promise<void> {
|
||||||
await client.delete(`/api/permission/${params.permissionId}`);
|
await client.delete(`api/permission/${params.permissionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AcceptInvitingParams {
|
export interface AcceptInvitingParams {
|
||||||
@@ -148,31 +149,22 @@ export interface AcceptInvitingParams {
|
|||||||
export async function acceptInviting(
|
export async function acceptInviting(
|
||||||
params: AcceptInvitingParams
|
params: AcceptInvitingParams
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
await bareClient.post(`/api/invitation/${params.invitingCode}`);
|
await bareClient.post(`api/invitation/${params.invitingCode}`);
|
||||||
}
|
|
||||||
|
|
||||||
export interface DownloadWOrkspaceParams {
|
|
||||||
workspaceId: string;
|
|
||||||
}
|
|
||||||
export async function downloadWorkspace(
|
|
||||||
params: DownloadWOrkspaceParams
|
|
||||||
): Promise<ArrayBuffer> {
|
|
||||||
return client.get(`/api/workspace/${params.workspaceId}/doc`).arrayBuffer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadBlob(params: { blob: Blob }): Promise<string> {
|
export async function uploadBlob(params: { blob: Blob }): Promise<string> {
|
||||||
return client.put('/api/blob', { body: params.blob }).text();
|
return client.put('api/blob', { body: params.blob }).text();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getBlob(params: {
|
export async function getBlob(params: {
|
||||||
blobId: string;
|
blobId: string;
|
||||||
}): Promise<ArrayBuffer> {
|
}): Promise<ArrayBuffer> {
|
||||||
return client.get(`/api/blob/${params.blobId}`).arrayBuffer();
|
return client.get(`api/blob/${params.blobId}`).arrayBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LeaveWorkspaceParams {
|
export interface LeaveWorkspaceParams {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
}
|
}
|
||||||
export async function leaveWorkspace({ id }: LeaveWorkspaceParams) {
|
export async function leaveWorkspace({ id }: LeaveWorkspaceParams) {
|
||||||
await client.delete(`/api/workspace/${id}/permission`).json();
|
await client.delete(`api/workspace/${id}/permission`).json();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ test('can init affine provider', async () => {
|
|||||||
const dataCenter = await getDataCenter();
|
const dataCenter = await getDataCenter();
|
||||||
|
|
||||||
// TODO: set constant token for testing
|
// TODO: set constant token for testing
|
||||||
await dataCenter.setWorkspaceConfig('test', 'token', '');
|
await dataCenter.setWorkspaceConfig(
|
||||||
|
'6',
|
||||||
|
'token',
|
||||||
|
'Zzq338Py_3veZwD4XTa0nyoDGsLqhd9nFeaT1p_SK43TAOCSkcV63Tn3kDUWfBI4JHKqX7mhED4IFejm_62KUpGXRWZv11c5BGay7Nhvb_br'
|
||||||
|
);
|
||||||
|
|
||||||
const workspace = await dataCenter.initWorkspace('test', 'affine');
|
const workspace = await dataCenter.initWorkspace('6', 'affine');
|
||||||
|
|
||||||
expect(workspace).toBeTruthy();
|
expect(workspace).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|||||||
Generated
+20
@@ -131,6 +131,7 @@ importers:
|
|||||||
|
|
||||||
packages/data-center:
|
packages/data-center:
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@blocksuite/blocks': 0.3.0-20221228162706-9576a3a
|
||||||
'@blocksuite/store': 0.3.0-20221228162706-9576a3a
|
'@blocksuite/store': 0.3.0-20221228162706-9576a3a
|
||||||
'@playwright/test': ^1.29.1
|
'@playwright/test': ^1.29.1
|
||||||
encoding: ^0.1.13
|
encoding: ^0.1.13
|
||||||
@@ -145,6 +146,7 @@ importers:
|
|||||||
y-protocols: ^1.0.5
|
y-protocols: ^1.0.5
|
||||||
yjs: ^13.5.43
|
yjs: ^13.5.43
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@blocksuite/blocks': 0.3.0-20221228162706-9576a3a_yjs@13.5.44
|
||||||
'@blocksuite/store': 0.3.0-20221228162706-9576a3a_yjs@13.5.44
|
'@blocksuite/store': 0.3.0-20221228162706-9576a3a_yjs@13.5.44
|
||||||
encoding: 0.1.13
|
encoding: 0.1.13
|
||||||
firebase: 9.15.0_encoding@0.1.13
|
firebase: 9.15.0_encoding@0.1.13
|
||||||
@@ -1490,6 +1492,24 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@blocksuite/blocks/0.3.0-20221228162706-9576a3a_yjs@13.5.44:
|
||||||
|
resolution: {integrity: sha512-gBwqynM0WQuUbsJbazBKIe+jkyoXYZPalYQlz0f1TT51kXmvyhQ1TlvsnZx94aW1JwT2st3z65Q197MmfZ8jSw==}
|
||||||
|
dependencies:
|
||||||
|
'@blocksuite/store': 0.3.0-20221228162706-9576a3a_yjs@13.5.44
|
||||||
|
'@tldraw/intersect': 1.8.0
|
||||||
|
highlight.js: 11.7.0
|
||||||
|
hotkeys-js: 3.10.1
|
||||||
|
lit: 2.5.0
|
||||||
|
perfect-freehand: 1.2.0
|
||||||
|
quill: 1.3.7
|
||||||
|
quill-cursors: 4.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
- yjs
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@blocksuite/blocks/0.3.1_yjs@13.5.44:
|
/@blocksuite/blocks/0.3.1_yjs@13.5.44:
|
||||||
resolution: {integrity: sha512-b0dGz2MG4yIgngJPRumaMY58wAsd2FEVSZl3tpCXlagK9I0HD165Bq70PxcaRHVjBSV1Gf29ZVHUF6BVTYogPw==}
|
resolution: {integrity: sha512-b0dGz2MG4yIgngJPRumaMY58wAsd2FEVSZl3tpCXlagK9I0HD165Bq70PxcaRHVjBSV1Gf29ZVHUF6BVTYogPw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user