mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-22 08:47:10 +08:00
feat(nbstore): add cloud implementation (#8810)
This commit is contained in:
173
packages/common/nbstore/src/impls/cloud/socket.ts
Normal file
173
packages/common/nbstore/src/impls/cloud/socket.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
import {
|
||||
Manager as SocketIOManager,
|
||||
type Socket as SocketIO,
|
||||
type SocketOptions,
|
||||
} from 'socket.io-client';
|
||||
|
||||
import { Connection, type ConnectionStatus } from '../../connection';
|
||||
|
||||
// TODO(@forehalo): use [UserFriendlyError]
|
||||
interface EventError {
|
||||
name: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
type WebsocketResponse<T> =
|
||||
| {
|
||||
error: EventError;
|
||||
}
|
||||
| {
|
||||
data: T;
|
||||
};
|
||||
|
||||
interface ServerEvents {
|
||||
'space:broadcast-doc-update': {
|
||||
spaceType: string;
|
||||
spaceId: string;
|
||||
docId: string;
|
||||
update: string;
|
||||
timestamp: number;
|
||||
editor: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ClientEvents {
|
||||
'space:join': [
|
||||
{ spaceType: string; spaceId: string; clientVersion: string },
|
||||
{ clientId: string },
|
||||
];
|
||||
'space:leave': { spaceType: string; spaceId: string };
|
||||
'space:join-awareness': [
|
||||
{
|
||||
spaceType: string;
|
||||
spaceId: string;
|
||||
docId: string;
|
||||
clientVersion: string;
|
||||
},
|
||||
{ clientId: string },
|
||||
];
|
||||
'space:leave-awareness': {
|
||||
spaceType: string;
|
||||
spaceId: string;
|
||||
docId: string;
|
||||
};
|
||||
|
||||
'space:push-doc-update': [
|
||||
{ spaceType: string; spaceId: string; docId: string; updates: string },
|
||||
{ timestamp: number },
|
||||
];
|
||||
'space:load-doc-timestamps': [
|
||||
{
|
||||
spaceType: string;
|
||||
spaceId: string;
|
||||
timestamp?: number;
|
||||
},
|
||||
Record<string, number>,
|
||||
];
|
||||
'space:load-doc': [
|
||||
{
|
||||
spaceType: string;
|
||||
spaceId: string;
|
||||
docId: string;
|
||||
stateVector?: string;
|
||||
},
|
||||
{
|
||||
missing: string;
|
||||
state: string;
|
||||
timestamp: number;
|
||||
},
|
||||
];
|
||||
'space:delete-doc': { spaceType: string; spaceId: string; docId: string };
|
||||
}
|
||||
|
||||
export type ServerEventsMap = {
|
||||
[Key in keyof ServerEvents]: (data: ServerEvents[Key]) => void;
|
||||
};
|
||||
|
||||
export type ClientEventsMap = {
|
||||
[Key in keyof ClientEvents]: ClientEvents[Key] extends Array<any>
|
||||
? (
|
||||
data: ClientEvents[Key][0],
|
||||
ack: (res: WebsocketResponse<ClientEvents[Key][1]>) => void
|
||||
) => void
|
||||
: (data: ClientEvents[Key]) => void;
|
||||
};
|
||||
|
||||
export type Socket = SocketIO<ServerEventsMap, ClientEventsMap>;
|
||||
|
||||
export function uint8ArrayToBase64(array: Uint8Array): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
// Create a blob from the Uint8Array
|
||||
const blob = new Blob([array]);
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
const dataUrl = reader.result as string | null;
|
||||
if (!dataUrl) {
|
||||
resolve('');
|
||||
return;
|
||||
}
|
||||
// The result includes the `data:` URL prefix and the MIME type. We only want the Base64 data
|
||||
const base64 = dataUrl.split(',')[1];
|
||||
resolve(base64);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
export function base64ToUint8Array(base64: string) {
|
||||
const binaryString = atob(base64);
|
||||
const binaryArray = binaryString.split('').map(function (char) {
|
||||
return char.charCodeAt(0);
|
||||
});
|
||||
return new Uint8Array(binaryArray);
|
||||
}
|
||||
|
||||
export class SocketConnection extends Connection<Socket> {
|
||||
manager = new SocketIOManager(this.endpoint, {
|
||||
autoConnect: false,
|
||||
transports: ['websocket'],
|
||||
secure: new URL(this.endpoint).protocol === 'https:',
|
||||
});
|
||||
|
||||
constructor(
|
||||
private readonly endpoint: string,
|
||||
private readonly socketOptions: SocketOptions
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override get shareId() {
|
||||
return `socket:${this.endpoint}`;
|
||||
}
|
||||
|
||||
override async doConnect() {
|
||||
const conn = this.manager.socket('/', this.socketOptions);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
conn.once('connect', () => {
|
||||
resolve();
|
||||
});
|
||||
conn.once('connect_error', err => {
|
||||
reject(err);
|
||||
});
|
||||
conn.open();
|
||||
});
|
||||
|
||||
return conn;
|
||||
}
|
||||
|
||||
override async doDisconnect(conn: Socket) {
|
||||
conn.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Socket connection allow explicitly set status by user
|
||||
*
|
||||
* used when join space failed
|
||||
*/
|
||||
override setStatus(status: ConnectionStatus, error?: Error) {
|
||||
super.setStatus(status, error);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user