feat: optional provider

This commit is contained in:
DarkSky
2022-08-12 16:54:47 +08:00
parent 57b8263acd
commit edfa95057b
11 changed files with 267 additions and 85 deletions

View File

@@ -23,7 +23,7 @@ export function RoutePrivate({
return <PageLoading />; return <PageLoading />;
} }
if (!user) { if (!user || !pathname.startsWith(`/${user.id}`)) {
return ( return (
<Navigate <Navigate
to={unauthorizedRedirectTo} to={unauthorizedRedirectTo}

View File

@@ -46,7 +46,7 @@ const requestPermission = async (workspace: string) => {
}; };
export const FileSystem = (props: { onError: () => void }) => { export const FileSystem = (props: { onError: () => void }) => {
const onSelected = useLocalTrigger(); const [, onSelected] = useLocalTrigger();
const apiSupported = useMemo(() => { const apiSupported = useMemo(() => {
try { try {
@@ -56,12 +56,6 @@ export const FileSystem = (props: { onError: () => void }) => {
} }
}, []); }, []);
useEffect(() => {
if (process.env['NX_E2E']) {
onSelected();
}
}, []);
return ( return (
<MuiButton <MuiButton
variant="outlined" variant="outlined"

View File

@@ -21,7 +21,7 @@ export function Login() {
<MuiSnackbar <MuiSnackbar
anchorOrigin={{ vertical: 'top', horizontal: 'right' }} anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
open={error} open={error}
message="Login failed, please check if you have permission" message="Request File Permission failed, please check if you have permission"
/> />
<MuiGrid item xs={8}> <MuiGrid item xs={8}>
<Error <Error
@@ -32,7 +32,6 @@ export function Login() {
</MuiGrid> </MuiGrid>
<MuiGrid item xs={4}> <MuiGrid item xs={4}>
{' '}
<MuiBox <MuiBox
style={{ style={{
display: 'flex', display: 'flex',

View File

@@ -0,0 +1,112 @@
/* eslint-disable filename-rules/match */
import { useCallback, useMemo, useState } from 'react';
import { IconButton, MuiSnackbar, styled } from '@toeverything/components/ui';
import { services } from '@toeverything/datasource/db-service';
import { useLocalTrigger } from '@toeverything/datasource/state';
import { CloseIcon } from '@toeverything/components/common';
const cleanupWorkspace = (workspace: string) =>
new Promise((resolve, reject) => {
const req = indexedDB.deleteDatabase(workspace);
req.addEventListener('error', e => reject(e));
req.addEventListener('blocked', e => reject(e));
req.addEventListener('upgradeneeded', e => reject(e));
req.addEventListener('success', e => resolve(e));
});
const requestPermission = async (workspace: string) => {
await cleanupWorkspace(workspace);
const dirHandler = await window.showDirectoryPicker({
id: 'AFFiNE_' + workspace,
mode: 'readwrite',
startIn: 'documents',
});
const fileHandle = await dirHandler.getFileHandle('affine.db', {
create: true,
});
const file = await fileHandle.getFile();
const initialData = new Uint8Array(await file.arrayBuffer());
const exporter = async (contents: Uint8Array) => {
try {
const writable = await fileHandle.createWritable();
await writable.write(contents);
await writable.close();
} catch (e) {
console.log(e);
}
};
await services.api.editorBlock.setupDataExporter(
workspace,
new Uint8Array(initialData),
exporter
);
};
const StyledFileSystem = styled('div')<{ disabled?: boolean }>({
padding: '10px 12px',
fontWeight: 600,
fontSize: '14px',
color: '#3E6FDB',
textTransform: 'uppercase',
cursor: 'pointer',
'&:hover': {
background: '#F5F7F8',
borderRadius: '5px',
},
});
export const FileSystem = () => {
const [selected, onSelected] = useLocalTrigger();
const [error, setError] = useState(false);
const onError = useCallback(() => {
setError(true);
setTimeout(() => setError(false), 3000);
}, []);
const apiSupported = useMemo(() => {
try {
return 'showOpenFilePicker' in window;
} catch (e) {
return false;
}
}, []);
if (apiSupported && !selected) {
return (
<>
<MuiSnackbar
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
open={error}
message="Request File Permission failed, please check if you have permission"
sx={{ marginTop: '3em' }}
action={
<IconButton
aria-label="close"
onClick={() => setError(false)}
>
<CloseIcon />
</IconButton>
}
/>
<StyledFileSystem
onClick={async () => {
try {
await requestPermission('AFFiNE');
onSelected();
} catch (e) {
onError();
}
}}
>
Sync to Disk
</StyledFileSystem>
</>
);
}
return null;
};

View File

@@ -1,4 +1,4 @@
import { IconButton, styled, MuiButton } from '@toeverything/components/ui'; import { IconButton, styled } from '@toeverything/components/ui';
import { import {
LogoIcon, LogoIcon,
SideBarViewIcon, SideBarViewIcon,
@@ -7,8 +7,9 @@ import {
} from '@toeverything/components/icons'; } from '@toeverything/components/icons';
import { useShowSettingsSidebar } from '@toeverything/datasource/state'; import { useShowSettingsSidebar } from '@toeverything/datasource/state';
import { CurrentPageTitle } from './Title';
import { EditorBoardSwitcher } from './EditorBoardSwitcher'; import { EditorBoardSwitcher } from './EditorBoardSwitcher';
import { FileSystem } from './FileSystem';
import { CurrentPageTitle } from './Title';
export const LayoutHeader = () => { export const LayoutHeader = () => {
const { toggleSettingsSidebar: toggleInfoSidebar, showSettingsSidebar } = const { toggleSettingsSidebar: toggleInfoSidebar, showSettingsSidebar } =
@@ -25,6 +26,7 @@ export const LayoutHeader = () => {
</FlexContainer> </FlexContainer>
<FlexContainer> <FlexContainer>
<StyledHelper> <StyledHelper>
<FileSystem />
<StyledShare disabled={true}>Share</StyledShare> <StyledShare disabled={true}>Share</StyledShare>
<div style={{ margin: '0px 12px' }}> <div style={{ margin: '0px 12px' }}>
<IconButton <IconButton

View File

@@ -37,6 +37,11 @@ async function _getCurrentToken() {
return undefined; return undefined;
} }
const _enabled = {
demo: [],
AFFiNE: ['sqlite'],
} as any;
async function _getBlockDatabase( async function _getBlockDatabase(
workspace: string, workspace: string,
options?: BlockInitOptions options?: BlockInitOptions
@@ -45,16 +50,10 @@ async function _getBlockDatabase(
await waitLoading(workspace); await waitLoading(workspace);
} }
// if (
// options?.userId &&
// workspaces[workspace]?.getUserId() !== options?.userId
// ) {
// delete workspaces[workspace];
// }
if (!workspaces[workspace]) { if (!workspaces[workspace]) {
loading.add(workspace); loading.add(workspace);
workspaces[workspace] = await BlockClient.init(workspace, { workspaces[workspace] = await BlockClient.init(workspace, {
enabled: _enabled[workspace] || ['idb'],
...options, ...options,
token: await _getCurrentToken(), token: await _getCurrentToken(),
}); });

View File

@@ -18,8 +18,6 @@ import {
snapshot, snapshot,
} from 'yjs'; } from 'yjs';
import { IndexedDBProvider } from '@toeverything/datasource/jwt-rpc';
import { import {
AsyncDatabaseAdapter, AsyncDatabaseAdapter,
BlockListener, BlockListener,
@@ -48,15 +46,17 @@ type ConnectivityListener = (
workspace: string, workspace: string,
connectivity: Connectivity connectivity: Connectivity
) => void; ) => void;
type YjsProviders = { type YjsProviders = {
awareness: Awareness; awareness: Awareness;
idb: IndexedDBProvider; binaries: Doc;
binariesIdb: IndexedDBProvider; doc: Doc;
gatekeeper: GateKeeper; gatekeeper: GateKeeper;
connListener: { listeners?: ConnectivityListener }; connListener: { listeners?: ConnectivityListener };
userId: string; userId: string;
remoteToken?: string; // remote storage token remoteToken?: string; // remote storage token
}; };
const _yjsDatabaseInstance = new Map<string, YjsProviders>(); const _yjsDatabaseInstance = new Map<string, YjsProviders>();
const _asyncInitLoading = new Set<string>(); const _asyncInitLoading = new Set<string>();
@@ -90,13 +90,9 @@ async function _initYjsDatabase(
const { userId, token } = options; const { userId, token } = options;
const doc = new Doc({ autoLoad: true, shouldLoad: true }); const doc = new Doc({ autoLoad: true, shouldLoad: true });
const idb = await new IndexedDBProvider(workspace, doc).whenSynced; // const idb = await new IndexedDBProvider(workspace, doc).whenSynced;
const binaries = new Doc({ autoLoad: true, shouldLoad: true }); const binaries = new Doc({ autoLoad: true, shouldLoad: true });
const binariesIdb = await new IndexedDBProvider(
`${workspace}_binaries`,
binaries
).whenSynced;
const awareness = new Awareness(doc); const awareness = new Awareness(doc);
@@ -114,16 +110,24 @@ async function _initYjsDatabase(
const emitState = (c: Connectivity) => const emitState = (c: Connectivity) =>
connListener.listeners?.(workspace, c); connListener.listeners?.(workspace, c);
await Promise.all( await Promise.all(
Object.entries(options.provider).map(async ([, p]) => Object.entries(options.provider).flatMap(async ([, p]) => [
p({ awareness, doc, token, workspace, emitState }) p({ awareness, doc, token, workspace, emitState }),
) p({
awareness,
doc: binaries,
token,
workspace: `${workspace}_binaries`,
emitState,
}),
])
); );
} }
const newInstance = { const newInstance = {
awareness, awareness,
idb, binaries,
binariesIdb, doc,
gatekeeper, gatekeeper,
connListener, connListener,
userId, userId,
remoteToken: token, remoteToken: token,
@@ -183,7 +187,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
private constructor(providers: YjsProviders) { private constructor(providers: YjsProviders) {
this._provider = providers; this._provider = providers;
this._doc = providers.idb.doc; this._doc = providers.doc;
this._awareness = providers.awareness; this._awareness = providers.awareness;
this._gatekeeper = providers.gatekeeper; this._gatekeeper = providers.gatekeeper;
this._reload = () => { this._reload = () => {
@@ -201,7 +205,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
}); });
// @ts-ignore // @ts-ignore
this._binaries = new YjsRemoteBinaries( this._binaries = new YjsRemoteBinaries(
providers.binariesIdb.doc.getMap(), providers.binaries.getMap(),
providers.remoteToken providers.remoteToken
); );
// @ts-ignore // @ts-ignore
@@ -336,7 +340,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
}); });
const [file] = (await fromEvent(handles)) as File[]; const [file] = (await fromEvent(handles)) as File[];
const binary = await file.arrayBuffer(); const binary = await file.arrayBuffer();
await this._provider.idb.clearData(); // await this._provider.idb.clearData();
const doc = new Doc({ autoLoad: true, shouldLoad: true }); const doc = new Doc({ autoLoad: true, shouldLoad: true });
let updated = 0; let updated = 0;
let isUpdated = false; let isUpdated = false;
@@ -357,8 +361,8 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
}; };
check(); check();
}); });
await new IndexedDBProvider(this._provider.idb.name, doc) // await new IndexedDBProvider(this._provider.idb.name, doc)
.whenSynced; // .whenSynced;
applyUpdate(doc, new Uint8Array(binary)); applyUpdate(doc, new Uint8Array(binary));
await update_check; await update_check;
console.log('load success'); console.log('load success');

View File

@@ -2,6 +2,7 @@ import { Doc } from 'yjs';
import { Awareness } from 'y-protocols/awareness.js'; import { Awareness } from 'y-protocols/awareness.js';
import { import {
IndexedDBProvider,
SQLiteProvider, SQLiteProvider,
WebsocketProvider, WebsocketProvider,
} from '@toeverything/datasource/jwt-rpc'; } from '@toeverything/datasource/jwt-rpc';
@@ -19,7 +20,10 @@ type YjsDefaultInstances = {
export type YjsProvider = (instances: YjsDefaultInstances) => Promise<void>; export type YjsProvider = (instances: YjsDefaultInstances) => Promise<void>;
type ProviderType = 'idb' | 'sqlite' | 'ws';
export type YjsProviderOptions = { export type YjsProviderOptions = {
enabled: ProviderType[];
backend: typeof BucketBackend[keyof typeof BucketBackend]; backend: typeof BucketBackend[keyof typeof BucketBackend];
params?: Record<string, string>; params?: Record<string, string>;
importData?: () => Promise<Uint8Array> | Uint8Array | undefined; importData?: () => Promise<Uint8Array> | Uint8Array | undefined;
@@ -30,53 +34,64 @@ export type YjsProviderOptions = {
export const getYjsProviders = ( export const getYjsProviders = (
options: YjsProviderOptions options: YjsProviderOptions
): Record<string, YjsProvider> => { ): Record<string, YjsProvider> => {
console.log('getYjsProviders', options);
return { return {
indexeddb: async (instances: YjsDefaultInstances) => {
if (options.enabled.includes('idb')) {
await new IndexedDBProvider(instances.workspace, instances.doc)
.whenSynced;
}
},
sqlite: async (instances: YjsDefaultInstances) => { sqlite: async (instances: YjsDefaultInstances) => {
const fsHandle = setInterval(async () => { if (options.enabled.includes('sqlite')) {
if (options.hasExporter?.()) { const fsHandle = setInterval(async () => {
clearInterval(fsHandle); if (options.hasExporter?.()) {
const fs = new SQLiteProvider( clearInterval(fsHandle);
instances.workspace, const fs = new SQLiteProvider(
instances.doc, instances.workspace,
await options.importData?.() instances.doc,
); await options.importData?.()
if (options.exportData) { );
fs.registerExporter(options.exportData); if (options.exportData) {
fs.registerExporter(options.exportData);
}
await fs.whenSynced;
} }
await fs.whenSynced; }, 500);
} }
}, 500);
}, },
ws: async (instances: YjsDefaultInstances) => { ws: async (instances: YjsDefaultInstances) => {
if (instances.token) { if (options.enabled.includes('ws')) {
const ws = new WebsocketProvider( if (instances.token) {
instances.token, const ws = new WebsocketProvider(
options.backend, instances.token,
instances.workspace, options.backend,
instances.doc, instances.workspace,
{ instances.doc,
awareness: instances.awareness, {
params: options.params, awareness: instances.awareness,
} params: options.params,
) as any; // TODO: type is erased after cascading references }
) as any; // TODO: type is erased after cascading references
// Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later // Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
// TODO: synced will also be triggered on reconnection after losing sync // TODO: synced will also be triggered on reconnection after losing sync
// There needs to be an event mechanism to emit the synchronization state to the upper layer // There needs to be an event mechanism to emit the synchronization state to the upper layer
ws.once('synced', () => resolve()); ws.once('synced', () => resolve());
ws.once('lost-connection', () => resolve()); ws.once('lost-connection', () => resolve());
ws.once('connection-error', () => reject()); ws.once('connection-error', () => reject());
ws.on('synced', () => instances.emitState('connected')); ws.on('synced', () => instances.emitState('connected'));
ws.on('lost-connection', () => ws.on('lost-connection', () =>
instances.emitState('retry') instances.emitState('retry')
); );
ws.on('connection-error', () => ws.on('connection-error', () =>
instances.emitState('retry') instances.emitState('retry')
); );
}); });
} else { } else {
return; return;
}
} }
}, },
}; };

View File

@@ -618,6 +618,7 @@ export class BlockClient<
const instance = await YjsAdapter.init(workspace, { const instance = await YjsAdapter.init(workspace, {
provider: getYjsProviders({ provider: getYjsProviders({
enabled: [],
backend: BucketBackend.YjsWebSocketAffine, backend: BucketBackend.YjsWebSocketAffine,
importData, importData,
exportData, exportData,

View File

@@ -1,3 +1,4 @@
import { useNavigate } from 'react-router';
import { import {
getAuth, getAuth,
onAuthStateChanged, onAuthStateChanged,
@@ -62,11 +63,17 @@ const BRAND_ID = 'AFFiNE';
const _localTrigger = atom<boolean>(false); const _localTrigger = atom<boolean>(false);
const _useUserAndSpacesForFreeLogin = () => { const _useUserAndSpacesForFreeLogin = () => {
const navigate = useNavigate();
const [user, setUser] = useAtom(_userAtom); const [user, setUser] = useAtom(_userAtom);
const [loading, setLoading] = useAtom(_loadingAtom); const [loading, setLoading] = useAtom(_loadingAtom);
const [localTrigger] = useAtom(_localTrigger); const [localTrigger] = useAtom(_localTrigger);
useEffect(() => setLoading(false), []); useEffect(() => {
if (loading) {
navigate('/demo');
setLoading(false);
}
}, []);
useEffect(() => { useEffect(() => {
if (localTrigger) { if (localTrigger) {
@@ -77,6 +84,14 @@ const _useUserAndSpacesForFreeLogin = () => {
nickname: BRAND_ID, nickname: BRAND_ID,
email: '', email: '',
}); });
} else {
setUser({
photo: '',
id: 'demo',
username: 'demo',
nickname: 'demo',
email: '',
});
} }
}, [localTrigger, setLoading, setUser]); }, [localTrigger, setLoading, setUser]);
@@ -86,8 +101,8 @@ const _useUserAndSpacesForFreeLogin = () => {
}; };
export const useLocalTrigger = () => { export const useLocalTrigger = () => {
const [, setTrigger] = useAtom(_localTrigger); const [trigger, setTrigger] = useAtom(_localTrigger);
return () => setTrigger(true); return [trigger, () => setTrigger(true)] as [boolean, () => void];
}; };
export const useUserAndSpaces = process.env['NX_LOCAL'] export const useUserAndSpaces = process.env['NX_LOCAL']

47
pnpm-lock.yaml generated
View File

@@ -194,7 +194,7 @@ importers:
yjs: ^13.5.41 yjs: ^13.5.41
dependencies: dependencies:
authing-js-sdk: 4.23.35 authing-js-sdk: 4.23.35
firebase-admin: 11.0.1_@firebase+app-types@0.7.0 firebase-admin: 11.0.1
lib0: 0.2.52 lib0: 0.2.52
lru-cache: 7.13.2 lru-cache: 7.13.2
nanoid: 4.0.0 nanoid: 4.0.0
@@ -571,6 +571,9 @@ importers:
dependencies: dependencies:
ffc-js-client-side-sdk: 1.1.5 ffc-js-client-side-sdk: 1.1.5
libs/datasource/jwst/pkg:
specifiers: {}
libs/datasource/jwt: libs/datasource/jwt:
specifiers: specifiers:
'@types/debug': ^4.1.7 '@types/debug': ^4.1.7
@@ -3291,6 +3294,15 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/@firebase/auth-interop-types/0.1.6_@firebase+util@1.6.3:
resolution: {integrity: sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==}
peerDependencies:
'@firebase/app-types': 0.x
'@firebase/util': 1.x
dependencies:
'@firebase/util': 1.6.3
dev: false
/@firebase/auth-interop-types/0.1.6_pbfwexsq7uf6mrzcwnikj3g37m: /@firebase/auth-interop-types/0.1.6_pbfwexsq7uf6mrzcwnikj3g37m:
resolution: {integrity: sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==} resolution: {integrity: sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==}
peerDependencies: peerDependencies:
@@ -3299,6 +3311,7 @@ packages:
dependencies: dependencies:
'@firebase/app-types': 0.7.0 '@firebase/app-types': 0.7.0
'@firebase/util': 1.6.3 '@firebase/util': 1.6.3
dev: true
/@firebase/auth-types/0.11.0_pbfwexsq7uf6mrzcwnikj3g37m: /@firebase/auth-types/0.11.0_pbfwexsq7uf6mrzcwnikj3g37m:
resolution: {integrity: sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==} resolution: {integrity: sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==}
@@ -3334,6 +3347,19 @@ packages:
'@firebase/util': 1.6.3 '@firebase/util': 1.6.3
tslib: 2.4.0 tslib: 2.4.0
/@firebase/database-compat/0.2.4:
resolution: {integrity: sha512-VtsGixO5mTjNMJn6PwxAJEAR70fj+3blCXIdQKel3q+eYGZAfdqxox1+tzZDnf9NWBJpaOgAHPk3JVDxEo9NFQ==}
dependencies:
'@firebase/component': 0.5.17
'@firebase/database': 0.13.4
'@firebase/database-types': 0.9.12
'@firebase/logger': 0.3.3
'@firebase/util': 1.6.3
tslib: 2.4.0
transitivePeerDependencies:
- '@firebase/app-types'
dev: false
/@firebase/database-compat/0.2.4_@firebase+app-types@0.7.0: /@firebase/database-compat/0.2.4_@firebase+app-types@0.7.0:
resolution: {integrity: sha512-VtsGixO5mTjNMJn6PwxAJEAR70fj+3blCXIdQKel3q+eYGZAfdqxox1+tzZDnf9NWBJpaOgAHPk3JVDxEo9NFQ==} resolution: {integrity: sha512-VtsGixO5mTjNMJn6PwxAJEAR70fj+3blCXIdQKel3q+eYGZAfdqxox1+tzZDnf9NWBJpaOgAHPk3JVDxEo9NFQ==}
dependencies: dependencies:
@@ -3345,6 +3371,7 @@ packages:
tslib: 2.4.0 tslib: 2.4.0
transitivePeerDependencies: transitivePeerDependencies:
- '@firebase/app-types' - '@firebase/app-types'
dev: true
/@firebase/database-types/0.9.10: /@firebase/database-types/0.9.10:
resolution: {integrity: sha512-2ji6nXRRsY+7hgU6zRhUtK0RmSjVWM71taI7Flgaw+BnopCo/lDF5HSwxp8z7LtiHlvQqeRA3Ozqx5VhlAbiKg==} resolution: {integrity: sha512-2ji6nXRRsY+7hgU6zRhUtK0RmSjVWM71taI7Flgaw+BnopCo/lDF5HSwxp8z7LtiHlvQqeRA3Ozqx5VhlAbiKg==}
@@ -3359,6 +3386,19 @@ packages:
'@firebase/app-types': 0.7.0 '@firebase/app-types': 0.7.0
'@firebase/util': 1.6.3 '@firebase/util': 1.6.3
/@firebase/database/0.13.4:
resolution: {integrity: sha512-NW7bOoiaC4sJCj6DY/m9xHoFNa0CK32YPMCh6FiMweLCDQbOZM8Ql/Kn6yyuxCb7K7ypz9eSbRlCWQJsJRQjhg==}
dependencies:
'@firebase/auth-interop-types': 0.1.6_@firebase+util@1.6.3
'@firebase/component': 0.5.17
'@firebase/logger': 0.3.3
'@firebase/util': 1.6.3
faye-websocket: 0.11.4
tslib: 2.4.0
transitivePeerDependencies:
- '@firebase/app-types'
dev: false
/@firebase/database/0.13.4_@firebase+app-types@0.7.0: /@firebase/database/0.13.4_@firebase+app-types@0.7.0:
resolution: {integrity: sha512-NW7bOoiaC4sJCj6DY/m9xHoFNa0CK32YPMCh6FiMweLCDQbOZM8Ql/Kn6yyuxCb7K7ypz9eSbRlCWQJsJRQjhg==} resolution: {integrity: sha512-NW7bOoiaC4sJCj6DY/m9xHoFNa0CK32YPMCh6FiMweLCDQbOZM8Ql/Kn6yyuxCb7K7ypz9eSbRlCWQJsJRQjhg==}
dependencies: dependencies:
@@ -3370,6 +3410,7 @@ packages:
tslib: 2.4.0 tslib: 2.4.0
transitivePeerDependencies: transitivePeerDependencies:
- '@firebase/app-types' - '@firebase/app-types'
dev: true
/@firebase/firestore-compat/0.1.23_53yvy43rwpg2c45kgeszsxtrca: /@firebase/firestore-compat/0.1.23_53yvy43rwpg2c45kgeszsxtrca:
resolution: {integrity: sha512-QfcuyMAavp//fQnjSfCEpnbWi7spIdKaXys1kOLu7395fLr+U6ykmto1HUMCSz8Yus9cEr/03Ujdi2SUl2GUAA==} resolution: {integrity: sha512-QfcuyMAavp//fQnjSfCEpnbWi7spIdKaXys1kOLu7395fLr+U6ykmto1HUMCSz8Yus9cEr/03Ujdi2SUl2GUAA==}
@@ -10863,12 +10904,12 @@ packages:
semver-regex: 2.0.0 semver-regex: 2.0.0
dev: true dev: true
/firebase-admin/11.0.1_@firebase+app-types@0.7.0: /firebase-admin/11.0.1:
resolution: {integrity: sha512-rL3wlZbi2Kb/KJgcmj1YHlD4ZhfmhfgRO2YJialxAllm0tj1IQea878hHuBLGmv4DpbW9t9nLvX9kddNR2Y65Q==} resolution: {integrity: sha512-rL3wlZbi2Kb/KJgcmj1YHlD4ZhfmhfgRO2YJialxAllm0tj1IQea878hHuBLGmv4DpbW9t9nLvX9kddNR2Y65Q==}
engines: {node: '>=14'} engines: {node: '>=14'}
dependencies: dependencies:
'@fastify/busboy': 1.1.0 '@fastify/busboy': 1.1.0
'@firebase/database-compat': 0.2.4_@firebase+app-types@0.7.0 '@firebase/database-compat': 0.2.4
'@firebase/database-types': 0.9.10 '@firebase/database-types': 0.9.10
'@types/node': 18.0.1 '@types/node': 18.0.1
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1