feat: auth & workspace load

This commit is contained in:
DarkSky
2022-12-30 19:42:01 +08:00
committed by DarkSky
parent 7b34ea010c
commit b01703b836
26 changed files with 161 additions and 518 deletions
+1
View File
@@ -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]);
};
+1
View File
@@ -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",
+3 -19
View File
@@ -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);
}
} }
} }
+4 -1
View File
@@ -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);
}
}
}
-45
View File
@@ -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 };
-138
View File
@@ -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();
-2
View File
@@ -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,
};
}
+3 -2
View File
@@ -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() {}
+14 -22
View File
@@ -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();
}); });
+20
View File
@@ -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: