refactor: init package @affine/workspace (#1661)

This commit is contained in:
Himself65 2023-03-23 11:17:38 -05:00 committed by GitHub
parent 84d27e939d
commit 69721f2a61
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 952 additions and 236 deletions

View File

@ -15,6 +15,7 @@
"@affine/env": "workspace:*",
"@affine/i18n": "workspace:*",
"@affine/templates": "workspace:*",
"@affine/workspace": "workspace:*",
"@blocksuite/blocks": "0.5.0-20230323085636-3110abb",
"@blocksuite/editor": "0.5.0-20230323085636-3110abb",
"@blocksuite/icons": "2.0.23",

View File

@ -1,3 +1,4 @@
import type { WorkspaceFlavour } from '@affine/workspace/type';
import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store';
import { assertExists } from '@blocksuite/store';
@ -6,7 +7,7 @@ import { atomWithStorage } from 'jotai/utils';
import { unstable_batchedUpdates } from 'react-dom';
import { WorkspacePlugins } from '../plugins';
import type { RemWorkspace, RemWorkspaceFlavour } from '../shared';
import type { RemWorkspace } from '../shared';
// workspace necessary atoms
export const currentWorkspaceIdAtom = atom<string | null>(null);
export const currentPageIdAtom = atom<string | null>(null);
@ -37,7 +38,7 @@ export const jotaiStore = createStore();
type JotaiWorkspace = {
id: string;
flavour: RemWorkspaceFlavour;
flavour: WorkspaceFlavour;
};
export const jotaiWorkspacesAtom = atomWithStorage<JotaiWorkspace[]>(

View File

@ -1,8 +1,8 @@
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { atom } from 'jotai/index';
import { BlockSuiteWorkspace } from '../../shared';
import { apis } from '../../shared/apis';
import { createEmptyBlockSuiteWorkspace } from '../../utils';
export const publicWorkspaceIdAtom = atom<string | null>(null);
export const publicBlockSuiteAtom = atom<Promise<BlockSuiteWorkspace>>(

View File

@ -1,11 +1,11 @@
'use client';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import type { EditorContainer } from '@blocksuite/editor';
import type { Page } from '@blocksuite/store';
import { Generator } from '@blocksuite/store';
import type React from 'react';
import { useCallback, useRef } from 'react';
import { createEmptyBlockSuiteWorkspace } from '../../../utils';
import { BlockSuiteEditor } from '../../blocksuite/block-suite-editor';
const blockSuiteWorkspace = createEmptyBlockSuiteWorkspace(

View File

@ -3,6 +3,7 @@
*/
import 'fake-indexeddb/auto';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { assertExists } from '@blocksuite/store';
import { render, renderHook } from '@testing-library/react';
import { createStore, getDefaultStore, Provider } from 'jotai';
@ -20,7 +21,7 @@ import {
import { useBlockSuiteWorkspaceHelper } from '../../hooks/use-blocksuite-workspace-helper';
import { useWorkspacesHelper } from '../../hooks/use-workspaces';
import { ThemeProvider } from '../../providers/ThemeProvider';
import { pathGenerator, RemWorkspaceFlavour } from '../../shared';
import { pathGenerator } from '../../shared';
import { WorkSpaceSliderBar } from '../pure/workspace-slider-bar';
vi.mock('../blocksuite/header/editor-mode-switch/CustomLottie', () => ({
@ -92,7 +93,7 @@ describe('WorkSpaceSliderBar', () => {
currentWorkspaceHook.result.current[1](id);
const currentWorkspace = await store.get(currentWorkspaceAtom);
expect(currentWorkspace).toBeDefined();
expect(currentWorkspace?.flavour).toBe(RemWorkspaceFlavour.LOCAL);
expect(currentWorkspace?.flavour).toBe(WorkspaceFlavour.LOCAL);
expect(currentWorkspace?.id).toBe(id);
const app = render(<App />);
const card = await app.findByTestId('current-workspace');

View File

@ -1,4 +1,6 @@
import { useTranslation } from '@affine/i18n';
import type { SettingPanel, WorkspaceRegistry } from '@affine/workspace/type';
import { settingPanel, WorkspaceFlavour } from '@affine/workspace/type';
import type { MouseEvent } from 'react';
import type React from 'react';
import { Suspense, useCallback, useEffect, useMemo, useRef } from 'react';
@ -6,12 +8,7 @@ import { preload } from 'swr';
import { useIsWorkspaceOwner } from '../../../hooks/affine/use-is-workspace-owner';
import { fetcher, QueryKey } from '../../../plugins/affine/fetcher';
import type {
AffineOfficialWorkspace,
FlavourToWorkspace,
SettingPanel,
} from '../../../shared';
import { RemWorkspaceFlavour, settingPanel } from '../../../shared';
import type { AffineOfficialWorkspace } from '../../../shared';
import { CollaborationPanel } from './panel/collaboration';
import { ExportPanel } from './panel/export';
import { GeneralPanel } from './panel/general';
@ -31,12 +28,12 @@ export type WorkspaceSettingDetailProps = {
onChangeTab: (tab: SettingPanel) => void;
onDeleteWorkspace: () => void;
onTransferWorkspace: <
From extends RemWorkspaceFlavour,
To extends RemWorkspaceFlavour
From extends WorkspaceFlavour,
To extends WorkspaceFlavour
>(
from: From,
to: To,
workspace: FlavourToWorkspace[From]
workspace: WorkspaceRegistry[From]
) => void;
};
@ -49,8 +46,7 @@ const panelMap = {
},
[settingPanel.Sync]: {
name: 'Sync',
enable: (flavour: RemWorkspaceFlavour) =>
flavour === RemWorkspaceFlavour.AFFINE,
enable: (flavour: WorkspaceFlavour) => flavour === WorkspaceFlavour.AFFINE,
ui: SyncPanel,
},
[settingPanel.Collaboration]: {
@ -68,7 +64,7 @@ const panelMap = {
} satisfies {
[Key in SettingPanel]: {
name: string;
enable?: (flavour: RemWorkspaceFlavour) => boolean;
enable?: (flavour: WorkspaceFlavour) => boolean;
ui: React.FC<PanelProps>;
};
};

View File

@ -8,6 +8,7 @@ import {
} from '@affine/component';
import { PermissionType } from '@affine/datacenter';
import { useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import {
DeleteTemporarilyIcon,
EmailIcon,
@ -18,7 +19,6 @@ import { useCallback, useState } from 'react';
import { useMembers } from '../../../../../hooks/affine/use-members';
import type { AffineWorkspace, LocalWorkspace } from '../../../../../shared';
import { RemWorkspaceFlavour } from '../../../../../shared';
import { Unreachable } from '../../../affine-error-eoundary';
import { TransformWorkspaceToAffineModal } from '../../../transform-workspace-to-affine-modal';
import type { PanelProps } from '../../index';
@ -194,8 +194,8 @@ const LocalCollaborationPanel: React.FC<
}}
onConform={() => {
onTransferWorkspace(
RemWorkspaceFlavour.LOCAL,
RemWorkspaceFlavour.AFFINE,
WorkspaceFlavour.LOCAL,
WorkspaceFlavour.AFFINE,
workspace
);
setOpen(false);
@ -207,13 +207,13 @@ const LocalCollaborationPanel: React.FC<
export const CollaborationPanel: React.FC<PanelProps> = props => {
switch (props.workspace.flavour) {
case RemWorkspaceFlavour.AFFINE: {
case WorkspaceFlavour.AFFINE: {
const workspace = props.workspace as AffineWorkspace;
return (
<AffineRemoteCollaborationPanel {...props} workspace={workspace} />
);
}
case RemWorkspaceFlavour.LOCAL: {
case WorkspaceFlavour.LOCAL: {
const workspace = props.workspace as LocalWorkspace;
return <LocalCollaborationPanel {...props} workspace={workspace} />;
}

View File

@ -1,10 +1,10 @@
import { Button, Input, Modal, ModalCloseButton } from '@affine/component';
import { Trans, useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { useCallback, useState } from 'react';
import { useBlockSuiteWorkspaceName } from '../../../../../../hooks/use-blocksuite-workspace-name';
import type { AffineOfficialWorkspace } from '../../../../../../shared';
import { RemWorkspaceFlavour } from '../../../../../../shared';
import {
StyledButtonContent,
StyledInputContent,
@ -43,7 +43,7 @@ export const WorkspaceDeleteModal = ({
<StyledModalWrapper>
<ModalCloseButton onClick={onClose} />
<StyledModalHeader>{t('Delete Workspace')}?</StyledModalHeader>
{workspace.flavour === RemWorkspaceFlavour.LOCAL ? (
{workspace.flavour === WorkspaceFlavour.LOCAL ? (
<StyledTextContent>
<Trans i18nKey="Delete Workspace Description">
Deleting (

View File

@ -1,12 +1,12 @@
import { Button, FlexWrapper, MuiFade } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import type React from 'react';
import { useState } from 'react';
import { useIsWorkspaceOwner } from '../../../../../hooks/affine/use-is-workspace-owner';
import { useBlockSuiteWorkspaceAvatarUrl } from '../../../../../hooks/use-blocksuite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '../../../../../hooks/use-blocksuite-workspace-name';
import { RemWorkspaceFlavour } from '../../../../../shared';
import { Upload } from '../../../../pure/file-upload';
import {
CloudWorkspaceIcon,
@ -161,7 +161,7 @@ export const GeneralPanel: React.FC<PanelProps> = ({
<StyledRow>
<StyledSettingKey>{t('Workspace Type')}</StyledSettingKey>
{isOwner ? (
workspace.flavour === RemWorkspaceFlavour.LOCAL ? (
workspace.flavour === WorkspaceFlavour.LOCAL ? (
<StyledWorkspaceInfo>
<LocalWorkspaceIcon />
<span>{t('Local Workspace')}</span>

View File

@ -7,6 +7,7 @@ import {
Wrapper,
} from '@affine/component';
import { useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { Box } from '@mui/material';
import type React from 'react';
import { useCallback, useEffect, useState } from 'react';
@ -17,7 +18,6 @@ import type {
AffineWorkspace,
LocalWorkspace,
} from '../../../../../shared';
import { RemWorkspaceFlavour } from '../../../../../shared';
import { Unreachable } from '../../../affine-error-eoundary';
import { EnableAffineCloudModal } from '../../../enable-affine-cloud-modal';
import type { WorkspaceSettingDetailProps } from '../../index';
@ -133,8 +133,8 @@ const PublishPanelLocal: React.FC<PublishPanelLocalProps> = ({
}}
onConfirm={() => {
onTransferWorkspace(
RemWorkspaceFlavour.LOCAL,
RemWorkspaceFlavour.AFFINE,
WorkspaceFlavour.LOCAL,
WorkspaceFlavour.AFFINE,
workspace
);
setOpen(false);
@ -145,9 +145,9 @@ const PublishPanelLocal: React.FC<PublishPanelLocalProps> = ({
};
export const PublishPanel: React.FC<PublishPanelProps> = props => {
if (props.workspace.flavour === RemWorkspaceFlavour.AFFINE) {
if (props.workspace.flavour === WorkspaceFlavour.AFFINE) {
return <PublishPanelAffine {...props} workspace={props.workspace} />;
} else if (props.workspace.flavour === RemWorkspaceFlavour.LOCAL) {
} else if (props.workspace.flavour === WorkspaceFlavour.LOCAL) {
return <PublishPanelLocal {...props} workspace={props.workspace} />;
}
throw new Unreachable();

View File

@ -1,11 +1,11 @@
import { Content, FlexWrapper, styled } from '@affine/component';
import { Trans, useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import type React from 'react';
import { useCurrentUser } from '../../../../../hooks/current/use-current-user';
import { useBlockSuiteWorkspaceAvatarUrl } from '../../../../../hooks/use-blocksuite-workspace-avatar-url';
import { useBlockSuiteWorkspaceName } from '../../../../../hooks/use-blocksuite-workspace-name';
import { RemWorkspaceFlavour } from '../../../../../shared';
import { WorkspaceAvatar } from '../../../../pure/footer';
import type { PanelProps } from '../../index';
@ -17,7 +17,7 @@ export const StyledWorkspaceName = styled('span')(({ theme }) => {
});
export const SyncPanel: React.FC<PanelProps> = ({ workspace }) => {
if (workspace.flavour !== RemWorkspaceFlavour.AFFINE) {
if (workspace.flavour !== WorkspaceFlavour.AFFINE) {
throw new TypeError('SyncPanel can only be used with Affine workspace');
}
const [name] = useBlockSuiteWorkspaceName(workspace.blockSuiteWorkspace);

View File

@ -1,5 +1,6 @@
import { displayFlex, IconButton, styled, Tooltip } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import {
CloudWorkspaceIcon,
LocalWorkspaceIcon,
@ -15,7 +16,6 @@ import type {
AffineOfficialWorkspace,
LocalWorkspace,
} from '../../../../shared';
import { RemWorkspaceFlavour } from '../../../../shared';
import { apis } from '../../../../shared/apis';
import { TransformWorkspaceToAffineModal } from '../../../affine/transform-workspace-to-affine-modal';
@ -117,10 +117,10 @@ export const SyncUser = () => {
router.reload();
return;
}
assertEquals(workspace.flavour, RemWorkspaceFlavour.LOCAL);
assertEquals(workspace.flavour, WorkspaceFlavour.LOCAL);
const id = await transformWorkspace(
RemWorkspaceFlavour.LOCAL,
RemWorkspaceFlavour.AFFINE,
WorkspaceFlavour.LOCAL,
WorkspaceFlavour.AFFINE,
workspace as LocalWorkspace
);
// fixme(himself65): refactor this

View File

@ -1,12 +1,12 @@
import { PermissionType } from '@affine/datacenter';
import { useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { SettingsIcon } from '@blocksuite/icons';
import type React from 'react';
import { useCallback } from 'react';
import { useBlockSuiteWorkspaceName } from '../../../hooks/use-blocksuite-workspace-name';
import type { RemWorkspace } from '../../../shared';
import { RemWorkspaceFlavour } from '../../../shared';
import {
CloudWorkspaceIcon,
JoinedWorkspaceIcon,
@ -28,13 +28,13 @@ export type WorkspaceTypeProps = {
const WorkspaceType: React.FC<WorkspaceTypeProps> = ({ workspace }) => {
const { t } = useTranslation();
let isOwner = true;
if (workspace.flavour === RemWorkspaceFlavour.AFFINE) {
if (workspace.flavour === WorkspaceFlavour.AFFINE) {
isOwner = workspace.permission === PermissionType.Owner;
} else if (workspace.flavour === RemWorkspaceFlavour.LOCAL) {
} else if (workspace.flavour === WorkspaceFlavour.LOCAL) {
isOwner = true;
}
if (workspace.flavour === RemWorkspaceFlavour.LOCAL) {
if (workspace.flavour === WorkspaceFlavour.LOCAL) {
return (
<p title={t('Local Workspace')}>
<LocalWorkspaceIcon />
@ -85,19 +85,18 @@ export const WorkspaceCard: React.FC<WorkspaceCardProps> = ({
<StyleWorkspaceInfo>
<StyleWorkspaceTitle>{name}</StyleWorkspaceTitle>
<WorkspaceType workspace={workspace} />
{workspace.flavour === RemWorkspaceFlavour.LOCAL && (
{workspace.flavour === WorkspaceFlavour.LOCAL && (
<p title={t('Available Offline')}>
<LocalDataIcon />
<span>{t('Available Offline')}</span>
</p>
)}
{workspace.flavour === RemWorkspaceFlavour.AFFINE &&
workspace.public && (
<p title={t('Published to Web')}>
<PublishIcon />
<span>{t('Published to Web')}</span>
</p>
)}
{workspace.flavour === WorkspaceFlavour.AFFINE && workspace.public && (
<p title={t('Published to Web')}>
<PublishIcon />
<span>{t('Published to Web')}</span>
</p>
)}
</StyleWorkspaceInfo>
<StyledSettingLink
className="setting-entry"

View File

@ -5,6 +5,7 @@ import 'fake-indexeddb/auto';
import assert from 'node:assert';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import type { Page } from '@blocksuite/store';
import { assertExists } from '@blocksuite/store';
@ -23,7 +24,7 @@ import {
} from '../../atoms';
import { LocalPlugin } from '../../plugins/local';
import type { LocalWorkspace } from '../../shared';
import { BlockSuiteWorkspace, RemWorkspaceFlavour } from '../../shared';
import { BlockSuiteWorkspace } from '../../shared';
import { useIsFirstLoad, useOpenTips } from '../affine/use-is-first-load';
import {
useRecentlyViewed,
@ -181,7 +182,7 @@ describe('useWorkspaces', () => {
expect(result2.current.length).toEqual(1);
const firstWorkspace = result2.current[0];
expect(firstWorkspace.flavour).toBe('local');
assert(firstWorkspace.flavour === RemWorkspaceFlavour.LOCAL);
assert(firstWorkspace.flavour === WorkspaceFlavour.LOCAL);
expect(firstWorkspace.blockSuiteWorkspace.meta.name).toBe('test');
});
});
@ -266,12 +267,12 @@ describe('useRecentlyViewed', () => {
store.set(jotaiWorkspacesAtom, [
{
id: workspaceId,
flavour: RemWorkspaceFlavour.LOCAL,
flavour: WorkspaceFlavour.LOCAL,
},
]);
LocalPlugin.CRUD.get = vi.fn().mockResolvedValue({
id: workspaceId,
flavour: RemWorkspaceFlavour.LOCAL,
flavour: WorkspaceFlavour.LOCAL,
blockSuiteWorkspace,
providers: [],
} satisfies LocalWorkspace);

View File

@ -1,12 +1,12 @@
import { DEFAULT_WORKSPACE_NAME } from '@affine/env';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { assertEquals, assertExists, nanoid } from '@blocksuite/store';
import { useAtom } from 'jotai/index';
import { useAtom } from 'jotai';
import { useEffect } from 'react';
import { jotaiWorkspacesAtom } from '../atoms';
import { LocalPlugin } from '../plugins/local';
import { RemWorkspaceFlavour } from '../shared';
import { createEmptyBlockSuiteWorkspace } from '../utils';
export function useCreateFirstWorkspace() {
const [jotaiWorkspaces, set] = useAtom(jotaiWorkspacesAtom);
@ -29,7 +29,7 @@ export function useCreateFirstWorkspace() {
set([
{
id: workspace.id,
flavour: RemWorkspaceFlavour.LOCAL,
flavour: WorkspaceFlavour.LOCAL,
},
]);
}

View File

@ -1,9 +1,9 @@
import { WorkspaceFlavour } from '@affine/workspace/type';
import type { NextRouter } from 'next/router';
import { useEffect } from 'react';
import { currentPageIdAtom, jotaiStore } from '../atoms';
import type { RemWorkspace } from '../shared';
import { RemWorkspaceFlavour } from '../shared';
import { useCurrentPageId } from './current/use-current-page-id';
import { useCurrentWorkspace } from './current/use-current-workspace';
import { useWorkspaces } from './use-workspaces';
@ -13,7 +13,7 @@ export function findSuitablePageId(
targetId: string
): string | null {
switch (workspace.flavour) {
case RemWorkspaceFlavour.AFFINE: {
case WorkspaceFlavour.AFFINE: {
return (
workspace.blockSuiteWorkspace.meta.pageMetas.find(
page => page.id === targetId
@ -22,7 +22,7 @@ export function findSuitablePageId(
null
);
}
case RemWorkspaceFlavour.LOCAL: {
case WorkspaceFlavour.LOCAL: {
return (
workspace.blockSuiteWorkspace.meta.pageMetas.find(
page => page.id === targetId

View File

@ -1,9 +1,10 @@
import type { WorkspaceFlavour } from '@affine/workspace/type';
import type { WorkspaceRegistry } from '@affine/workspace/type';
import { useSetAtom } from 'jotai';
import { useCallback } from 'react';
import { jotaiWorkspacesAtom } from '../atoms';
import { WorkspacePlugins } from '../plugins';
import type { FlavourToWorkspace, RemWorkspaceFlavour } from '../shared';
/**
* Transform workspace from one flavour to another
@ -13,10 +14,10 @@ import type { FlavourToWorkspace, RemWorkspaceFlavour } from '../shared';
export function useTransformWorkspace() {
const set = useSetAtom(jotaiWorkspacesAtom);
return useCallback(
async <From extends RemWorkspaceFlavour, To extends RemWorkspaceFlavour>(
async <From extends WorkspaceFlavour, To extends WorkspaceFlavour>(
from: From,
to: To,
workspace: FlavourToWorkspace[From]
workspace: WorkspaceRegistry[From]
): Promise<string> => {
await WorkspacePlugins[from].CRUD.delete(workspace as any);
const newId = await WorkspacePlugins[to].CRUD.create(

View File

@ -1,3 +1,5 @@
import { WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { nanoid } from '@blocksuite/store';
import { useAtomValue, useSetAtom } from 'jotai';
import { useCallback, useEffect } from 'react';
@ -6,8 +8,6 @@ import { jotaiWorkspacesAtom, workspacesAtom } from '../atoms';
import { WorkspacePlugins } from '../plugins';
import { LocalPlugin } from '../plugins/local';
import type { LocalWorkspace, RemWorkspace } from '../shared';
import { RemWorkspaceFlavour } from '../shared';
import { createEmptyBlockSuiteWorkspace } from '../utils';
export function useWorkspaces(): RemWorkspace[] {
return useAtomValue(workspacesAtom);
@ -43,7 +43,7 @@ export function useWorkspacesHelper() {
...workspaces,
{
id,
flavour: RemWorkspaceFlavour.LOCAL,
flavour: WorkspaceFlavour.LOCAL,
},
]);
return id;

View File

@ -1,5 +1,6 @@
import { Button, toast } from '@affine/component';
import { DebugLogger } from '@affine/debug';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { nanoid } from '@blocksuite/store';
import { Typography } from '@mui/material';
import type React from 'react';
@ -9,7 +10,6 @@ import { createBroadCastChannelProvider } from '../../blocksuite/providers';
import PageList from '../../components/blocksuite/block-suite-page-list/page-list';
import { StyledPage, StyledWrapper } from '../../layouts/styles';
import type { BroadCastChannelProvider } from '../../shared';
import { createEmptyBlockSuiteWorkspace } from '../../utils';
const logger = new DebugLogger('broadcast');

View File

@ -0,0 +1,78 @@
import { Button, toast } from '@affine/component';
import { currentAffineUserAtom } from '@affine/workspace/affine/atom';
import {
clearLoginStorage,
createAffineAuth,
getLoginStorage,
isExpired,
parseIdToken,
setLoginStorage,
SignMethod,
} from '@affine/workspace/affine/login';
import { useAtom } from 'jotai';
import type { NextPage } from 'next';
import { useMemo } from 'react';
import { StyledPage, StyledWrapper } from '../../layouts/styles';
const LoginDevPage: NextPage = () => {
const [user, setUser] = useAtom(currentAffineUserAtom);
const auth = useMemo(() => createAffineAuth(), []);
return (
<StyledPage>
<StyledWrapper>
<h1>LoginDevPage</h1>
<Button
onClick={async () => {
const storage = getLoginStorage();
if (storage) {
const user = parseIdToken(storage.token);
if (isExpired(user)) {
await auth.refreshToken(storage);
}
}
const response = await auth.generateToken(SignMethod.Google);
if (response) {
setLoginStorage(response);
const user = parseIdToken(response.token);
setUser(user);
} else {
toast('Login failed');
}
}}
>
Login
</Button>
<Button
onClick={async () => {
const storage = getLoginStorage();
if (!storage) {
throw new Error('No storage');
}
const response = await auth.refreshToken(storage);
if (response) {
setLoginStorage(response);
const user = parseIdToken(response.token);
setUser(user);
} else {
toast('Login failed');
}
}}
>
Refresh Token
</Button>
<Button
onClick={() => {
clearLoginStorage();
setUser(null);
}}
>
Reset Storage
</Button>
{user && JSON.stringify(user)}
</StyledWrapper>
</StyledPage>
);
};
export default LoginDevPage;

View File

@ -1,3 +1,4 @@
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { ContentParser } from '@blocksuite/blocks/content-parser';
import type {
GetStaticPaths,
@ -16,7 +17,6 @@ import {
StyledWrapper,
} from '../../layouts/styles';
import type { BlockSuiteWorkspace } from '../../shared';
import { createEmptyBlockSuiteWorkspace } from '../../utils';
export type PreviewPageProps = {
text: string;

View File

@ -1,3 +1,4 @@
import { WorkspaceFlavour } from '@affine/workspace/type';
import { useRouter } from 'next/router';
import type React from 'react';
import { useEffect } from 'react';
@ -11,7 +12,6 @@ import { useSyncRouterWithCurrentWorkspaceAndPage } from '../../../hooks/use-syn
import { WorkspaceLayout } from '../../../layouts';
import { WorkspacePlugins } from '../../../plugins';
import type { BlockSuiteWorkspace, NextPageWithLayout } from '../../../shared';
import { RemWorkspaceFlavour } from '../../../shared';
function enableFullFlags(blockSuiteWorkspace: BlockSuiteWorkspace) {
blockSuiteWorkspace.awarenessStore.setFlag('enable_set_remote_flag', false);
@ -38,12 +38,12 @@ const WorkspaceDetail: React.FC = () => {
if (!pageId) {
return <PageLoading />;
}
if (currentWorkspace.flavour === RemWorkspaceFlavour.AFFINE) {
if (currentWorkspace.flavour === WorkspaceFlavour.AFFINE) {
const PageDetail = WorkspacePlugins[currentWorkspace.flavour].UI.PageDetail;
return (
<PageDetail currentWorkspace={currentWorkspace} currentPageId={pageId} />
);
} else if (currentWorkspace.flavour === RemWorkspaceFlavour.LOCAL) {
} else if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
const PageDetail = WorkspacePlugins[currentWorkspace.flavour].UI.PageDetail;
return (
<PageDetail currentWorkspace={currentWorkspace} currentPageId={pageId} />

View File

@ -1,4 +1,5 @@
import { useTranslation } from '@affine/i18n';
import { WorkspaceFlavour } from '@affine/workspace/type';
import { FolderIcon } from '@blocksuite/icons';
import { assertEquals, assertExists, nanoid } from '@blocksuite/store';
import Head from 'next/head';
@ -20,7 +21,6 @@ import type {
LocalIndexedDBProvider,
NextPageWithLayout,
} from '../../../shared';
import { RemWorkspaceFlavour } from '../../../shared';
const AllPage: NextPageWithLayout = () => {
const router = useRouter();
@ -78,7 +78,7 @@ const AllPage: NextPageWithLayout = () => {
if (currentWorkspace === null) {
return <PageLoading />;
}
if (currentWorkspace.flavour === RemWorkspaceFlavour.AFFINE) {
if (currentWorkspace.flavour === WorkspaceFlavour.AFFINE) {
const PageList = WorkspacePlugins[currentWorkspace.flavour].UI.PageList;
return (
<>
@ -92,7 +92,7 @@ const AllPage: NextPageWithLayout = () => {
/>
</>
);
} else if (currentWorkspace.flavour === RemWorkspaceFlavour.LOCAL) {
} else if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
const PageList = WorkspacePlugins[currentWorkspace.flavour].UI.PageList;
return (
<>

View File

@ -1,4 +1,10 @@
import { useTranslation } from '@affine/i18n';
import type { SettingPanel, WorkspaceRegistry } from '@affine/workspace/type';
import {
settingPanel,
settingPanelValues,
WorkspaceFlavour,
} from '@affine/workspace/type';
import { SettingsIcon } from '@blocksuite/icons';
import { assertExists } from '@blocksuite/store';
import { useAtom } from 'jotai';
@ -16,16 +22,7 @@ import { useTransformWorkspace } from '../../../hooks/use-transform-workspace';
import { useWorkspacesHelper } from '../../../hooks/use-workspaces';
import { WorkspaceLayout } from '../../../layouts';
import { WorkspacePlugins } from '../../../plugins';
import type {
FlavourToWorkspace,
NextPageWithLayout,
SettingPanel,
} from '../../../shared';
import {
RemWorkspaceFlavour,
settingPanel,
settingPanelValues,
} from '../../../shared';
import type { NextPageWithLayout } from '../../../shared';
import { apis } from '../../../shared/apis';
const settingPanelAtom = atomWithStorage<SettingPanel>(
@ -105,13 +102,12 @@ const SettingPage: NextPageWithLayout = () => {
}, [currentWorkspace, helper]);
const transformWorkspace = useTransformWorkspace();
const onTransformWorkspace = useCallback(
async <From extends RemWorkspaceFlavour, To extends RemWorkspaceFlavour>(
async <From extends WorkspaceFlavour, To extends WorkspaceFlavour>(
from: From,
to: To,
workspace: FlavourToWorkspace[From]
workspace: WorkspaceRegistry[From]
): Promise<void> => {
const needRefresh =
to === RemWorkspaceFlavour.AFFINE && !apis.auth.isLogin;
const needRefresh = to === WorkspaceFlavour.AFFINE && !apis.auth.isLogin;
if (needRefresh) {
await apis.signInWithGoogle();
}
@ -135,7 +131,7 @@ const SettingPage: NextPageWithLayout = () => {
return <PageLoading />;
} else if (settingPanelValues.indexOf(currentTab as SettingPanel) === -1) {
return <PageLoading />;
} else if (currentWorkspace.flavour === RemWorkspaceFlavour.AFFINE) {
} else if (currentWorkspace.flavour === WorkspaceFlavour.AFFINE) {
const Setting =
WorkspacePlugins[currentWorkspace.flavour].UI.SettingsDetail;
return (
@ -155,7 +151,7 @@ const SettingPage: NextPageWithLayout = () => {
/>
</>
);
} else if (currentWorkspace.flavour === RemWorkspaceFlavour.LOCAL) {
} else if (currentWorkspace.flavour === WorkspaceFlavour.LOCAL) {
const Setting =
WorkspacePlugins[currentWorkspace.flavour].UI.SettingsDetail;
return (

View File

@ -1,12 +1,12 @@
import { WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { assertExists } from '@blocksuite/store';
import { jotaiStore, workspacesAtom } from '../../atoms';
import { createAffineProviders } from '../../blocksuite';
import { Unreachable } from '../../components/affine/affine-error-eoundary';
import type { AffineWorkspace } from '../../shared';
import { RemWorkspaceFlavour } from '../../shared';
import { apis } from '../../shared/apis';
import { createEmptyBlockSuiteWorkspace } from '../../utils';
type Query = (typeof QueryKey)[keyof typeof QueryKey];
@ -73,7 +73,7 @@ export const fetcher = async (
);
const remWorkspace: AffineWorkspace = {
...workspace,
flavour: RemWorkspaceFlavour.AFFINE,
flavour: WorkspaceFlavour.AFFINE,
blockSuiteWorkspace,
providers: [...createAffineProviders(blockSuiteWorkspace)],
};

View File

@ -1,3 +1,5 @@
import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { createJSONStorage } from 'jotai/utils';
import React from 'react';
import { mutate } from 'swr';
@ -9,13 +11,8 @@ import { WorkspaceSettingDetail } from '../../components/affine/workspace-settin
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
import { PageDetailEditor } from '../../components/page-detail-editor';
import type { AffineWorkspace } from '../../shared';
import {
BlockSuiteWorkspace,
LoadPriority,
RemWorkspaceFlavour,
} from '../../shared';
import { BlockSuiteWorkspace } from '../../shared';
import { apis, clientAuth } from '../../shared/apis';
import { createEmptyBlockSuiteWorkspace } from '../../utils';
import { initPage } from '../../utils/blocksuite';
import type { WorkspacePlugin } from '..';
import { QueryKey } from './fetcher';
@ -46,7 +43,7 @@ const getPersistenceAllWorkspace = () => {
);
const affineWorkspace: AffineWorkspace = {
...item,
flavour: RemWorkspaceFlavour.AFFINE,
flavour: WorkspaceFlavour.AFFINE,
blockSuiteWorkspace,
providers: [...createAffineProviders(blockSuiteWorkspace)],
};
@ -57,8 +54,8 @@ const getPersistenceAllWorkspace = () => {
return allWorkspaces;
};
export const AffinePlugin: WorkspacePlugin<RemWorkspaceFlavour.AFFINE> = {
flavour: RemWorkspaceFlavour.AFFINE,
export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
flavour: WorkspaceFlavour.AFFINE,
loadPriority: LoadPriority.HIGH,
cleanup: () => {
storage.removeItem(kAffineLocal);
@ -167,7 +164,7 @@ export const AffinePlugin: WorkspacePlugin<RemWorkspaceFlavour.AFFINE> = {
const affineWorkspace: AffineWorkspace = {
...workspace,
flavour: RemWorkspaceFlavour.AFFINE,
flavour: WorkspaceFlavour.AFFINE,
blockSuiteWorkspace,
providers: [...createAffineProviders(blockSuiteWorkspace)],
};

View File

@ -1,74 +1,35 @@
import type React from 'react';
import type {
BlockSuiteWorkspace,
FlavourToWorkspace,
LoadPriority,
SettingPanel,
} from '../shared';
import { RemWorkspaceFlavour } from '../shared';
WorkspaceCRUD,
WorkspaceUISchema,
} from '@affine/workspace/type';
import { WorkspaceFlavour } from '@affine/workspace/type';
import type { AffineWorkspace, LocalWorkspace } from '../shared';
import { AffinePlugin } from './affine';
import { LocalPlugin } from './local';
type UIBaseProps<Flavour extends RemWorkspaceFlavour> = {
currentWorkspace: FlavourToWorkspace[Flavour];
};
declare module '@affine/workspace/type' {
interface WorkspaceRegistry {
[WorkspaceFlavour.AFFINE]: AffineWorkspace;
[WorkspaceFlavour.LOCAL]: LocalWorkspace;
}
}
type SettingProps<Flavour extends RemWorkspaceFlavour> =
UIBaseProps<Flavour> & {
currentTab: SettingPanel;
onChangeTab: (tab: SettingPanel) => void;
onDeleteWorkspace: () => void;
onTransformWorkspace: <
From extends RemWorkspaceFlavour,
To extends RemWorkspaceFlavour
>(
from: From,
to: To,
workspace: FlavourToWorkspace[From]
) => void;
};
type PageDetailProps<Flavour extends RemWorkspaceFlavour> =
UIBaseProps<Flavour> & {
currentPageId: string;
};
type PageListProps<Flavour extends RemWorkspaceFlavour> = {
blockSuiteWorkspace: BlockSuiteWorkspace;
onOpenPage: (pageId: string, newTab?: boolean) => void;
};
type SideBarMenuProps<Flavour extends RemWorkspaceFlavour> =
UIBaseProps<Flavour> & {
setSideBarOpen: (open: boolean) => void;
};
export interface WorkspacePlugin<Flavour extends RemWorkspaceFlavour> {
export interface WorkspacePlugin<Flavour extends WorkspaceFlavour> {
flavour: Flavour;
// Plugin will be loaded according to the priority
loadPriority: LoadPriority;
// fixme: this is a hack
cleanup?: () => void;
// Fetch necessary data for the first render
CRUD: {
create: (blockSuiteWorkspace: BlockSuiteWorkspace) => Promise<string>;
delete: (workspace: FlavourToWorkspace[Flavour]) => Promise<void>;
get: (workspaceId: string) => Promise<FlavourToWorkspace[Flavour] | null>;
// not supported yet
// update: (workspace: FlavourToWorkspace[Flavour]) => Promise<void>;
list: () => Promise<FlavourToWorkspace[Flavour][]>;
};
UI: {
PageDetail: React.FC<PageDetailProps<Flavour>>;
PageList: React.FC<PageListProps<Flavour>>;
SettingsDetail: React.FC<SettingProps<Flavour>>;
};
CRUD: WorkspaceCRUD<Flavour>;
UI: WorkspaceUISchema<Flavour>;
}
export const WorkspacePlugins = {
[RemWorkspaceFlavour.AFFINE]: AffinePlugin,
[RemWorkspaceFlavour.LOCAL]: LocalPlugin,
[WorkspaceFlavour.AFFINE]: AffinePlugin,
[WorkspaceFlavour.LOCAL]: LocalPlugin,
} satisfies {
[Key in RemWorkspaceFlavour]: WorkspacePlugin<Key>;
[Key in WorkspaceFlavour]: WorkspacePlugin<Key>;
};

View File

@ -1,4 +1,6 @@
import { DEFAULT_WORKSPACE_NAME } from '@affine/env';
import { LoadPriority, WorkspaceFlavour } from '@affine/workspace/type';
import { createEmptyBlockSuiteWorkspace } from '@affine/workspace/utils';
import { nanoid } from '@blocksuite/store';
import { createJSONStorage } from 'jotai/utils';
import React from 'react';
@ -11,12 +13,7 @@ import { WorkspaceSettingDetail } from '../../components/affine/workspace-settin
import { BlockSuitePageList } from '../../components/blocksuite/block-suite-page-list';
import { PageDetailEditor } from '../../components/page-detail-editor';
import type { LocalWorkspace } from '../../shared';
import {
BlockSuiteWorkspace,
LoadPriority,
RemWorkspaceFlavour,
} from '../../shared';
import { createEmptyBlockSuiteWorkspace } from '../../utils';
import { BlockSuiteWorkspace } from '../../shared';
import { initPage } from '../../utils/blocksuite';
import type { WorkspacePlugin } from '..';
@ -25,8 +22,8 @@ const getStorage = () => createJSONStorage(() => localStorage);
export const kStoreKey = 'affine-local-workspace';
const schema = z.array(z.string());
export const LocalPlugin: WorkspacePlugin<RemWorkspaceFlavour.LOCAL> = {
flavour: RemWorkspaceFlavour.LOCAL,
export const LocalPlugin: WorkspacePlugin<WorkspaceFlavour.LOCAL> = {
flavour: WorkspaceFlavour.LOCAL,
loadPriority: LoadPriority.LOW,
CRUD: {
get: async workspaceId => {
@ -44,7 +41,7 @@ export const LocalPlugin: WorkspacePlugin<RemWorkspaceFlavour.LOCAL> = {
);
const workspace: LocalWorkspace = {
id,
flavour: RemWorkspaceFlavour.LOCAL,
flavour: WorkspaceFlavour.LOCAL,
blockSuiteWorkspace: blockSuiteWorkspace,
providers: [...createLocalProviders(blockSuiteWorkspace)],
};

View File

@ -12,7 +12,7 @@ let prefixUrl = '/';
if (typeof window === 'undefined') {
// SSR
const serverAPI = config.serverAPI;
if (isValidIPAddress(serverAPI)) {
if (isValidIPAddress(serverAPI.split(':')[0])) {
// This is for Server side rendering support
prefixUrl = new URL('http://' + config.serverAPI + '/').origin;
} else {

View File

@ -1,4 +1,5 @@
import type { Workspace as RemoteWorkspace } from '@affine/datacenter';
import type { WorkspaceFlavour } from '@affine/workspace/type';
import { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import type { NextPage } from 'next';
import type { ReactElement, ReactNode } from 'react';
@ -11,25 +12,15 @@ declare global {
}
}
export const enum RemWorkspaceFlavour {
AFFINE = 'affine',
LOCAL = 'local',
}
export interface FlavourToWorkspace {
[RemWorkspaceFlavour.AFFINE]: AffineWorkspace;
[RemWorkspaceFlavour.LOCAL]: LocalWorkspace;
}
export interface AffineWorkspace extends RemoteWorkspace {
flavour: RemWorkspaceFlavour.AFFINE;
flavour: WorkspaceFlavour.AFFINE;
// empty
blockSuiteWorkspace: BlockSuiteWorkspace;
providers: Provider[];
}
export interface LocalWorkspace {
flavour: RemWorkspaceFlavour.LOCAL;
flavour: WorkspaceFlavour.LOCAL;
id: string;
blockSuiteWorkspace: BlockSuiteWorkspace;
providers: Provider[];
@ -89,16 +80,6 @@ export const enum WorkspaceSubPath {
TRASH = 'trash',
}
export const settingPanel = {
General: 'general',
Collaboration: 'collaboration',
Publish: 'publish',
Export: 'export',
Sync: 'sync',
} as const;
export const settingPanelValues = [...Object.values(settingPanel)] as const;
export type SettingPanel = (typeof settingPanel)[keyof typeof settingPanel];
export const WorkspaceSubPathName = {
[WorkspaceSubPath.ALL]: 'All Pages',
[WorkspaceSubPath.FAVORITE]: 'Favorites',
@ -125,9 +106,3 @@ export const publicPathGenerator = {
} satisfies {
[Path in WorkspaceSubPath]: (workspaceId: string) => string;
};
export const enum LoadPriority {
HIGH = 1,
MEDIUM = 2,
LOW = 3,
}

View File

@ -1,8 +1,3 @@
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import type { BlobOptionsGetter, Generator } from '@blocksuite/store';
import { BlockSuiteWorkspace } from '../shared';
export function stringToColour(str: string) {
str = str || 'affine';
let colour = '#';
@ -23,24 +18,3 @@ export function stringToColour(str: string) {
return colour;
}
const hashMap = new Map<string, BlockSuiteWorkspace>();
export const createEmptyBlockSuiteWorkspace = (
id: string,
blobOptionsGetter?: BlobOptionsGetter,
idGenerator?: Generator
): BlockSuiteWorkspace => {
if (hashMap.has(id)) {
return hashMap.get(id) as BlockSuiteWorkspace;
}
const workspace = new BlockSuiteWorkspace({
id,
isSSR: typeof window === 'undefined',
blobOptionsGetter,
idGenerator,
})
.register(AffineSchemas)
.register(__unstableSchemas);
hashMap.set(id, workspace);
return workspace;
};

View File

@ -57,6 +57,7 @@
"happy-dom": "^8.9.0",
"husky": "^8.0.3",
"lint-staged": "^13.2.0",
"msw": "^1.2.0",
"nanoid": "^4.0.1",
"nyc": "^15.1.0",
"prettier": "^2.8.5",

View File

@ -2,12 +2,17 @@
"name": "@affine/env",
"private": true,
"main": "./src/index.ts",
"module": "./src/index.ts",
"devDependencies": {
"next": "=13.2.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zod": "^3.21.4"
},
"exports": {
".": "./src/index.ts",
"./constant": "./src/constant.ts"
},
"dependencies": {
"@blocksuite/global": "0.5.0-20230323085636-3110abb",
"lit": "^2.6.1"

View File

@ -1,2 +1,68 @@
export const DEFAULT_WORKSPACE_NAME = 'Demo Workspace';
export const UNTITLED_WORKSPACE_NAME = 'Untitled';
export const enum MessageCode {
loginError,
noPermission,
loadListFailed,
getDetailFailed,
createWorkspaceFailed,
getMembersFailed,
updateWorkspaceFailed,
deleteWorkspaceFailed,
inviteMemberFailed,
removeMemberFailed,
acceptInvitingFailed,
getBlobFailed,
leaveWorkspaceFailed,
downloadWorkspaceFailed,
refreshTokenError,
}
export const Messages = {
[MessageCode.loginError]: {
message: 'Login failed',
},
[MessageCode.noPermission]: {
message: 'No permission',
},
[MessageCode.loadListFailed]: {
message: 'Load list failed',
},
[MessageCode.getDetailFailed]: {
message: 'Get detail failed',
},
[MessageCode.createWorkspaceFailed]: {
message: 'Create workspace failed',
},
[MessageCode.getMembersFailed]: {
message: 'Get members failed',
},
[MessageCode.updateWorkspaceFailed]: {
message: 'Update workspace failed',
},
[MessageCode.deleteWorkspaceFailed]: {
message: 'Delete workspace failed',
},
[MessageCode.inviteMemberFailed]: {
message: 'Invite member failed',
},
[MessageCode.removeMemberFailed]: {
message: 'Remove member failed',
},
[MessageCode.acceptInvitingFailed]: {
message: 'Accept inviting failed',
},
[MessageCode.getBlobFailed]: {
message: 'Get blob failed',
},
[MessageCode.leaveWorkspaceFailed]: {
message: 'Leave workspace failed',
},
[MessageCode.downloadWorkspaceFailed]: {
message: 'Download workspace failed',
},
[MessageCode.refreshTokenError]: {
message: 'Refresh token failed',
},
} as const;

View File

@ -0,0 +1,22 @@
{
"name": "@affine/workspace",
"private": true,
"exports": {
"./utils": "./src/utils.ts",
"./type": "./src/type.ts",
"./affine/*": "./src/affine/*.ts"
},
"dependencies": {
"@affine/component": "workspace:*",
"@affine/debug": "workspace:*",
"@affine/env": "workspace:*",
"@blocksuite/blocks": "0.5.0-20230323085636-3110abb",
"@blocksuite/store": "0.5.0-20230323085636-3110abb",
"firebase": "^9.18.0",
"jotai": "^2.0.3",
"js-base64": "^3.7.5",
"ky": "^0.33.3",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}

View File

@ -0,0 +1,20 @@
import { getDefaultStore } from 'jotai';
import { describe, expect, test } from 'vitest';
import { currentAffineUserAtom } from '../atom';
describe('atom', () => {
test('currentAffineUserAtom', () => {
const store = getDefaultStore();
const mock = {
created_at: 0,
exp: 0,
email: '',
id: '',
name: '',
avatar_url: '',
};
store.set(currentAffineUserAtom, mock);
expect(store.get(currentAffineUserAtom)).toEqual(mock);
});
});

View File

@ -0,0 +1,42 @@
/**
* @vitest-environment happy-dom
*/
import type { AccessTokenMessage } from '@affine/workspace/affine/login';
import {
getLoginStorage,
isExpired,
setLoginStorage,
STORAGE_KEY,
} from '@affine/workspace/affine/login';
import { describe, expect, test } from 'vitest';
describe('storage', () => {
test('should work', () => {
setLoginStorage({
token: '1',
refresh: '2',
});
const data = localStorage.getItem(STORAGE_KEY);
expect(data).toBe('{"token":"1","refresh":"2"}');
const login = getLoginStorage();
expect(login).toEqual({
token: '1',
refresh: '2',
});
});
});
describe('utils', () => {
test('isExpired', async () => {
const now = Math.floor(Date.now() / 1000);
expect(isExpired({ exp: now + 1 } as AccessTokenMessage)).toBeFalsy();
const promise = new Promise<void>(resolve => {
setTimeout(() => {
expect(isExpired({ exp: now + 1 } as AccessTokenMessage)).toBeTruthy();
resolve();
}, 2000);
});
expect(isExpired({ exp: now - 1 } as AccessTokenMessage)).toBeTruthy();
await promise;
});
});

View File

@ -0,0 +1,4 @@
import type { AccessTokenMessage } from '@affine/workspace/affine/login';
import { atom } from 'jotai';
export const currentAffineUserAtom = atom<AccessTokenMessage | null>(null);

View File

@ -0,0 +1,175 @@
import { DebugLogger } from '@affine/debug';
import { initializeApp } from 'firebase/app';
import type { AuthProvider } from 'firebase/auth';
import {
type Auth as FirebaseAuth,
connectAuthEmulator,
getAuth as getFirebaseAuth,
GithubAuthProvider,
GoogleAuthProvider,
signInWithPopup,
} from 'firebase/auth';
import { decode } from 'js-base64';
// Connect emulators based on env vars
const envConnectEmulators = process.env.REACT_APP_FIREBASE_EMULATORS === 'true';
export type AccessTokenMessage = {
created_at: number;
exp: number;
email: string;
id: string;
name: string;
avatar_url: string;
};
export type LoginParams = {
type: 'Google' | 'Refresh';
token: string;
};
export type LoginResponse = {
// access token, expires in a very short time
token: string;
// Refresh token
refresh: string;
};
const logger = new DebugLogger('token');
export const STORAGE_KEY = 'affine-login-v2';
export function parseIdToken(token: string): AccessTokenMessage {
return JSON.parse(decode(token.split('.')[1]));
}
export const isExpired = (token: AccessTokenMessage): boolean => {
const now = Math.floor(Date.now() / 1000);
return token.exp < now;
};
export const setLoginStorage = (login: LoginResponse) => {
localStorage.setItem(
STORAGE_KEY,
JSON.stringify({
token: login.token,
refresh: login.refresh,
})
);
};
export const clearLoginStorage = () => {
localStorage.removeItem(STORAGE_KEY);
};
export const getLoginStorage = (): LoginResponse | null => {
const login = localStorage.getItem(STORAGE_KEY);
if (login) {
try {
return JSON.parse(login);
} catch (error) {
logger.error('Failed to parse login', error);
}
}
return null;
};
export const enum SignMethod {
Google = 'Google',
GitHub = 'GitHub',
// Twitter = 'Twitter',
}
declare global {
// eslint-disable-next-line no-var
var firebaseAuthEmulatorStarted: boolean | undefined;
}
export function createAffineAuth() {
let _firebaseAuth: FirebaseAuth | null = null;
const getAuth = (): FirebaseAuth | null => {
try {
if (!_firebaseAuth) {
const app = initializeApp({
apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
messagingSenderId:
process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
});
_firebaseAuth = getFirebaseAuth(app);
}
if (envConnectEmulators && !globalThis.firebaseAuthEmulatorStarted) {
connectAuthEmulator(_firebaseAuth, 'http://localhost:9099', {
disableWarnings: true,
});
globalThis.firebaseAuthEmulatorStarted = true;
}
return _firebaseAuth;
} catch (error) {
logger.error('Failed to initialize firebase', error);
return null;
}
};
return {
generateToken: async (
method: SignMethod
): Promise<LoginResponse | null> => {
const auth = getAuth();
if (!auth) {
throw new Error('Failed to initialize firebase');
}
let provider: AuthProvider;
switch (method) {
case SignMethod.Google:
provider = new GoogleAuthProvider();
break;
case SignMethod.GitHub:
provider = new GithubAuthProvider();
break;
default:
throw new Error('Unsupported sign method');
}
try {
const response = await signInWithPopup(auth, provider);
const idToken = await response.user.getIdToken();
logger.debug(idToken);
return fetch('/api/user/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'Google',
token: idToken,
}),
}).then(r => r.json()) as Promise<LoginResponse>;
} catch (error) {
if (error instanceof Error && 'code' in error) {
if (error.code === 'auth/popup-closed-by-user') {
return null;
}
}
logger.error('Failed to sign in', error);
}
return null;
},
refreshToken: async (
loginResponse: LoginResponse
): Promise<LoginResponse | null> => {
return fetch('/api/user/token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
type: 'Refresh',
token: loginResponse.refresh,
}),
}).then(r => r.json()) as Promise<LoginResponse>;
},
} as const;
}

View File

@ -0,0 +1,74 @@
import type { Workspace as BlockSuiteWorkspace } from '@blocksuite/store';
import type { FC } from 'react';
export const enum LoadPriority {
HIGH = 1,
MEDIUM = 2,
LOW = 3,
}
export const enum WorkspaceFlavour {
AFFINE = 'affine',
LOCAL = 'local',
}
export const settingPanel = {
General: 'general',
Collaboration: 'collaboration',
Publish: 'publish',
Export: 'export',
Sync: 'sync',
} as const;
export const settingPanelValues = [...Object.values(settingPanel)] as const;
export type SettingPanel = (typeof settingPanel)[keyof typeof settingPanel];
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface WorkspaceRegistry {}
export interface WorkspaceCRUD<Flavour extends keyof WorkspaceRegistry> {
create: (blockSuiteWorkspace: BlockSuiteWorkspace) => Promise<string>;
delete: (workspace: WorkspaceRegistry[Flavour]) => Promise<void>;
get: (workspaceId: string) => Promise<WorkspaceRegistry[Flavour] | null>;
// not supported yet
// update: (workspace: FlavourToWorkspace[Flavour]) => Promise<void>;
list: () => Promise<WorkspaceRegistry[Flavour][]>;
}
type UIBaseProps<Flavour extends keyof WorkspaceRegistry> = {
currentWorkspace: WorkspaceRegistry[Flavour];
};
type SettingProps<Flavour extends keyof WorkspaceRegistry> =
UIBaseProps<Flavour> & {
currentTab: SettingPanel;
onChangeTab: (tab: SettingPanel) => void;
onDeleteWorkspace: () => void;
onTransformWorkspace: <
From extends keyof WorkspaceRegistry,
To extends keyof WorkspaceRegistry
>(
from: From,
to: To,
workspace: WorkspaceRegistry[From]
) => void;
};
type PageDetailProps<Flavour extends keyof WorkspaceRegistry> =
UIBaseProps<Flavour> & {
currentPageId: string;
};
type PageListProps<Flavour extends keyof WorkspaceRegistry> = {
blockSuiteWorkspace: BlockSuiteWorkspace;
onOpenPage: (pageId: string, newTab?: boolean) => void;
};
type SideBarMenuProps<Flavour extends keyof WorkspaceRegistry> =
UIBaseProps<Flavour> & {
setSideBarOpen: (open: boolean) => void;
};
export interface WorkspaceUISchema<Flavour extends keyof WorkspaceRegistry> {
PageDetail: FC<PageDetailProps<Flavour>>;
PageList: FC<PageListProps<Flavour>>;
SettingsDetail: FC<SettingProps<Flavour>>;
}

View File

@ -0,0 +1,24 @@
import { __unstableSchemas, AffineSchemas } from '@blocksuite/blocks/models';
import type { BlobOptionsGetter, Generator } from '@blocksuite/store';
import { Workspace } from '@blocksuite/store';
const hashMap = new Map<string, Workspace>();
export const createEmptyBlockSuiteWorkspace = (
id: string,
blobOptionsGetter?: BlobOptionsGetter,
idGenerator?: Generator
): Workspace => {
if (hashMap.has(id)) {
return hashMap.get(id) as Workspace;
}
const workspace = new Workspace({
id,
isSSR: typeof window === 'undefined',
blobOptionsGetter,
idGenerator,
})
.register(AffineSchemas)
.register(__unstableSchemas);
hashMap.set(id, workspace);
return workspace;
};

View File

@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["./src"]
}

View File

@ -26,7 +26,9 @@
"@affine/i18n": ["./packages/i18n/src"],
"@affine/debug": ["./packages/debug"],
"@affine/env": ["./packages/env"],
"@affine/utils": ["./packages/utils"]
"@affine/env/*": ["./packages/env/src/*"],
"@affine/utils": ["./packages/utils"],
"@affine/workspace/*": ["./packages/workspace/src/*"]
}
},
"references": [
@ -55,7 +57,7 @@
"path": "./packages/debug"
},
{
"path": "./packages/cli"
"path": "./packages/workspace"
}
],
"files": [],

317
yarn.lock
View File

@ -22,6 +22,7 @@ __metadata:
"@affine/env": "workspace:*"
"@affine/i18n": "workspace:*"
"@affine/templates": "workspace:*"
"@affine/workspace": "workspace:*"
"@blocksuite/blocks": 0.5.0-20230323085636-3110abb
"@blocksuite/editor": 0.5.0-20230323085636-3110abb
"@blocksuite/icons": 2.0.23
@ -251,6 +252,24 @@ __metadata:
languageName: unknown
linkType: soft
"@affine/workspace@workspace:*, @affine/workspace@workspace:packages/workspace":
version: 0.0.0-use.local
resolution: "@affine/workspace@workspace:packages/workspace"
dependencies:
"@affine/component": "workspace:*"
"@affine/debug": "workspace:*"
"@affine/env": "workspace:*"
"@blocksuite/blocks": 0.5.0-20230323085636-3110abb
"@blocksuite/store": 0.5.0-20230323085636-3110abb
firebase: ^9.18.0
jotai: ^2.0.3
js-base64: ^3.7.5
ky: ^0.33.3
react: ^18.2.0
react-dom: ^18.2.0
languageName: unknown
linkType: soft
"@ampproject/remapping@npm:^2.2.0":
version: 2.2.0
resolution: "@ampproject/remapping@npm:2.2.0"
@ -3856,6 +3875,32 @@ __metadata:
languageName: node
linkType: hard
"@mswjs/cookies@npm:^0.2.2":
version: 0.2.2
resolution: "@mswjs/cookies@npm:0.2.2"
dependencies:
"@types/set-cookie-parser": ^2.4.0
set-cookie-parser: ^2.4.6
checksum: 23b1ef56d57efcc1b44600076f531a1fb703855af342a31e01bad4adaf0dab51f6d3b5595a95a7988c3f612ba075835f9a06c52833205284d101eb9a51dd72b0
languageName: node
linkType: hard
"@mswjs/interceptors@npm:^0.17.5":
version: 0.17.9
resolution: "@mswjs/interceptors@npm:0.17.9"
dependencies:
"@open-draft/until": ^1.0.3
"@types/debug": ^4.1.7
"@xmldom/xmldom": ^0.8.3
debug: ^4.3.3
headers-polyfill: ^3.1.0
outvariant: ^1.2.1
strict-event-emitter: ^0.2.4
web-encoding: ^1.1.5
checksum: 4df726cbee93d8baa54ead1ecb11e98124468659f51eb659ef8ead4aca7d6375198baf412ea17d4810fa5f1ee4fa53994702cb3b0b4f6f427a2f0fb890020192
languageName: node
linkType: hard
"@mui/base@npm:5.0.0-alpha.121":
version: 5.0.0-alpha.121
resolution: "@mui/base@npm:5.0.0-alpha.121"
@ -4193,6 +4238,13 @@ __metadata:
languageName: node
linkType: hard
"@open-draft/until@npm:^1.0.3":
version: 1.0.3
resolution: "@open-draft/until@npm:1.0.3"
checksum: 323e92ebef0150ed0f8caedc7d219b68cdc50784fa4eba0377eef93533d3f46514eb2400ced83dda8c51bddc3d2c7b8e9cf95e5ec85ab7f62dfc015d174f62f2
languageName: node
linkType: hard
"@perfsee/bundle-analyzer@npm:1.4.0":
version: 1.4.0
resolution: "@perfsee/bundle-analyzer@npm:1.4.0"
@ -6475,6 +6527,13 @@ __metadata:
languageName: node
linkType: hard
"@types/cookie@npm:^0.4.1":
version: 0.4.1
resolution: "@types/cookie@npm:0.4.1"
checksum: 3275534ed69a76c68eb1a77d547d75f99fedc80befb75a3d1d03662fb08d697e6f8b1274e12af1a74c6896071b11510631ba891f64d30c78528d0ec45a9c1a18
languageName: node
linkType: hard
"@types/debug@npm:^4.1.7":
version: 4.1.7
resolution: "@types/debug@npm:4.1.7"
@ -6680,6 +6739,13 @@ __metadata:
languageName: node
linkType: hard
"@types/js-levenshtein@npm:^1.1.1":
version: 1.1.1
resolution: "@types/js-levenshtein@npm:1.1.1"
checksum: 1d1ff1ee2ad551909e47f3ce19fcf85b64dc5146d3b531c8d26fc775492d36e380b32cf5ef68ff301e812c3b00282f37aac579ebb44498b94baff0ace7509769
languageName: node
linkType: hard
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.11, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9":
version: 7.0.11
resolution: "@types/json-schema@npm:7.0.11"
@ -6949,6 +7015,15 @@ __metadata:
languageName: node
linkType: hard
"@types/set-cookie-parser@npm:^2.4.0":
version: 2.4.2
resolution: "@types/set-cookie-parser@npm:2.4.2"
dependencies:
"@types/node": "*"
checksum: c31bf04eb9620829dc3c91bced74ac934ad039d20d20893fb5acac0f08769cbd4eef3bf7502a0289c7be59c3e9cfa456147b4e88bff47dd1b9efb4995ba5d5a3
languageName: node
linkType: hard
"@types/stack-utils@npm:^2.0.0":
version: 2.0.1
resolution: "@types/stack-utils@npm:2.0.1"
@ -7395,6 +7470,13 @@ __metadata:
languageName: node
linkType: hard
"@xmldom/xmldom@npm:^0.8.3":
version: 0.8.6
resolution: "@xmldom/xmldom@npm:0.8.6"
checksum: f17ac6d99a971a6aeb831fcfc5cfa86f367664e45815046548814b2deb17ccc421fef4e0d5ba29e66179d112b552f6caa5680064f8e7bd8a389b788a60404c8e
languageName: node
linkType: hard
"@xtuc/ieee754@npm:^1.2.0":
version: 1.2.0
resolution: "@xtuc/ieee754@npm:1.2.0"
@ -7420,6 +7502,13 @@ __metadata:
languageName: node
linkType: hard
"@zxing/text-encoding@npm:0.9.0":
version: 0.9.0
resolution: "@zxing/text-encoding@npm:0.9.0"
checksum: c23b12aee7639382e4949961304a1294776afaffa40f579e09ffecd0e5e68cf26ef3edd75009de46da8a536e571448755ca68b3e2ea707d53793c0edb2e2c34a
languageName: node
linkType: hard
"AFFiNE@workspace:.":
version: 0.0.0-use.local
resolution: "AFFiNE@workspace:."
@ -7447,6 +7536,7 @@ __metadata:
happy-dom: ^8.9.0
husky: ^8.0.3
lint-staged: ^13.2.0
msw: ^1.2.0
nanoid: ^4.0.1
nyc: ^15.1.0
prettier: ^2.8.5
@ -8665,6 +8755,16 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:4.1.1":
version: 4.1.1
resolution: "chalk@npm:4.1.1"
dependencies:
ansi-styles: ^4.1.0
supports-color: ^7.1.0
checksum: 036e973e665ba1a32c975e291d5f3d549bceeb7b1b983320d4598fb75d70fe20c5db5d62971ec0fe76cdbce83985a00ee42372416abfc3a5584465005a7855ed
languageName: node
linkType: hard
"chalk@npm:5.2.0, chalk@npm:^5.2.0":
version: 5.2.0
resolution: "chalk@npm:5.2.0"
@ -8693,7 +8793,7 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.2":
"chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2":
version: 4.1.2
resolution: "chalk@npm:4.1.2"
dependencies:
@ -8717,6 +8817,13 @@ __metadata:
languageName: node
linkType: hard
"chardet@npm:^0.7.0":
version: 0.7.0
resolution: "chardet@npm:0.7.0"
checksum: 6fd5da1f5d18ff5712c1e0aed41da200d7c51c28f11b36ee3c7b483f3696dabc08927fc6b227735eb8f0e1215c9a8abd8154637f3eff8cada5959df7f58b024d
languageName: node
linkType: hard
"check-error@npm:^1.0.2":
version: 1.0.2
resolution: "check-error@npm:1.0.2"
@ -8724,7 +8831,7 @@ __metadata:
languageName: node
linkType: hard
"chokidar@npm:^3.5.3":
"chokidar@npm:^3.4.2, chokidar@npm:^3.5.3":
version: 3.5.3
resolution: "chokidar@npm:3.5.3"
dependencies:
@ -8861,6 +8968,13 @@ __metadata:
languageName: node
linkType: hard
"cli-width@npm:^3.0.0":
version: 3.0.0
resolution: "cli-width@npm:3.0.0"
checksum: 4c94af3769367a70e11ed69aa6095f1c600c0ff510f3921ab4045af961820d57c0233acfa8b6396037391f31b4c397e1f614d234294f979ff61430a6c166c3f6
languageName: node
linkType: hard
"client-only@npm:0.0.1":
version: 0.0.1
resolution: "client-only@npm:0.0.1"
@ -9219,6 +9333,13 @@ __metadata:
languageName: node
linkType: hard
"cookie@npm:^0.4.2":
version: 0.4.2
resolution: "cookie@npm:0.4.2"
checksum: a00833c998bedf8e787b4c342defe5fa419abd96b32f4464f718b91022586b8f1bafbddd499288e75c037642493c83083da426c6a9080d309e3bd90fd11baa9b
languageName: node
linkType: hard
"core-js-compat@npm:^3.25.1":
version: 3.29.1
resolution: "core-js-compat@npm:3.29.1"
@ -11028,7 +11149,7 @@ __metadata:
languageName: node
linkType: hard
"events@npm:^3.2.0":
"events@npm:^3.2.0, events@npm:^3.3.0":
version: 3.3.0
resolution: "events@npm:3.3.0"
checksum: f6f487ad2198aa41d878fa31452f1a3c00958f46e9019286ff4787c84aac329332ab45c9cdc8c445928fc6d7ded294b9e005a7fce9426488518017831b272780
@ -11197,6 +11318,17 @@ __metadata:
languageName: node
linkType: hard
"external-editor@npm:^3.0.3":
version: 3.1.0
resolution: "external-editor@npm:3.1.0"
dependencies:
chardet: ^0.7.0
iconv-lite: ^0.4.24
tmp: ^0.0.33
checksum: 1c2a616a73f1b3435ce04030261bed0e22d4737e14b090bb48e58865da92529c9f2b05b893de650738d55e692d071819b45e1669259b2b354bc3154d27a698c7
languageName: node
linkType: hard
"extract-zip@npm:^1.6.6":
version: 1.7.0
resolution: "extract-zip@npm:1.7.0"
@ -11331,6 +11463,15 @@ __metadata:
languageName: node
linkType: hard
"figures@npm:^3.0.0":
version: 3.2.0
resolution: "figures@npm:3.2.0"
dependencies:
escape-string-regexp: ^1.0.5
checksum: 85a6ad29e9aca80b49b817e7c89ecc4716ff14e3779d9835af554db91bac41c0f289c418923519392a1e582b4d10482ad282021330cd045bb7b80c84152f2a2b
languageName: node
linkType: hard
"file-entry-cache@npm:^6.0.1":
version: 6.0.1
resolution: "file-entry-cache@npm:6.0.1"
@ -12392,7 +12533,7 @@ __metadata:
languageName: node
linkType: hard
"graphql@npm:^16.0.0":
"graphql@npm:^15.0.0 || ^16.0.0, graphql@npm:^16.0.0":
version: 16.6.0
resolution: "graphql@npm:16.6.0"
checksum: bf1d9e3c1938ce3c1a81e909bd3ead1ae4707c577f91cff1ca2eca474bfbc7873d5d7b942e1e9777ff5a8304421dba57a4b76d7a29eb19de8711cb70e3c2415e
@ -12536,6 +12677,13 @@ __metadata:
languageName: node
linkType: hard
"headers-polyfill@npm:^3.1.0":
version: 3.1.2
resolution: "headers-polyfill@npm:3.1.2"
checksum: 510ca9637ef652404dbd432e680418f8d418ba18094ef2f64c3d8de955ebf6e68d553c7f0aeaa5fc937d130b139c1e2d7c2066cd4cf0f740a4627924eaaee9db
languageName: node
linkType: hard
"hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2":
version: 3.3.2
resolution: "hoist-non-react-statics@npm:3.3.2"
@ -12751,7 +12899,7 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:0.4.24":
"iconv-lite@npm:0.4.24, iconv-lite@npm:^0.4.24":
version: 0.4.24
resolution: "iconv-lite@npm:0.4.24"
dependencies:
@ -12889,6 +13037,29 @@ __metadata:
languageName: node
linkType: hard
"inquirer@npm:^8.2.0":
version: 8.2.5
resolution: "inquirer@npm:8.2.5"
dependencies:
ansi-escapes: ^4.2.1
chalk: ^4.1.1
cli-cursor: ^3.1.0
cli-width: ^3.0.0
external-editor: ^3.0.3
figures: ^3.0.0
lodash: ^4.17.21
mute-stream: 0.0.8
ora: ^5.4.1
run-async: ^2.4.0
rxjs: ^7.5.5
string-width: ^4.1.0
strip-ansi: ^6.0.0
through: ^2.3.6
wrap-ansi: ^7.0.0
checksum: f13ee4c444187786fb393609dedf6b30870115a57b603f2e6424f29a99abc13446fd45ee22461c33c9c40a92a60a8df62d0d6b25d74fc6676fa4cb211de55b55
languageName: node
linkType: hard
"internal-slot@npm:^1.0.3, internal-slot@npm:^1.0.4, internal-slot@npm:^1.0.5":
version: 1.0.5
resolution: "internal-slot@npm:1.0.5"
@ -13159,6 +13330,13 @@ __metadata:
languageName: node
linkType: hard
"is-node-process@npm:^1.0.1":
version: 1.0.1
resolution: "is-node-process@npm:1.0.1"
checksum: 3ddb8a892a00f6eb9c2aea7e7e1426b8683512d9419933d95114f4f64b5455e26601c23a31c0682463890032136dd98a326988a770ab6b4eed54a43ade8bed50
languageName: node
linkType: hard
"is-number-object@npm:^1.0.4":
version: 1.0.7
resolution: "is-number-object@npm:1.0.7"
@ -14239,6 +14417,13 @@ __metadata:
languageName: node
linkType: hard
"js-levenshtein@npm:^1.1.6":
version: 1.1.6
resolution: "js-levenshtein@npm:1.1.6"
checksum: 409f052a7f1141be4058d97da7860e08efd97fc588b7a4c5cfa0548bc04f6d576644dae65ab630266dff685d56fb90d494e03d4d79cb484c287746b4f1bf0694
languageName: node
linkType: hard
"js-sdsl@npm:^4.1.4":
version: 4.3.0
resolution: "js-sdsl@npm:4.3.0"
@ -15469,6 +15654,40 @@ __metadata:
languageName: node
linkType: hard
"msw@npm:^1.2.0":
version: 1.2.0
resolution: "msw@npm:1.2.0"
dependencies:
"@mswjs/cookies": ^0.2.2
"@mswjs/interceptors": ^0.17.5
"@open-draft/until": ^1.0.3
"@types/cookie": ^0.4.1
"@types/js-levenshtein": ^1.1.1
chalk: 4.1.1
chokidar: ^3.4.2
cookie: ^0.4.2
graphql: ^15.0.0 || ^16.0.0
headers-polyfill: ^3.1.0
inquirer: ^8.2.0
is-node-process: ^1.0.1
js-levenshtein: ^1.1.6
node-fetch: ^2.6.7
outvariant: ^1.3.0
path-to-regexp: ^6.2.0
strict-event-emitter: ^0.4.3
type-fest: ^2.19.0
yargs: ^17.3.1
peerDependencies:
typescript: ">= 4.4.x <= 5.0.x"
peerDependenciesMeta:
typescript:
optional: true
bin:
msw: cli/index.js
checksum: 2cea7fe0f2ebb59b11534896cbaa91dca65f4fb2272402549079cc4190847fc42f5367f331c552d576d59a3521f498da0384d53cf4960beaebd6165bbc44593b
languageName: node
linkType: hard
"multipipe@npm:^1.0.2":
version: 1.0.2
resolution: "multipipe@npm:1.0.2"
@ -15490,6 +15709,13 @@ __metadata:
languageName: node
linkType: hard
"mute-stream@npm:0.0.8":
version: 0.0.8
resolution: "mute-stream@npm:0.0.8"
checksum: ff48d251fc3f827e5b1206cda0ffdaec885e56057ee86a3155e1951bc940fd5f33531774b1cc8414d7668c10a8907f863f6561875ee6e8768931a62121a531a1
languageName: node
linkType: hard
"mz@npm:^2.7.0":
version: 2.7.0
resolution: "mz@npm:2.7.0"
@ -16150,7 +16376,7 @@ __metadata:
languageName: node
linkType: hard
"ora@npm:^5.1.0":
"ora@npm:^5.1.0, ora@npm:^5.4.1":
version: 5.4.1
resolution: "ora@npm:5.4.1"
dependencies:
@ -16174,6 +16400,20 @@ __metadata:
languageName: node
linkType: hard
"os-tmpdir@npm:~1.0.2":
version: 1.0.2
resolution: "os-tmpdir@npm:1.0.2"
checksum: 5666560f7b9f10182548bf7013883265be33620b1c1b4a4d405c25be2636f970c5488ff3e6c48de75b55d02bde037249fe5dbfbb4c0fb7714953d56aed062e6d
languageName: node
linkType: hard
"outvariant@npm:^1.2.1, outvariant@npm:^1.3.0":
version: 1.3.0
resolution: "outvariant@npm:1.3.0"
checksum: ac76ca375c1c642989e1c74f0e9ebac84c05bc9fdc8f28be949c16fae1658e9f1f2fb1133fe3cc1e98afabef78fe4298fe9360b5734baf8e6ad440c182680848
languageName: node
linkType: hard
"p-cancelable@npm:^2.0.0":
version: 2.1.1
resolution: "p-cancelable@npm:2.1.1"
@ -16477,6 +16717,13 @@ __metadata:
languageName: node
linkType: hard
"path-to-regexp@npm:^6.2.0":
version: 6.2.1
resolution: "path-to-regexp@npm:6.2.1"
checksum: f0227af8284ea13300f4293ba111e3635142f976d4197f14d5ad1f124aebd9118783dd2e5f1fe16f7273743cc3dbeddfb7493f237bb27c10fdae07020cc9b698
languageName: node
linkType: hard
"path-type@npm:^2.0.0":
version: 2.0.0
resolution: "path-type@npm:2.0.0"
@ -17848,6 +18095,13 @@ __metadata:
languageName: node
linkType: hard
"run-async@npm:^2.4.0":
version: 2.4.1
resolution: "run-async@npm:2.4.1"
checksum: a2c88aa15df176f091a2878eb840e68d0bdee319d8d97bbb89112223259cebecb94bc0defd735662b83c2f7a30bed8cddb7d1674eb48ae7322dc602b22d03797
languageName: node
linkType: hard
"run-parallel@npm:^1.1.9":
version: 1.2.0
resolution: "run-parallel@npm:1.2.0"
@ -17866,7 +18120,7 @@ __metadata:
languageName: node
linkType: hard
"rxjs@npm:^7.8.0":
"rxjs@npm:^7.5.5, rxjs@npm:^7.8.0":
version: 7.8.0
resolution: "rxjs@npm:7.8.0"
dependencies:
@ -18057,6 +18311,13 @@ __metadata:
languageName: node
linkType: hard
"set-cookie-parser@npm:^2.4.6":
version: 2.6.0
resolution: "set-cookie-parser@npm:2.6.0"
checksum: bf11ebc594c53d84588f1b4c04f1b8ce14e0498b1c011b3d76b5c6d5aac481bbc3f7c5260ec4ce99bdc1d9aed19f9fc315e73166a36ca74d0f12349a73f6bdc9
languageName: node
linkType: hard
"setprototypeof@npm:1.2.0":
version: 1.2.0
resolution: "setprototypeof@npm:1.2.0"
@ -18541,6 +18802,22 @@ __metadata:
languageName: node
linkType: hard
"strict-event-emitter@npm:^0.2.4":
version: 0.2.8
resolution: "strict-event-emitter@npm:0.2.8"
dependencies:
events: ^3.3.0
checksum: 6ac06fe72a6ee6ae64d20f1dd42838ea67342f1b5f32b03b3050d73ee6ecee44b4d5c4ed2965a7154b47991e215f373d4e789e2b2be2769cd80e356126c2ca53
languageName: node
linkType: hard
"strict-event-emitter@npm:^0.4.3":
version: 0.4.6
resolution: "strict-event-emitter@npm:0.4.6"
checksum: 4f4f2909613e7811de789991c06bfb770d6d6987e2ec5c66fa7485d0f07cc4e7e32eba0dcf26cee6d86af6c92946d7f4acdfaff57d0c4114df2cfa1bf0e3c091
languageName: node
linkType: hard
"strict-uri-encode@npm:^2.0.0":
version: 2.0.0
resolution: "strict-uri-encode@npm:2.0.0"
@ -19101,7 +19378,7 @@ __metadata:
languageName: node
linkType: hard
"through@npm:2, through@npm:^2.3.8, through@npm:~2.3, through@npm:~2.3.1":
"through@npm:2, through@npm:^2.3.6, through@npm:^2.3.8, through@npm:~2.3, through@npm:~2.3.1":
version: 2.3.8
resolution: "through@npm:2.3.8"
checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd
@ -19165,6 +19442,15 @@ __metadata:
languageName: node
linkType: hard
"tmp@npm:^0.0.33":
version: 0.0.33
resolution: "tmp@npm:0.0.33"
dependencies:
os-tmpdir: ~1.0.2
checksum: 902d7aceb74453ea02abbf58c203f4a8fc1cead89b60b31e354f74ed5b3fb09ea817f94fb310f884a5d16987dd9fa5a735412a7c2dd088dd3d415aa819ae3a28
languageName: node
linkType: hard
"tmp@npm:^0.2.0":
version: 0.2.1
resolution: "tmp@npm:0.2.1"
@ -19817,7 +20103,7 @@ __metadata:
languageName: node
linkType: hard
"util@npm:^0.12.0, util@npm:^0.12.4":
"util@npm:^0.12.0, util@npm:^0.12.3, util@npm:^0.12.4":
version: 0.12.5
resolution: "util@npm:0.12.5"
dependencies:
@ -20090,6 +20376,19 @@ __metadata:
languageName: node
linkType: hard
"web-encoding@npm:^1.1.5":
version: 1.1.5
resolution: "web-encoding@npm:1.1.5"
dependencies:
"@zxing/text-encoding": 0.9.0
util: ^0.12.3
dependenciesMeta:
"@zxing/text-encoding":
optional: true
checksum: 2234a2b122f41006ce07859b3c0bf2e18f46144fda2907d5db0b571b76aa5c26977c646100ad9c00d2f8a4f6f2b848bc02147845d8c447ab365ec4eff376338d
languageName: node
linkType: hard
"web-streams-polyfill@npm:^3.0.3":
version: 3.2.1
resolution: "web-streams-polyfill@npm:3.2.1"