Files
nocobase/packages/core/client/src/application/WebSocketClient.ts
T
ChengLei Shao 61e7a89067 refactor: export action (#5665)
* chore: export xlsx command

* chore: container service

* chore: report export progress

* chore: export

* chore: performInsert

* chore: export command

* chore: base exporter

* chore: limit option

* chore: export

* chore: export

* chore: types

* chore: ws socket with auth

* refactor: refactor exporter, change addRows method to handleRow method to support row-by-row processing

* chore: xlsx exporter

* chore: base exporter

* chore: export limit

* chore: import action

* chore: load websocket

* chore: import action

* chore: object to cli args

* chore: import options

* chore: import action

* chore: import runner

* chore: import action

* chore: import options

* chore: import options

* chore: plugin load event

* chore: test

* chore: i18n

* chore: i18n

* chore: i18n

* fix: ws auth status

* chore: cache in data source

* chore: load datasource

* chore: import with field alias

* fix: build

* fix: test

* fix: build

* fix: import schema settings

* fix: import action

* fix: import progress

* chore: template creator

* fix: import error message

* fix: import error message

* chore: test

* chore: workflow dispatch event

* chore: export alias

* fix: typo

* chore: error render

* fix: event error

* chore: send message to tags

* fix: test

* fix: import action setting
2024-12-31 20:16:03 +08:00

203 lines
5.3 KiB
TypeScript

/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { define, observable } from '@formily/reactive';
import { getSubAppName } from '@nocobase/sdk';
import { Application } from './Application';
export type WebSocketClientOptions = {
reconnectInterval?: number;
reconnectAttempts?: number;
pingInterval?: number;
url?: string;
basename?: string;
protocols?: string | string[];
onServerDown?: any;
};
export class WebSocketClient {
protected _ws: WebSocket;
protected _reconnectTimes = 0;
protected events = [];
protected options: WebSocketClientOptions;
app: Application;
enabled: boolean;
connected = false;
serverDown = false;
lastMessage = {};
constructor(options: WebSocketClientOptions | boolean) {
if (!options) {
this.enabled = false;
return;
}
this.options = options === true ? {} : options;
this.enabled = true;
define(this, {
serverDown: observable.ref,
connected: observable.ref,
lastMessage: observable.ref,
});
}
getURL() {
if (!this.app) {
return;
}
const apiBaseURL = this.app.getApiUrl();
if (!apiBaseURL) {
return;
}
const subApp = getSubAppName(this.app.getPublicPath());
const queryString = subApp ? `?__appName=${subApp}` : '';
const wsPath = this.options.basename || '/ws';
if (this.options.url) {
const url = new URL(this.options.url);
if (url.hostname === 'localhost') {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
return `${protocol}://${location.hostname}:${url.port}${wsPath}${queryString}`;
}
return `${this.options.url}${queryString}`;
}
try {
const url = new URL(apiBaseURL);
return `${url.protocol === 'https:' ? 'wss' : 'ws'}://${url.host}${wsPath}${queryString}`;
} catch (error) {
return `${location.protocol === 'https:' ? 'wss' : 'ws'}://${location.host}${wsPath}${queryString}`;
}
}
get reconnectAttempts() {
return this.options?.reconnectAttempts || 30;
}
get reconnectInterval() {
return this.options?.reconnectInterval || 1000;
}
get pingInterval() {
return this.options?.pingInterval || 30000;
}
get readyState() {
if (!this._ws) {
return -1;
}
return this._ws.readyState;
}
connect() {
if (!this.enabled) {
return;
}
if (this._reconnectTimes === 0) {
console.log('[nocobase-ws]: connecting...');
}
if (this._reconnectTimes >= this.reconnectAttempts) {
return;
}
if (this.readyState === WebSocket.OPEN) {
return;
}
this._reconnectTimes++;
const ws = new WebSocket(this.getURL(), this.options.protocols);
let pingIntervalTimer: any;
ws.onopen = () => {
console.log('[nocobase-ws]: connected.');
this.serverDown = false;
if (this._ws) {
this.removeAllListeners();
}
this._reconnectTimes = 0;
this._ws = ws;
for (const { type, listener, options } of this.events) {
this._ws.addEventListener(type, listener, options);
}
pingIntervalTimer = setInterval(() => this.send('ping'), this.pingInterval);
this.connected = true;
this.emit('open', {});
};
ws.onerror = async () => {
// setTimeout(() => this.connect(), this.reconnectInterval);
console.log('onerror', this.readyState, this._reconnectTimes);
};
ws.onclose = async (event) => {
setTimeout(() => this.connect(), this.reconnectInterval);
console.log('onclose', this.readyState, this._reconnectTimes, this.serverDown);
this.connected = false;
clearInterval(pingIntervalTimer);
// if (this._reconnectTimes >= Math.min(this.reconnectAttempts, 5)) {
// this.serverDown = true;
// this.emit('serverDown', event);
// }
};
}
reconnect() {
this._reconnectTimes = 0;
this.connect();
}
close() {
if (!this._ws) {
return;
}
this._reconnectTimes = this.reconnectAttempts;
return this._ws.close();
}
send(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
if (!this._ws) {
return;
}
return this._ws.send(data);
}
on(type: string, listener: any, options?: boolean | AddEventListenerOptions) {
this.events.push({ type, listener, options });
if (!this._ws) {
return;
}
this._ws.addEventListener(type, listener, options);
}
emit(type: string, args: any) {
for (const event of this.events) {
if (event.type === type) {
event.listener(args);
}
}
}
off(type: string, listener: any, options?: boolean | EventListenerOptions) {
let index = 0;
for (const event of this.events) {
if (event.type === type && event.listener === listener) {
this.events.splice(index, 1);
break;
}
index++;
}
if (!this._ws) {
return;
}
this._ws.removeEventListener(type, listener, options);
}
removeAllListeners() {
if (!this._ws) {
return;
}
for (const { type, listener, options } of this.events) {
this._ws.removeEventListener(type, listener, options);
}
}
}