mirror of
https://github.com/dream-num/univer.git
synced 2026-10-02 04:24:34 +08:00
feat(rpc): init network package (#485)
* feat(rpc): init network package * chore: update pnpm lock
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# @univerjs/network
|
||||
|
||||
## Introduction
|
||||
|
||||
This plugin provides network services to other modules of Univer.
|
||||
|
||||
## Usage
|
||||
|
||||
### Installation
|
||||
|
||||
```shell
|
||||
npm i @univerjs/network
|
||||
```
|
||||
|
||||
### API
|
||||
|
||||
Check [Univer](https://github.com/dream-num/univer/)
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "@univerjs/network",
|
||||
"private": true,
|
||||
"main": "./src/index.ts",
|
||||
"module": "./src/index.ts",
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@wendellhu/redi": "^0.12.10",
|
||||
"rxjs": "^7.8.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export { HTTPHeaders } from './services/http/headers';
|
||||
export { HTTPService } from './services/http/http.service';
|
||||
export { XHRHTTPImplementation } from './services/http/implementations/xhr';
|
||||
export { HTTPRequest } from './services/http/request';
|
||||
export { HTTPResponse } from './services/http/response';
|
||||
export { type ISocket, ISocketService, WebSocketService } from './services/web-socket/web-socket.service';
|
||||
@@ -0,0 +1,43 @@
|
||||
interface IHeadersConstructorProps {
|
||||
[key: string]: string | number | boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* It wraps headers of HTTP requests' and responses' headers.
|
||||
*/
|
||||
export class HTTPHeaders {
|
||||
private readonly _headers: { [key: string]: string[] } = {};
|
||||
|
||||
constructor(headers?: IHeadersConstructorProps | string) {
|
||||
if (typeof headers === 'string') {
|
||||
// split header text and serialize them to HTTPHeaders
|
||||
headers.split('\n').forEach((header) => {
|
||||
const [name, value] = header.split(':');
|
||||
if (name && value) {
|
||||
this._setHeader(name, value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (headers) {
|
||||
Object.keys(headers).forEach(([name, value]) => {
|
||||
this._setHeader(name, value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
forEach(callback: (name: string, value: string[]) => void): void {
|
||||
Object.keys(this._headers).forEach((name) => {
|
||||
callback(name, this._headers[name]);
|
||||
});
|
||||
}
|
||||
|
||||
private _setHeader(name: string, value: string | number | boolean): void {
|
||||
const lowerCase = name.toLowerCase();
|
||||
if (this._headers[lowerCase]) {
|
||||
this._headers[lowerCase].push(value.toString());
|
||||
} else {
|
||||
this._headers[lowerCase] = [value.toString()];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { Disposable, Nullable, remove, toDisposable } from '@univerjs/core';
|
||||
import { IDisposable } from '@wendellhu/redi';
|
||||
import { firstValueFrom, Observable, of } from 'rxjs';
|
||||
import { concatMap } from 'rxjs/operators';
|
||||
|
||||
import { HTTPHeaders } from './headers';
|
||||
import { HTTPResponseType } from './http';
|
||||
import { IHTTPImplementation } from './implementations/implementation';
|
||||
import { HTTPParams } from './params';
|
||||
import { HTTPRequest, HTTPRequestMethod } from './request';
|
||||
import { HTTPEvent, HTTPResponse, HTTPResponseError } from './response';
|
||||
|
||||
// TODO: error handling of HTTPService should be strengthened.
|
||||
|
||||
export interface IRequestParams {
|
||||
/** Query params. These params would be append to the url before the request is sent. */
|
||||
params?: { [param: string]: string | number | boolean };
|
||||
headers?: { [key: string]: string | number | boolean };
|
||||
responseType?: HTTPResponseType;
|
||||
withCredentials?: boolean;
|
||||
}
|
||||
|
||||
export interface IPostRequestParams extends IRequestParams {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
body?: any;
|
||||
}
|
||||
|
||||
type HTTPHandlerFn = (request: HTTPRequest) => Observable<HTTPEvent<unknown>>;
|
||||
type HTTPInterceptorFn = (request: HTTPRequest, next: HTTPHandlerFn) => Observable<HTTPEvent<unknown>>;
|
||||
type RequestPipe<T> = (req: HTTPRequest, finalHandlerFn: HTTPHandlerFn) => Observable<HTTPEvent<T>>;
|
||||
|
||||
/**
|
||||
* Register an HTTP interceptor. Interceptor could modify requests, responses, headers or errors.
|
||||
*/
|
||||
export interface IHTTPInterceptor {
|
||||
priority?: number;
|
||||
interceptor: HTTPInterceptorFn;
|
||||
}
|
||||
|
||||
export class HTTPService extends Disposable {
|
||||
private _interceptors: IHTTPInterceptor[] = [];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private _pipe: Nullable<RequestPipe<any>>;
|
||||
|
||||
constructor(@IHTTPImplementation private readonly _http: IHTTPImplementation) {
|
||||
super();
|
||||
}
|
||||
|
||||
registerHTTPInterceptor(interceptor: IHTTPInterceptor): IDisposable {
|
||||
if (this._interceptors.indexOf(interceptor) !== -1) {
|
||||
throw new Error('[HTTPService]: The interceptor has already been registered!');
|
||||
}
|
||||
|
||||
this._interceptors.push(interceptor);
|
||||
this._interceptors = this._interceptors.sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
|
||||
|
||||
this._pipe = null;
|
||||
|
||||
return toDisposable(() => remove(this._interceptors, interceptor));
|
||||
}
|
||||
|
||||
get<T>(url: string, options?: IRequestParams): Promise<HTTPResponse<T>> {
|
||||
return this._request<T>('GET', url, options);
|
||||
}
|
||||
|
||||
post<T>(url: string, options?: IPostRequestParams): Promise<HTTPResponse<T>> {
|
||||
return this._request<T>('POST', url, options);
|
||||
}
|
||||
|
||||
put<T>(url: string, options?: IPostRequestParams): Promise<HTTPResponse<T>> {
|
||||
return this._request<T>('PUT', url, options);
|
||||
}
|
||||
|
||||
delete<T>(url: string, options?: IRequestParams): Promise<HTTPResponse<T>> {
|
||||
return this._request<T>('DELETE', url, options);
|
||||
}
|
||||
|
||||
/** The HTTP request implementations */
|
||||
private async _request<T>(
|
||||
method: HTTPRequestMethod,
|
||||
url: string,
|
||||
options?: IRequestParams
|
||||
): Promise<HTTPResponse<T>> {
|
||||
// Things to do when sending a HTTP request:
|
||||
// 1. Generate HTTPRequest/HTTPHeader object
|
||||
// 2. Call interceptors and finally the HTTP implementation.
|
||||
const headers = new HTTPHeaders(options?.headers);
|
||||
const params = new HTTPParams(options?.params);
|
||||
const request = new HTTPRequest(method, url, {
|
||||
headers,
|
||||
params,
|
||||
withCredentials: options?.withCredentials ?? false, // default value for withCredentials is false by MDN
|
||||
responseType: options?.responseType ?? 'json',
|
||||
});
|
||||
|
||||
const events$: Observable<HTTPEvent<any>> = of(request).pipe(
|
||||
concatMap((request) => this._runInterceptorsAndImplementation(request))
|
||||
);
|
||||
|
||||
// The event$ may emit multiple values, but we only care about the first one.
|
||||
// We may need to care about other events (especially progress events) in the future.
|
||||
const result = await firstValueFrom(events$);
|
||||
if (result instanceof HTTPResponse) {
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new Error(`${(result as HTTPResponseError).error}`);
|
||||
}
|
||||
|
||||
private _runInterceptorsAndImplementation(request: HTTPRequest): Observable<HTTPEvent<any>> {
|
||||
// In this method we first call all interceptors and finally the HTTP implementation.
|
||||
// And the HTTP response will be passed back through the interceptor chain.
|
||||
if (!this._pipe) {
|
||||
this._pipe = this._interceptors
|
||||
.map((handler) => handler.interceptor)
|
||||
.reduceRight(
|
||||
(nextHandlerFunction, interceptorFunction: HTTPInterceptorFn) =>
|
||||
chainInterceptorFn(nextHandlerFunction, interceptorFunction),
|
||||
(requestFromPrevInterceptor, finalHandler) => finalHandler(requestFromPrevInterceptor)
|
||||
);
|
||||
}
|
||||
|
||||
return this._pipe!(request, (requestToNext) => this._http.send(requestToNext) /* final handler */);
|
||||
}
|
||||
}
|
||||
|
||||
function chainInterceptorFn(afterInterceptorChain: HTTPInterceptorFn, currentInterceptorFn: HTTPInterceptorFn) {
|
||||
return (prevRequest: HTTPRequest, nextHandlerFn: HTTPHandlerFn) =>
|
||||
currentInterceptorFn(prevRequest, (nextRequest) => afterInterceptorChain(nextRequest, nextHandlerFn));
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/* eslint-disable no-magic-numbers */
|
||||
|
||||
export type HTTPResponseType = 'arraybuffer' | 'blob' | 'json' | 'text';
|
||||
|
||||
export const SuccessStatusCodeLowerBound = 200;
|
||||
|
||||
export const ErrorStatusCodeLowerBound = 300;
|
||||
|
||||
/**
|
||||
* Http status codes.
|
||||
*
|
||||
* https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
*/
|
||||
export enum HTTPStatusCode {
|
||||
Continue = 100,
|
||||
SwitchingProtocols = 101,
|
||||
Processing = 102,
|
||||
EarlyHints = 103,
|
||||
|
||||
Ok = 200,
|
||||
Created = 201,
|
||||
Accepted = 202,
|
||||
NonAuthoritativeInformation = 203,
|
||||
NoContent = 204,
|
||||
ResetContent = 205,
|
||||
PartialContent = 206,
|
||||
MultiStatus = 207,
|
||||
AlreadyReported = 208,
|
||||
ImUsed = 226,
|
||||
|
||||
MultipleChoices = 300,
|
||||
MovedPermanently = 301,
|
||||
Found = 302,
|
||||
SeeOther = 303,
|
||||
NotModified = 304,
|
||||
UseProxy = 305,
|
||||
Unused = 306,
|
||||
TemporaryRedirect = 307,
|
||||
PermanentRedirect = 308,
|
||||
|
||||
BadRequest = 400,
|
||||
Unauthorized = 401,
|
||||
PaymentRequired = 402,
|
||||
Forbidden = 403,
|
||||
NotFound = 404,
|
||||
MethodNotAllowed = 405,
|
||||
NotAcceptable = 406,
|
||||
ProxyAuthenticationRequired = 407,
|
||||
RequestTimeout = 408,
|
||||
Conflict = 409,
|
||||
Gone = 410,
|
||||
LengthRequired = 411,
|
||||
PreconditionFailed = 412,
|
||||
PayloadTooLarge = 413,
|
||||
UriTooLong = 414,
|
||||
UnsupportedMediaType = 415,
|
||||
RangeNotSatisfiable = 416,
|
||||
ExpectationFailed = 417,
|
||||
ImATeapot = 418,
|
||||
MisdirectedRequest = 421,
|
||||
UnprocessableEntity = 422,
|
||||
Locked = 423,
|
||||
FailedDependency = 424,
|
||||
TooEarly = 425,
|
||||
UpgradeRequired = 426,
|
||||
PreconditionRequired = 428,
|
||||
TooManyRequests = 429,
|
||||
RequestHeaderFieldsTooLarge = 431,
|
||||
UnavailableForLegalReasons = 451,
|
||||
|
||||
InternalServerError = 500,
|
||||
NotImplemented = 501,
|
||||
BadGateway = 502,
|
||||
ServiceUnavailable = 503,
|
||||
GatewayTimeout = 504,
|
||||
HttpVersionNotSupported = 505,
|
||||
VariantAlsoNegotiates = 506,
|
||||
InsufficientStorage = 507,
|
||||
LoopDetected = 508,
|
||||
NotExtended = 510,
|
||||
NetworkAuthenticationRequired = 511,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// TODO
|
||||
@@ -0,0 +1,16 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { createIdentifier } from '@wendellhu/redi';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { HTTPRequest } from '../request';
|
||||
import { HTTPEvent } from '../response';
|
||||
|
||||
/**
|
||||
* HTTP service could be implemented differently on platforms.
|
||||
*/
|
||||
export interface IHTTPImplementation {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
// There may be stream response so the return value is an observable.
|
||||
send(request: HTTPRequest): Observable<HTTPEvent<any>>;
|
||||
}
|
||||
export const IHTTPImplementation = createIdentifier<IHTTPImplementation>('univer-pro.network.http-implementation');
|
||||
@@ -0,0 +1,123 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { Nullable } from '@univerjs/core';
|
||||
import { Observable, Observer } from 'rxjs';
|
||||
|
||||
import { HTTPHeaders } from '../headers';
|
||||
import { ErrorStatusCodeLowerBound, HTTPStatusCode, SuccessStatusCodeLowerBound } from '../http';
|
||||
import { HTTPRequest } from '../request';
|
||||
import { HTTPEvent, HTTPResponse, HTTPResponseError, ResponseHeader } from '../response';
|
||||
import { IHTTPImplementation } from './implementation';
|
||||
|
||||
/**
|
||||
* A HTTP implementation using XHR. XHR could only be async.
|
||||
*/
|
||||
export class XHRHTTPImplementation implements IHTTPImplementation {
|
||||
send(request: HTTPRequest): Observable<HTTPEvent<any>> {
|
||||
return new Observable((observer: Observer<HTTPEvent<any>>) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(request.method, request.getUrlWithParams());
|
||||
if (request.withCredentials) {
|
||||
xhr.withCredentials = true;
|
||||
}
|
||||
|
||||
request.headers.forEach((key, value) => xhr.setRequestHeader(key, value.join(',')));
|
||||
|
||||
let responseHeader: Nullable<ResponseHeader>;
|
||||
|
||||
const buildResponseHeader = () => {
|
||||
if (responseHeader) {
|
||||
return responseHeader;
|
||||
}
|
||||
|
||||
const statusText = xhr.statusText || 'OK';
|
||||
const headers = new HTTPHeaders(xhr.getAllResponseHeaders());
|
||||
return new ResponseHeader(headers, xhr.status, statusText);
|
||||
};
|
||||
|
||||
const onLoadHandler = () => {
|
||||
const { headers, statusText, status } = buildResponseHeader();
|
||||
const { responseType } = request;
|
||||
|
||||
let body: any = null;
|
||||
let error: any = null;
|
||||
if (status !== HTTPStatusCode.NoContent) {
|
||||
body = typeof xhr.response === 'undefined' ? xhr.responseText : xhr.response;
|
||||
}
|
||||
|
||||
let success = status >= SuccessStatusCodeLowerBound && status < ErrorStatusCodeLowerBound;
|
||||
|
||||
// Parse response body according to request's `responseType`.
|
||||
// However we will not verify whether the response type is
|
||||
// the same as the request's `responseType`. We will do that
|
||||
// in the `HTTPService` class because the verification should be
|
||||
// the same across different implementations.
|
||||
if (responseType === 'json' && typeof body === 'string') {
|
||||
const originalBody = body;
|
||||
try {
|
||||
body = body ? JSON.parse(body) : null;
|
||||
} catch (e) {
|
||||
// make success as false to emit a HTTPResponseError
|
||||
success = false;
|
||||
// revert our change to the original body
|
||||
body = originalBody;
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
observer.next(
|
||||
new HTTPResponse({
|
||||
body,
|
||||
headers,
|
||||
status,
|
||||
statusText,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
// next http error here
|
||||
observer.next(
|
||||
new HTTPResponseError({
|
||||
error,
|
||||
headers,
|
||||
status,
|
||||
statusText,
|
||||
})
|
||||
);
|
||||
// Handler server logic error here
|
||||
}
|
||||
};
|
||||
|
||||
const onErrorHandler = (error: ProgressEvent) => {
|
||||
const res = new HTTPResponseError({
|
||||
error,
|
||||
status: xhr.status || 0,
|
||||
statusText: xhr.statusText || 'Unknown Error',
|
||||
headers: buildResponseHeader().headers,
|
||||
});
|
||||
|
||||
observer.next(res);
|
||||
};
|
||||
|
||||
xhr.addEventListener('load', onLoadHandler);
|
||||
xhr.addEventListener('error', onErrorHandler);
|
||||
xhr.addEventListener('abort', onErrorHandler);
|
||||
xhr.addEventListener('timeout', onErrorHandler);
|
||||
|
||||
const body = request.getBody();
|
||||
xhr.send(body);
|
||||
|
||||
// Abort the request if the subscription is disposed before the request completes.
|
||||
return () => {
|
||||
if (xhr.readyState !== xhr.DONE) {
|
||||
xhr.abort();
|
||||
}
|
||||
|
||||
xhr.removeEventListener('load', onLoadHandler);
|
||||
xhr.removeEventListener('error', onErrorHandler);
|
||||
xhr.removeEventListener('abort', onErrorHandler);
|
||||
xhr.removeEventListener('timeout', onErrorHandler);
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export class HTTPParams {
|
||||
constructor(readonly params?: { [key: string]: string | number | boolean }) {}
|
||||
|
||||
toString(): string {
|
||||
if (!this.params) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return Object.keys(this.params)
|
||||
.map((key) => `${key}=${this.params![key]}`)
|
||||
.join('&');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { HTTPHeaders } from './headers';
|
||||
import { HTTPResponseType } from './http';
|
||||
import { HTTPParams } from './params';
|
||||
|
||||
export type HTTPRequestMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
||||
|
||||
export interface IHTTPRequestParams {
|
||||
body?: any;
|
||||
headers: HTTPHeaders;
|
||||
params?: HTTPParams;
|
||||
responseType: HTTPResponseType;
|
||||
withCredentials: boolean;
|
||||
}
|
||||
|
||||
export class HTTPRequest {
|
||||
get headers(): HTTPHeaders {
|
||||
return this.requestParams!.headers;
|
||||
}
|
||||
|
||||
get withCredentials(): boolean {
|
||||
return this.requestParams!.withCredentials;
|
||||
}
|
||||
|
||||
get responseType(): string {
|
||||
return this.requestParams!.responseType;
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly method: HTTPRequestMethod,
|
||||
readonly url: string,
|
||||
readonly requestParams?: IHTTPRequestParams
|
||||
) {
|
||||
// TODO@wzhudev: deal with `requestParams` is empty.
|
||||
}
|
||||
|
||||
getUrlWithParams(): string {
|
||||
const params = this.requestParams?.params?.toString();
|
||||
if (!params) {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
return `${this.url}${this.url.includes('?') ? '&' : '?'}${params}`;
|
||||
}
|
||||
|
||||
getBody(): string | null {
|
||||
if (!this.requestParams?.body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { HTTPHeaders } from './headers';
|
||||
|
||||
/**
|
||||
* There are multiple events could be resolved from the HTTP server.
|
||||
*/
|
||||
export type HTTPEvent<T> = HTTPResponse<T> | HTTPResponseError;
|
||||
|
||||
/** Wraps (success) response info. */
|
||||
export class HTTPResponse<T> {
|
||||
readonly body: T;
|
||||
readonly headers: HTTPHeaders;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
|
||||
constructor({
|
||||
body,
|
||||
headers,
|
||||
status,
|
||||
statusText,
|
||||
}: {
|
||||
body: T;
|
||||
headers: HTTPHeaders;
|
||||
status: number;
|
||||
statusText: string;
|
||||
}) {
|
||||
this.body = body;
|
||||
this.headers = headers;
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
}
|
||||
}
|
||||
|
||||
export class HTTPResponseError {
|
||||
readonly headers: HTTPHeaders;
|
||||
readonly status: number;
|
||||
readonly statusText: string;
|
||||
readonly error: any;
|
||||
|
||||
constructor({
|
||||
headers,
|
||||
status,
|
||||
statusText,
|
||||
error,
|
||||
}: {
|
||||
headers: HTTPHeaders;
|
||||
status: number;
|
||||
statusText: string;
|
||||
error: any;
|
||||
}) {
|
||||
this.headers = headers;
|
||||
this.status = status;
|
||||
this.statusText = statusText;
|
||||
this.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
export class ResponseHeader {
|
||||
constructor(
|
||||
readonly headers: HTTPHeaders,
|
||||
readonly status: number,
|
||||
readonly statusText: string
|
||||
) {}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Disposable, DisposableCollection, Nullable, toDisposable } from '@univerjs/core';
|
||||
import { createIdentifier } from '@wendellhu/redi';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
/**
|
||||
* This service is responsible for establishing bidi-directional connection to a remote server.
|
||||
*/
|
||||
export const ISocketService = createIdentifier<ISocketService>('univer.socket');
|
||||
export interface ISocketService {
|
||||
createSocket(url: string): Nullable<ISocket>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface that represents a socket connection.
|
||||
*/
|
||||
export interface ISocket {
|
||||
URL: string;
|
||||
|
||||
close(code?: number, reason?: string): void;
|
||||
|
||||
// TODO: Data type can support plain object as a type.
|
||||
|
||||
/**
|
||||
* Send a message to the remote server.
|
||||
*/
|
||||
send(data: string | ArrayBufferLike | Blob | ArrayBufferView): void;
|
||||
|
||||
close$: Observable<[ISocket, CloseEvent]>;
|
||||
error$: Observable<[ISocket, Event]>;
|
||||
message$: Observable<[ISocket, MessageEvent]>;
|
||||
open$: Observable<[ISocket, Event]>;
|
||||
}
|
||||
|
||||
/**
|
||||
* This service create a WebSocket connection to a remote server.
|
||||
*/
|
||||
export class WebSocketService extends Disposable implements ISocketService {
|
||||
createSocket(URL: string): Nullable<ISocket> {
|
||||
try {
|
||||
const connection = new WebSocket(URL);
|
||||
|
||||
const disposables = new DisposableCollection();
|
||||
const webSocket: ISocket = {
|
||||
URL,
|
||||
close: (code?: number, reason?: string) => {
|
||||
connection.close(code, reason);
|
||||
disposables.dispose();
|
||||
},
|
||||
send: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => {
|
||||
connection.send(data);
|
||||
},
|
||||
open$: new Observable((subscriber) => {
|
||||
const callback = (event: Event) => subscriber.next([webSocket, event]);
|
||||
connection.addEventListener('open', callback);
|
||||
disposables.add(toDisposable(() => connection.removeEventListener('open', callback)));
|
||||
}),
|
||||
close$: new Observable((subscriber) => {
|
||||
const callback = (event: CloseEvent) => subscriber.next([webSocket, event]);
|
||||
connection.addEventListener('close', callback);
|
||||
disposables.add(toDisposable(() => connection.removeEventListener('close', callback)));
|
||||
}),
|
||||
error$: new Observable((subscriber) => {
|
||||
const callback = (event: Event) => subscriber.next([webSocket, event]);
|
||||
connection.addEventListener('error', callback);
|
||||
disposables.add(toDisposable(() => connection.removeEventListener('error', callback)));
|
||||
}),
|
||||
message$: new Observable((subscriber) => {
|
||||
const callback = (event: MessageEvent) => subscriber.next([webSocket, event]);
|
||||
connection.addEventListener('message', callback);
|
||||
disposables.add(toDisposable(() => connection.removeEventListener('message', callback)));
|
||||
}),
|
||||
};
|
||||
|
||||
return webSocket;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+12
@@ -806,6 +806,18 @@ importers:
|
||||
specifier: ^0.34.6
|
||||
version: 0.34.6(happy-dom@12.10.3)(less@4.2.0)
|
||||
|
||||
packages/network:
|
||||
dependencies:
|
||||
'@univerjs/core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@wendellhu/redi':
|
||||
specifier: ^0.12.10
|
||||
version: 0.12.11
|
||||
rxjs:
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.1
|
||||
|
||||
packages/rpc:
|
||||
dependencies:
|
||||
'@univerjs/core':
|
||||
|
||||
Reference in New Issue
Block a user