重新设计了subscriber和socket
This commit is contained in:
parent
a0a25d8445
commit
4a94777066
|
|
@ -2,12 +2,12 @@ import { ED, OakAbsAttrDef } from '../../types/AbstractComponent';
|
|||
import { ReactComponentProps } from '../../types/Page';
|
||||
import { Breakpoint } from 'antd';
|
||||
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
|
||||
column?: number | Record<Breakpoint, number> | undefined;
|
||||
column?: number | Record<Breakpoint, number>;
|
||||
entity: T2;
|
||||
attributes: OakAbsAttrDef[];
|
||||
data: Partial<ED2[T2]["Schema"]>;
|
||||
title?: string | undefined;
|
||||
bordered?: boolean | undefined;
|
||||
layout?: "horizontal" | "vertical" | undefined;
|
||||
data: Partial<ED2[T2]['Schema']>;
|
||||
title?: string;
|
||||
bordered?: boolean;
|
||||
layout?: 'horizontal' | 'vertical';
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { ReactComponentProps } from '../../types/Page';
|
|||
import { ECode } from '../../types/ErrorPage';
|
||||
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
|
||||
code: ECode;
|
||||
title?: string | undefined;
|
||||
desc?: string | undefined;
|
||||
title?: string;
|
||||
desc?: string;
|
||||
children?: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
}>) => React.ReactElement;
|
||||
|
|
|
|||
|
|
@ -10,18 +10,13 @@ declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactCompo
|
|||
data: RowWithActions<ED2, T2>[];
|
||||
loading: boolean;
|
||||
tablePagination?: React.ReactNode | false;
|
||||
rowSelection?: import("antd/es/table/interface").TableRowSelection<RowWithActions<ED2, T2>> | undefined;
|
||||
hideHeader?: boolean | undefined;
|
||||
disableSerialNumber?: boolean | undefined;
|
||||
size?: "small" | "large" | "middle" | undefined;
|
||||
scroll?: ({
|
||||
x?: string | number | true | undefined;
|
||||
y?: string | number | undefined;
|
||||
} & {
|
||||
scrollToFirstRowOnChange?: boolean | undefined;
|
||||
}) | undefined;
|
||||
rowSelection?: TableProps<RowWithActions<ED2, T2>>['rowSelection'];
|
||||
hideHeader?: boolean;
|
||||
disableSerialNumber?: boolean;
|
||||
size?: 'large' | 'middle' | 'small';
|
||||
scroll?: TableProps<RowWithActions<ED2, T2>>['scroll'];
|
||||
empty?: React.ReactNode;
|
||||
opWidth?: number | undefined;
|
||||
ellipsis?: boolean | undefined;
|
||||
opWidth?: number;
|
||||
ellipsis?: boolean;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,21 +1,20 @@
|
|||
/// <reference types="react" />
|
||||
import { ReactComponentProps } from '../../types/Page';
|
||||
import { ED } from '../../types/AbstractComponent';
|
||||
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
|
||||
style?: import("react").CSSProperties | undefined;
|
||||
className?: string | undefined;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
title?: React.ReactNode;
|
||||
showBack?: boolean | undefined;
|
||||
onBack?: (() => void) | undefined;
|
||||
showBack?: boolean;
|
||||
onBack?: () => void;
|
||||
backIcon?: React.ReactNode;
|
||||
delta?: number | undefined;
|
||||
delta?: number;
|
||||
extra?: React.ReactNode;
|
||||
subTitle?: React.ReactNode;
|
||||
contentMargin?: boolean | undefined;
|
||||
contentStyle?: import("react").CSSProperties | undefined;
|
||||
contentClassName?: string | undefined;
|
||||
contentMargin?: boolean;
|
||||
contentStyle?: React.CSSProperties;
|
||||
contentClassName?: string;
|
||||
tags?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
showHeader?: boolean | undefined;
|
||||
showHeader?: boolean;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
/// <reference types="react" />
|
||||
import { ReactComponentProps } from '../../types/Page';
|
||||
import { ED } from '../../types/AbstractComponent';
|
||||
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
|
||||
style?: import("react").CSSProperties | undefined;
|
||||
className?: string | undefined;
|
||||
showHeader?: boolean | undefined;
|
||||
showBack?: boolean | undefined;
|
||||
onBack?: (() => void) | undefined;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
showHeader?: boolean;
|
||||
showBack?: boolean;
|
||||
onBack?: () => void;
|
||||
backIcon?: React.ReactNode;
|
||||
delta?: number | undefined;
|
||||
delta?: number;
|
||||
title?: React.ReactNode;
|
||||
subTitle?: React.ReactNode;
|
||||
tags?: React.ReactNode;
|
||||
extra?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
content: React.ReactNode;
|
||||
contentStyle?: import("react").CSSProperties | undefined;
|
||||
contentClassName?: string | undefined;
|
||||
bodyStyle?: import("react").CSSProperties | undefined;
|
||||
bodyClassName?: string | undefined;
|
||||
contentStyle?: React.CSSProperties;
|
||||
contentClassName?: string;
|
||||
bodyStyle?: React.CSSProperties;
|
||||
bodyClassName?: string;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
/// <reference types="react" />
|
||||
import { ReactComponentProps } from '../../types/Page';
|
||||
import { ED } from '../../types/AbstractComponent';
|
||||
declare const _default: <ED2 extends ED, T2 extends keyof ED2>(props: ReactComponentProps<ED2, T2, false, {
|
||||
entity: T2;
|
||||
style?: import("react").CSSProperties | undefined;
|
||||
className?: string | undefined;
|
||||
showQuickJumper?: boolean | undefined;
|
||||
size?: "small" | "default" | undefined;
|
||||
showSizeChanger?: boolean | undefined;
|
||||
showTotal?: ((total: number, range: [number, number]) => React.ReactNode) | undefined;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
showQuickJumper?: boolean;
|
||||
size?: 'default' | 'small';
|
||||
showSizeChanger?: boolean;
|
||||
showTotal?: (total: number, range: [number, number]) => React.ReactNode;
|
||||
}>) => React.ReactElement;
|
||||
export default _default;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ declare const _default: <ED2 extends ED, T2 extends keyof ED2, T3 extends string
|
|||
helps: Record<string, string>;
|
||||
entity: T2;
|
||||
attributes: OakAbsAttrUpsertDef<ED2, T2, T3>[];
|
||||
data: ED2[T2]["Schema"];
|
||||
data: ED2[T2]['Schema'];
|
||||
layout: 'horizontal' | 'vertical';
|
||||
mode: 'default' | 'card';
|
||||
}>) => React.ReactElement;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { EntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { SyncContext, SyncRowStore } from 'oak-domain/lib/store/SyncRowStore';
|
||||
import { SubScriber } from '../features/subscriber';
|
||||
import { SubScriber } from '../features/socket/subscriber';
|
||||
import { Environment } from '../features/environment';
|
||||
import { Navigator } from '../features/navigator';
|
||||
import { BriefEnv } from 'oak-domain/lib/types/Environment';
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ import { CacheStore } from '../cacheStore/CacheStore';
|
|||
import { Navigator } from './navigator';
|
||||
import { Port } from './port';
|
||||
import { Style } from './style';
|
||||
import { SubScriber } from './subscriber';
|
||||
import { SubScriber } from './socket/subscriber';
|
||||
import { Socket } from './socket/socket';
|
||||
import { ContextMenuFactory } from './contextMenuFactory';
|
||||
import { Geo } from './geo';
|
||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||
import { CommonConfiguration, RenderConfiguration } from 'oak-domain/lib/types/Configuration';
|
||||
export declare function initializeStep2<ED extends EntityDict & BaseEntityDict, FrontCxt extends SyncContext<ED>>(features: Pick<BasicFeatures<ED>, 'localStorage' | 'environment' | 'message'>, connector: Connector<ED, FrontCxt>, storageSchema: StorageSchema<ED>, frontendContextBuilder: (store: CacheStore<ED>) => SyncContext<ED>, checkers: Array<Checker<ED, keyof ED, FrontCxt>>, common: CommonConfiguration<ED>, render: RenderConfiguration<ED>): {
|
||||
cache: Cache<ED>;
|
||||
socket: Socket;
|
||||
runningTree: RunningTree<ED>;
|
||||
locales: Locales<ED>;
|
||||
port: Port<ED>;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import { Navigator } from './navigator';
|
|||
import { Port } from './port';
|
||||
// import { RelationAuth } from './relationAuth';
|
||||
import { Style } from './style';
|
||||
import { SubScriber } from './subscriber';
|
||||
import { SubScriber } from './socket/subscriber';
|
||||
import SocketPoint from './socket/socketPoint';
|
||||
import { Socket } from './socket/socket';
|
||||
import { ContextMenuFactory } from './contextMenuFactory';
|
||||
import { Geo } from './geo';
|
||||
export function initializeStep2(features, connector, storageSchema, frontendContextBuilder, checkers, common, render) {
|
||||
|
|
@ -22,10 +24,12 @@ export function initializeStep2(features, connector, storageSchema, frontendCont
|
|||
const style = new Style(render.styleDict);
|
||||
const locales = new Locales(cache, localStorage, environment, 'zh-CN'); // 临时性代码,应由上层传入
|
||||
const contextMenuFactory = new ContextMenuFactory(cache);
|
||||
const subscriber = new SubScriber(cache, message, () => connector.getSubscribePoint());
|
||||
const socketPoint = new SocketPoint(() => connector.getSocketPoint());
|
||||
const subscriber = new SubScriber(cache, message, socketPoint);
|
||||
const socket = new Socket(message, socketPoint);
|
||||
return {
|
||||
cache,
|
||||
// relationAuth,
|
||||
socket,
|
||||
runningTree,
|
||||
locales,
|
||||
port,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { Message } from '../message';
|
||||
import { Feature } from '../../types/Feature';
|
||||
import SocketPoint from './socketPoint';
|
||||
export type SocketEvent = {
|
||||
event: 'connect';
|
||||
} | {
|
||||
event: 'disconnect';
|
||||
} | {
|
||||
event: 'connect_error';
|
||||
reason: any;
|
||||
} | {
|
||||
event: 'data';
|
||||
data: any;
|
||||
};
|
||||
export declare class Socket extends Feature {
|
||||
private socketPoint;
|
||||
private url?;
|
||||
private path?;
|
||||
private io?;
|
||||
private message;
|
||||
constructor(message: Message, socketPoint: SocketPoint);
|
||||
private initSocketPoint;
|
||||
private connectQueue;
|
||||
private ioState;
|
||||
private onConnected;
|
||||
connect(): Promise<unknown>;
|
||||
send(data: any, callback?: Function): Promise<void>;
|
||||
disconnect(): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import { OakSocketConnectException } from 'oak-domain/lib/types/Exception';
|
||||
import { Feature } from '../../types/Feature';
|
||||
import io from '../../utils/socket.io/socket.io';
|
||||
import assert from 'assert';
|
||||
export class Socket extends Feature {
|
||||
socketPoint;
|
||||
url;
|
||||
path;
|
||||
io;
|
||||
message;
|
||||
constructor(message, socketPoint) {
|
||||
super();
|
||||
this.message = message;
|
||||
this.socketPoint = socketPoint;
|
||||
}
|
||||
async initSocketPoint() {
|
||||
try {
|
||||
const { socketUrl, path } = await this.socketPoint.get();
|
||||
this.url = socketUrl;
|
||||
this.path = path;
|
||||
}
|
||||
catch (err) {
|
||||
this.url = undefined;
|
||||
this.path = undefined;
|
||||
throw new OakSocketConnectException();
|
||||
}
|
||||
}
|
||||
connectQueue = [];
|
||||
ioState = 'unconnected';
|
||||
onConnected() {
|
||||
this.io.off('disconnect');
|
||||
this.io.off('data');
|
||||
this.io.on('disconnect', () => {
|
||||
this.publish({
|
||||
event: 'disconnect',
|
||||
});
|
||||
this.ioState = 'unconnected';
|
||||
this.io = undefined;
|
||||
});
|
||||
this.io.on('data', (data) => {
|
||||
this.publish({
|
||||
event: 'data',
|
||||
data,
|
||||
});
|
||||
});
|
||||
}
|
||||
async connect() {
|
||||
if (this.ioState === 'connecting') {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
this.connectQueue.push([resolve, reject]);
|
||||
});
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
assert(this.ioState === 'unconnected');
|
||||
this.ioState = 'connecting';
|
||||
await this.initSocketPoint();
|
||||
this.io = io(this.url, {
|
||||
path: this.path,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
this.io.off('connect');
|
||||
this.io.on('connect', () => {
|
||||
this.publish({
|
||||
event: 'connect',
|
||||
});
|
||||
this.ioState = 'connected';
|
||||
this.connectQueue.forEach(([resolve]) => resolve());
|
||||
this.onConnected();
|
||||
resolve(undefined);
|
||||
});
|
||||
this.io.on('connect_error', (err) => {
|
||||
this.message.setMessage({
|
||||
type: 'error',
|
||||
title: 'connect error',
|
||||
content: err.message,
|
||||
});
|
||||
this.publish({
|
||||
event: 'connect_error',
|
||||
reason: err,
|
||||
});
|
||||
this.ioState = 'unconnected';
|
||||
this.connectQueue.forEach(([resolve, reject]) => reject(err));
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
async send(data, callback) {
|
||||
if (!this.io) {
|
||||
await this.connect();
|
||||
}
|
||||
this.io.emit('data', data, callback);
|
||||
}
|
||||
disconnect() {
|
||||
assert(this.ioState === 'connected');
|
||||
this.io.disconnect();
|
||||
}
|
||||
}
|
||||
;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
type SocketPointInfo = {
|
||||
path: string;
|
||||
subscribeUrl: string;
|
||||
socketUrl: string;
|
||||
};
|
||||
export default class SocketPoint {
|
||||
getSocketPointFn: () => Promise<SocketPointInfo>;
|
||||
private socketPointInfo?;
|
||||
constructor(getSocketPointFn: () => Promise<SocketPointInfo>);
|
||||
init(): Promise<void>;
|
||||
get(): Promise<SocketPointInfo>;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import { OakSocketConnectException } from "oak-domain/lib/types/Exception";
|
||||
export default class SocketPoint {
|
||||
getSocketPointFn;
|
||||
socketPointInfo;
|
||||
constructor(getSocketPointFn) {
|
||||
this.getSocketPointFn = getSocketPointFn;
|
||||
}
|
||||
async init() {
|
||||
try {
|
||||
this.socketPointInfo = await this.getSocketPointFn();
|
||||
}
|
||||
catch (err) {
|
||||
throw new OakSocketConnectException();
|
||||
}
|
||||
}
|
||||
async get() {
|
||||
if (!this.socketPointInfo) {
|
||||
await this.init();
|
||||
}
|
||||
return this.socketPointInfo;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { EntityDict, OpRecord } from 'oak-domain/lib/types';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { Cache } from '../cache';
|
||||
import { Message } from '../message';
|
||||
import { Feature } from '../../types/Feature';
|
||||
import SocketPoint from './socketPoint';
|
||||
type SubscribeEvent = 'connect' | 'disconnect';
|
||||
type Callback<ED extends EntityDict & BaseEntityDict> = (event: string, records: OpRecord<ED>[]) => void;
|
||||
export declare class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature {
|
||||
private cache;
|
||||
private message;
|
||||
private socketPoint;
|
||||
private eventMap;
|
||||
private url?;
|
||||
private path?;
|
||||
private socket?;
|
||||
private halted;
|
||||
private reconnectInterval;
|
||||
private reconnectTimer;
|
||||
private count;
|
||||
private socketState;
|
||||
private eventCallbackMap;
|
||||
constructor(cache: Cache<ED>, message: Message, socketPoint: SocketPoint);
|
||||
on(event: SubscribeEvent, callback: (...data: any) => void): void;
|
||||
off(event: SubscribeEvent, callback: () => void): void;
|
||||
private emit;
|
||||
private initSocketPoint;
|
||||
private connect;
|
||||
sub(events: string[], callback?: Callback<ED>): Promise<() => void>;
|
||||
private unsub;
|
||||
getSubscriberId(): string | undefined;
|
||||
setHalted(halted: boolean): void;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { EntityDict, OpRecord, SubDataDef } from 'oak-domain/lib/types';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { Feature } from '../../types/Feature';
|
||||
type SubscribeEvent = 'connect' | 'disconnect';
|
||||
export declare class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature {
|
||||
private eventCallbackMap;
|
||||
constructor();
|
||||
on(event: SubscribeEvent, callback: () => void): void;
|
||||
off(event: SubscribeEvent, callback: () => void): void;
|
||||
sub(data: SubDataDef<ED, keyof ED>[], callback: (records: OpRecord<ED>[], ids: string[]) => void): Promise<void>;
|
||||
unsub(ids: string[]): Promise<void>;
|
||||
getSubscriberId(): undefined;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
import { pull } from 'oak-domain/lib/utils/lodash';
|
||||
import { Feature } from '../../types/Feature';
|
||||
export class SubScriber extends Feature {
|
||||
eventCallbackMap = {
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
};
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
on(event, callback) {
|
||||
this.eventCallbackMap[event].push(callback);
|
||||
}
|
||||
off(event, callback) {
|
||||
pull(this.eventCallbackMap[event], callback);
|
||||
}
|
||||
async sub(data, callback) {
|
||||
console.log('data subscribe 在dev模式下不起作用');
|
||||
}
|
||||
async unsub(ids) { }
|
||||
getSubscriberId() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
import { assert } from 'oak-domain/lib/utils/assert';
|
||||
import { pull, unset } from 'oak-domain/lib/utils/lodash';
|
||||
import { OakSocketConnectException } from 'oak-domain/lib/types/Exception';
|
||||
import io from '../../utils/socket.io/socket.io';
|
||||
import { Feature } from '../../types/Feature';
|
||||
export class SubScriber extends Feature {
|
||||
cache;
|
||||
message;
|
||||
socketPoint;
|
||||
eventMap = {};
|
||||
url;
|
||||
path;
|
||||
socket;
|
||||
halted;
|
||||
reconnectInterval;
|
||||
reconnectTimer;
|
||||
count;
|
||||
socketState = 'unconnected';
|
||||
eventCallbackMap = {
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
};
|
||||
constructor(cache, message, socketPoint) {
|
||||
super();
|
||||
this.cache = cache;
|
||||
this.message = message;
|
||||
this.socketPoint = socketPoint;
|
||||
this.halted = false;
|
||||
this.reconnectInterval = 10 * 1000; // 每隔10秒重连
|
||||
this.reconnectTimer = undefined;
|
||||
this.count = 0;
|
||||
}
|
||||
on(event, callback) {
|
||||
this.eventCallbackMap[event].push(callback);
|
||||
}
|
||||
off(event, callback) {
|
||||
pull(this.eventCallbackMap[event], callback);
|
||||
}
|
||||
emit(event, ...data) {
|
||||
this.eventCallbackMap[event].forEach((ele) => ele(data));
|
||||
}
|
||||
async initSocketPoint() {
|
||||
try {
|
||||
const { subscribeUrl, path } = await this.socketPoint.get();
|
||||
this.url = subscribeUrl;
|
||||
this.path = path;
|
||||
}
|
||||
catch (err) {
|
||||
this.url = undefined;
|
||||
this.path = undefined;
|
||||
throw new OakSocketConnectException();
|
||||
}
|
||||
}
|
||||
async connect() {
|
||||
if (this.halted) {
|
||||
return;
|
||||
}
|
||||
this.socketState = 'connecting';
|
||||
let optionInited = false;
|
||||
if (!this.url) {
|
||||
await this.initSocketPoint();
|
||||
optionInited = true;
|
||||
this.count = 0;
|
||||
}
|
||||
const url = this.url;
|
||||
const path = this.path;
|
||||
// import { io } from "socket.io-client";
|
||||
// const socket = io('https://example.com/order', {
|
||||
// path: '/my-custom-path/',
|
||||
// });
|
||||
// the Socket instance is attached to the "order" Namespace
|
||||
// the HTTP requests will look like: GET https://example.com/my-custom-path/?EIO=4&transport=polling&t=ML4jUwU
|
||||
// 文档 https://socket.io/docs/v4/client-options/
|
||||
if (!this.socket) {
|
||||
// 理论上有可能出现this.socket初始化时,url和path和这次connect时发生变化,暂时不处理。by Xc
|
||||
this.socket = io(url, {
|
||||
path,
|
||||
});
|
||||
}
|
||||
const socket = this.socket;
|
||||
return new Promise((resolve, reject) => {
|
||||
/**
|
||||
* https://socket.io/zh-CN/docs/v4/client-socket-instance/
|
||||
*/
|
||||
socket.on('connect', async () => {
|
||||
this.socketState = 'connected';
|
||||
this.emit('connect');
|
||||
socket.off('connect');
|
||||
socket.on('disconnect', () => {
|
||||
this.socketState = 'unconnected';
|
||||
this.emit('disconnect');
|
||||
socket.removeAllListeners();
|
||||
if (Object.keys(this.eventMap).length > 0) {
|
||||
this.connect();
|
||||
}
|
||||
});
|
||||
socket.on('data', (opRecords, event) => {
|
||||
const registered = this.eventMap[event];
|
||||
if (registered) {
|
||||
registered.callbacks.forEach((ele) => ele(event, opRecords));
|
||||
}
|
||||
this.cache.sync(opRecords);
|
||||
});
|
||||
socket.on('error', (errString) => {
|
||||
console.error(errString);
|
||||
this.message.setMessage({
|
||||
type: 'error',
|
||||
title: '服务器subscriber抛出异常',
|
||||
content: errString,
|
||||
});
|
||||
});
|
||||
if (Object.keys(this.eventMap).length > 0) {
|
||||
socket.emit('sub', Object.keys(this.eventMap));
|
||||
resolve(undefined);
|
||||
}
|
||||
else {
|
||||
resolve(undefined);
|
||||
socket.disconnect();
|
||||
}
|
||||
});
|
||||
if (!optionInited) {
|
||||
socket.on('connect_error', async (err) => {
|
||||
this.count++;
|
||||
if (this.count > 50) {
|
||||
// 可能socket地址改变了,刷新重连
|
||||
this.url = undefined;
|
||||
}
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
this.socket = undefined;
|
||||
// 清除之前的重连定时器
|
||||
if (this.reconnectTimer) {
|
||||
clearInterval(this.reconnectTimer);
|
||||
}
|
||||
// 根据重连次数设置不同的重连间隔
|
||||
if (this.count <= 10) {
|
||||
this.reconnectInterval = 10 * 1000; // 10秒
|
||||
}
|
||||
else if (this.count <= 20) {
|
||||
this.reconnectInterval = 30 * 1000; // 30秒
|
||||
}
|
||||
else if (this.count <= 30) {
|
||||
this.reconnectInterval = 60 * 1000; // 60秒
|
||||
}
|
||||
else if (this.count <= 40) {
|
||||
this.reconnectInterval = 90 * 1000; // 90秒
|
||||
}
|
||||
else {
|
||||
this.reconnectInterval = 120 * 1000; // 2分钟
|
||||
}
|
||||
// 设置新的定时器
|
||||
this.reconnectTimer = setInterval(async () => {
|
||||
await this.connect();
|
||||
// resolve(undefined);
|
||||
}, this.reconnectInterval);
|
||||
resolve(undefined);
|
||||
});
|
||||
}
|
||||
socket.connect();
|
||||
});
|
||||
}
|
||||
async sub(events, callback) {
|
||||
const newEvents = [];
|
||||
events.forEach((event) => {
|
||||
const registered = this.eventMap[event];
|
||||
if (registered) {
|
||||
if (callback) {
|
||||
registered.callbacks.push(callback);
|
||||
}
|
||||
registered.count++;
|
||||
}
|
||||
else {
|
||||
this.eventMap[event] = {
|
||||
callbacks: callback ? [callback] : [],
|
||||
count: 1,
|
||||
};
|
||||
newEvents.push(event);
|
||||
}
|
||||
;
|
||||
});
|
||||
if (this.socketState === 'unconnected') {
|
||||
await this.connect();
|
||||
}
|
||||
else if (this.socketState === 'connected' && newEvents.length > 0) {
|
||||
await new Promise((resolve, reject) => {
|
||||
// this.socket!.emit('sub', newEvents, (result: string) => {
|
||||
// if (result) {
|
||||
// this.message.setMessage({
|
||||
// type: 'error',
|
||||
// title: 'sub data error',
|
||||
// content: result,
|
||||
// });
|
||||
// reject();
|
||||
// }
|
||||
// else {
|
||||
// resolve(undefined);
|
||||
// }
|
||||
// });
|
||||
// TODO 临时代码 后续结合文档再解决
|
||||
this.socket.emit('sub', newEvents);
|
||||
resolve(undefined);
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
this.unsub(events, callback);
|
||||
};
|
||||
}
|
||||
async unsub(events, callback) {
|
||||
const invalidEvents = [];
|
||||
events.forEach((event) => {
|
||||
const registered = this.eventMap[event];
|
||||
assert(registered);
|
||||
registered.count--;
|
||||
if (registered.count > 0) {
|
||||
if (callback) {
|
||||
pull(registered.callbacks, callback);
|
||||
}
|
||||
}
|
||||
else {
|
||||
invalidEvents.push(event);
|
||||
}
|
||||
});
|
||||
invalidEvents.forEach(event => unset(this.eventMap, event));
|
||||
if (this.socketState === 'connected') {
|
||||
this.socket.emit('unsub', events);
|
||||
if (Object.keys(this.eventMap).length === 0) {
|
||||
this.socket.disconnect();
|
||||
this.socket.removeAllListeners();
|
||||
this.socketState = 'unconnected';
|
||||
}
|
||||
}
|
||||
}
|
||||
getSubscriberId() {
|
||||
if (this.socket) {
|
||||
return this.socket.id;
|
||||
}
|
||||
}
|
||||
setHalted(halted) {
|
||||
this.halted = halted;
|
||||
if (halted === false && Object.keys(this.eventMap).length > 0) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,12 +28,13 @@ export declare function initialize<ED extends EntityDict & BaseEntityDict, Cxt e
|
|||
navigator: import("./features/navigator.web").Navigator;
|
||||
} & {
|
||||
cache: import("./features/cache").Cache<ED>;
|
||||
socket: import("./features/socket/socket").Socket;
|
||||
runningTree: import("./features/runningTree").RunningTree<ED>;
|
||||
locales: import("./features/locales").Locales<ED>;
|
||||
port: import("./features/port").Port<ED>;
|
||||
style: import("./features/style").Style<ED>;
|
||||
geo: import("./features/geo").Geo<ED>;
|
||||
contextMenuFactory: import("./features/contextMenuFactory").ContextMenuFactory<ED>;
|
||||
subscriber: import("./features/subscriber").SubScriber<ED>;
|
||||
subscriber: import("./features/socket/subscriber").SubScriber<ED>;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -49,21 +49,21 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
|
|||
redirectTo<T2_1 extends keyof ED>(options: {
|
||||
url: string;
|
||||
} & OakNavigateToParameters<ED, T2_1>, state?: Record<string, any> | undefined, disableNamespace?: boolean | undefined): Promise<void>;
|
||||
addItem<T extends keyof ED>(data: Omit<ED[T]["CreateSingle"]["data"], "id"> & {
|
||||
addItem<T_1 extends keyof ED>(data: Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
|
||||
id?: string | undefined;
|
||||
}, path?: string | undefined): string;
|
||||
addItems<T_1 extends keyof ED>(data: (Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
|
||||
addItems<T_2 extends keyof ED>(data: (Omit<ED[T_2]["CreateSingle"]["data"], "id"> & {
|
||||
id?: string | undefined;
|
||||
})[], path?: string | undefined): string[];
|
||||
removeItem(id: string, path?: string | undefined): void;
|
||||
removeItems(ids: string[], path?: string | undefined): void;
|
||||
updateItem<T_2 extends keyof ED>(data: ED[T_2]["Update"]["data"], id: string, action?: ED[T_2]["Action"] | undefined, path?: string | undefined): void;
|
||||
updateItems<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], ids: string[], action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
|
||||
updateItem<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], id: string, action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
|
||||
updateItems<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], ids: string[], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
|
||||
recoverItem(id: string, path?: string | undefined): void;
|
||||
recoverItems(ids: string[], path?: string | undefined): void;
|
||||
resetItem(id: string, path?: string | undefined): void;
|
||||
update<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
|
||||
create<T_5 extends keyof ED>(data: Omit<ED[T_5]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
|
||||
update<T_5 extends keyof ED>(data: ED[T_5]["Update"]["data"], action?: ED[T_5]["Action"] | undefined, path?: string | undefined): void;
|
||||
create<T_6 extends keyof ED>(data: Omit<ED[T_6]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
|
||||
remove(path?: string | undefined): void;
|
||||
isCreation(path?: string | undefined): boolean;
|
||||
clean(lsn?: number | undefined, dontPublish?: true | undefined, path?: string | undefined): void;
|
||||
|
|
@ -77,7 +77,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
|
|||
getFreshValue(path?: string | undefined): Partial<import("oak-domain/lib/types").GeneralEntityShape> | Partial<import("oak-domain/lib/types").GeneralEntityShape>[] | undefined;
|
||||
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: CheckerType[] | undefined): boolean | { [A in ED[T2_2]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException<ED>; }[ED[T2_2]["Action"]];
|
||||
tryExecute(path?: string | undefined, action?: string | undefined): boolean | { [A_1 in ED[keyof ED]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException<ED>; }[ED[keyof ED]["Action"]];
|
||||
getOperations<T_6 extends keyof ED>(path?: string | undefined): {
|
||||
getOperations<T_7 extends keyof ED>(path?: string | undefined): {
|
||||
entity: keyof ED;
|
||||
operation: ED[keyof ED]["Operation"];
|
||||
}[] | undefined;
|
||||
|
|
@ -119,10 +119,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
|
|||
componentWillReceiveProps?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextContext: any): void;
|
||||
UNSAFE_componentWillReceiveProps?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextContext: any): void;
|
||||
componentWillUpdate?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextState: Readonly<ComponentData<ED, T, FormedData, TData>>, nextContext: any): void;
|
||||
UNSAFE_componentWillUpdate?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextState: Readonly<ComponentData<ED, T, FormedData, TData>>, nextContext: any): void; /**
|
||||
* 实际中出现了setState后先调willUnmount析构,而callback不被调用的情况,此时willUnmount需要将runningTree上的node正确析构
|
||||
* by Xc 20240806
|
||||
*/
|
||||
UNSAFE_componentWillUpdate?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextState: Readonly<ComponentData<ED, T, FormedData, TData>>, nextContext: any): void;
|
||||
};
|
||||
contextType?: React.Context<any> | undefined;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ export default class DebugConnector<ED extends EntityDict & BaseEntityDict, Cxt
|
|||
parseRequest(): any;
|
||||
serializeResult(): any;
|
||||
serializeException(): any;
|
||||
getSubscribeRouter(): any;
|
||||
getSubscribePointRouter(): any;
|
||||
getSubscribePoint(): any;
|
||||
getSocketPath(): any;
|
||||
getSocketPointRouter(): any;
|
||||
getSocketPoint(): any;
|
||||
getBridgeRouter(): string;
|
||||
makeBridgeUrl(url: string): string;
|
||||
getEndpointRouter(): string;
|
||||
|
|
|
|||
|
|
@ -56,15 +56,15 @@ export default class DebugConnector {
|
|||
throw new Error('not available in debugConnector');
|
||||
return {};
|
||||
}
|
||||
getSubscribeRouter() {
|
||||
getSocketPath() {
|
||||
throw new Error('not available in debugConnector');
|
||||
return {};
|
||||
}
|
||||
getSubscribePointRouter() {
|
||||
getSocketPointRouter() {
|
||||
throw new Error('not available in debugConnector');
|
||||
return {};
|
||||
}
|
||||
getSubscribePoint() {
|
||||
getSocketPoint() {
|
||||
throw new Error('not available in debugConnector');
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { EntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { SyncContext, SyncRowStore } from 'oak-domain/lib/store/SyncRowStore';
|
||||
import { SubScriber } from '../features/subscriber';
|
||||
import { SubScriber } from '../features/socket/subscriber';
|
||||
import { Environment } from '../features/environment';
|
||||
import { Navigator } from '../features/navigator';
|
||||
import { BriefEnv } from 'oak-domain/lib/types/Environment';
|
||||
|
|
|
|||
|
|
@ -13,13 +13,15 @@ import { CacheStore } from '../cacheStore/CacheStore';
|
|||
import { Navigator } from './navigator';
|
||||
import { Port } from './port';
|
||||
import { Style } from './style';
|
||||
import { SubScriber } from './subscriber';
|
||||
import { SubScriber } from './socket/subscriber';
|
||||
import { Socket } from './socket/socket';
|
||||
import { ContextMenuFactory } from './contextMenuFactory';
|
||||
import { Geo } from './geo';
|
||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||
import { CommonConfiguration, RenderConfiguration } from 'oak-domain/lib/types/Configuration';
|
||||
export declare function initializeStep2<ED extends EntityDict & BaseEntityDict, FrontCxt extends SyncContext<ED>>(features: Pick<BasicFeatures<ED>, 'localStorage' | 'environment' | 'message'>, connector: Connector<ED, FrontCxt>, storageSchema: StorageSchema<ED>, frontendContextBuilder: (store: CacheStore<ED>) => SyncContext<ED>, checkers: Array<Checker<ED, keyof ED, FrontCxt>>, common: CommonConfiguration<ED>, render: RenderConfiguration<ED>): {
|
||||
cache: Cache<ED>;
|
||||
socket: Socket;
|
||||
runningTree: RunningTree<ED>;
|
||||
locales: Locales<ED>;
|
||||
port: Port<ED>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.initializeStep1 = exports.initializeStep2 = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const cache_1 = require("./cache");
|
||||
const location_1 = require("./location");
|
||||
const runningTree_1 = require("./runningTree");
|
||||
|
|
@ -13,7 +14,9 @@ const navigator_1 = require("./navigator");
|
|||
const port_1 = require("./port");
|
||||
// import { RelationAuth } from './relationAuth';
|
||||
const style_1 = require("./style");
|
||||
const subscriber_1 = require("./subscriber");
|
||||
const subscriber_1 = require("./socket/subscriber");
|
||||
const socketPoint_1 = tslib_1.__importDefault(require("./socket/socketPoint"));
|
||||
const socket_1 = require("./socket/socket");
|
||||
const contextMenuFactory_1 = require("./contextMenuFactory");
|
||||
const geo_1 = require("./geo");
|
||||
function initializeStep2(features, connector, storageSchema, frontendContextBuilder, checkers, common, render) {
|
||||
|
|
@ -25,10 +28,12 @@ function initializeStep2(features, connector, storageSchema, frontendContextBuil
|
|||
const style = new style_1.Style(render.styleDict);
|
||||
const locales = new locales_1.Locales(cache, localStorage, environment, 'zh-CN'); // 临时性代码,应由上层传入
|
||||
const contextMenuFactory = new contextMenuFactory_1.ContextMenuFactory(cache);
|
||||
const subscriber = new subscriber_1.SubScriber(cache, message, () => connector.getSubscribePoint());
|
||||
const socketPoint = new socketPoint_1.default(() => connector.getSocketPoint());
|
||||
const subscriber = new subscriber_1.SubScriber(cache, message, socketPoint);
|
||||
const socket = new socket_1.Socket(message, socketPoint);
|
||||
return {
|
||||
cache,
|
||||
// relationAuth,
|
||||
socket,
|
||||
runningTree,
|
||||
locales,
|
||||
port,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { Message } from '../message';
|
||||
import { Feature } from '../../types/Feature';
|
||||
import SocketPoint from './socketPoint';
|
||||
export type SocketEvent = {
|
||||
event: 'connect';
|
||||
} | {
|
||||
event: 'disconnect';
|
||||
} | {
|
||||
event: 'connect_error';
|
||||
reason: any;
|
||||
} | {
|
||||
event: 'data';
|
||||
data: any;
|
||||
};
|
||||
export declare class Socket extends Feature {
|
||||
private socketPoint;
|
||||
private url?;
|
||||
private path?;
|
||||
private io?;
|
||||
private message;
|
||||
constructor(message: Message, socketPoint: SocketPoint);
|
||||
private initSocketPoint;
|
||||
private connectQueue;
|
||||
private ioState;
|
||||
private onConnected;
|
||||
connect(): Promise<unknown>;
|
||||
send(data: any, callback?: Function): Promise<void>;
|
||||
disconnect(): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.Socket = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const Exception_1 = require("oak-domain/lib/types/Exception");
|
||||
const Feature_1 = require("../../types/Feature");
|
||||
const socket_io_1 = tslib_1.__importDefault(require("../../utils/socket.io/socket.io"));
|
||||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
class Socket extends Feature_1.Feature {
|
||||
socketPoint;
|
||||
url;
|
||||
path;
|
||||
io;
|
||||
message;
|
||||
constructor(message, socketPoint) {
|
||||
super();
|
||||
this.message = message;
|
||||
this.socketPoint = socketPoint;
|
||||
}
|
||||
async initSocketPoint() {
|
||||
try {
|
||||
const { socketUrl, path } = await this.socketPoint.get();
|
||||
this.url = socketUrl;
|
||||
this.path = path;
|
||||
}
|
||||
catch (err) {
|
||||
this.url = undefined;
|
||||
this.path = undefined;
|
||||
throw new Exception_1.OakSocketConnectException();
|
||||
}
|
||||
}
|
||||
connectQueue = [];
|
||||
ioState = 'unconnected';
|
||||
onConnected() {
|
||||
this.io.off('disconnect');
|
||||
this.io.off('data');
|
||||
this.io.on('disconnect', () => {
|
||||
this.publish({
|
||||
event: 'disconnect',
|
||||
});
|
||||
this.ioState = 'unconnected';
|
||||
this.io = undefined;
|
||||
});
|
||||
this.io.on('data', (data) => {
|
||||
this.publish({
|
||||
event: 'data',
|
||||
data,
|
||||
});
|
||||
});
|
||||
}
|
||||
async connect() {
|
||||
if (this.ioState === 'connecting') {
|
||||
const p = new Promise((resolve, reject) => {
|
||||
this.connectQueue.push([resolve, reject]);
|
||||
});
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
(0, assert_1.default)(this.ioState === 'unconnected');
|
||||
this.ioState = 'connecting';
|
||||
await this.initSocketPoint();
|
||||
this.io = (0, socket_io_1.default)(this.url, {
|
||||
path: this.path,
|
||||
});
|
||||
return new Promise((resolve, reject) => {
|
||||
this.io.off('connect');
|
||||
this.io.on('connect', () => {
|
||||
this.publish({
|
||||
event: 'connect',
|
||||
});
|
||||
this.ioState = 'connected';
|
||||
this.connectQueue.forEach(([resolve]) => resolve());
|
||||
this.onConnected();
|
||||
resolve(undefined);
|
||||
});
|
||||
this.io.on('connect_error', (err) => {
|
||||
this.message.setMessage({
|
||||
type: 'error',
|
||||
title: 'connect error',
|
||||
content: err.message,
|
||||
});
|
||||
this.publish({
|
||||
event: 'connect_error',
|
||||
reason: err,
|
||||
});
|
||||
this.ioState = 'unconnected';
|
||||
this.connectQueue.forEach(([resolve, reject]) => reject(err));
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
async send(data, callback) {
|
||||
if (!this.io) {
|
||||
await this.connect();
|
||||
}
|
||||
this.io.emit('data', data, callback);
|
||||
}
|
||||
disconnect() {
|
||||
(0, assert_1.default)(this.ioState === 'connected');
|
||||
this.io.disconnect();
|
||||
}
|
||||
}
|
||||
exports.Socket = Socket;
|
||||
;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
type SocketPointInfo = {
|
||||
path: string;
|
||||
subscribeUrl: string;
|
||||
socketUrl: string;
|
||||
};
|
||||
export default class SocketPoint {
|
||||
getSocketPointFn: () => Promise<SocketPointInfo>;
|
||||
private socketPointInfo?;
|
||||
constructor(getSocketPointFn: () => Promise<SocketPointInfo>);
|
||||
init(): Promise<void>;
|
||||
get(): Promise<SocketPointInfo>;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Exception_1 = require("oak-domain/lib/types/Exception");
|
||||
class SocketPoint {
|
||||
getSocketPointFn;
|
||||
socketPointInfo;
|
||||
constructor(getSocketPointFn) {
|
||||
this.getSocketPointFn = getSocketPointFn;
|
||||
}
|
||||
async init() {
|
||||
try {
|
||||
this.socketPointInfo = await this.getSocketPointFn();
|
||||
}
|
||||
catch (err) {
|
||||
throw new Exception_1.OakSocketConnectException();
|
||||
}
|
||||
}
|
||||
async get() {
|
||||
if (!this.socketPointInfo) {
|
||||
await this.init();
|
||||
}
|
||||
return this.socketPointInfo;
|
||||
}
|
||||
}
|
||||
exports.default = SocketPoint;
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { EntityDict, OpRecord } from 'oak-domain/lib/types';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { Cache } from '../cache';
|
||||
import { Message } from '../message';
|
||||
import { Feature } from '../../types/Feature';
|
||||
import SocketPoint from './socketPoint';
|
||||
type SubscribeEvent = 'connect' | 'disconnect';
|
||||
type Callback<ED extends EntityDict & BaseEntityDict> = (event: string, records: OpRecord<ED>[]) => void;
|
||||
export declare class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature {
|
||||
private cache;
|
||||
private message;
|
||||
private socketPoint;
|
||||
private eventMap;
|
||||
private url?;
|
||||
private path?;
|
||||
private socket?;
|
||||
private halted;
|
||||
private reconnectInterval;
|
||||
private reconnectTimer;
|
||||
private count;
|
||||
private socketState;
|
||||
private eventCallbackMap;
|
||||
constructor(cache: Cache<ED>, message: Message, socketPoint: SocketPoint);
|
||||
on(event: SubscribeEvent, callback: (...data: any) => void): void;
|
||||
off(event: SubscribeEvent, callback: () => void): void;
|
||||
private emit;
|
||||
private initSocketPoint;
|
||||
private connect;
|
||||
sub(events: string[], callback?: Callback<ED>): Promise<() => void>;
|
||||
private unsub;
|
||||
getSubscriberId(): string | undefined;
|
||||
setHalted(halted: boolean): void;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { EntityDict, OpRecord, SubDataDef } from 'oak-domain/lib/types';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { Feature } from '../../types/Feature';
|
||||
type SubscribeEvent = 'connect' | 'disconnect';
|
||||
export declare class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature {
|
||||
private eventCallbackMap;
|
||||
constructor();
|
||||
on(event: SubscribeEvent, callback: () => void): void;
|
||||
off(event: SubscribeEvent, callback: () => void): void;
|
||||
sub(data: SubDataDef<ED, keyof ED>[], callback: (records: OpRecord<ED>[], ids: string[]) => void): Promise<void>;
|
||||
unsub(ids: string[]): Promise<void>;
|
||||
getSubscriberId(): undefined;
|
||||
}
|
||||
export {};
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SubScriber = void 0;
|
||||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||||
const Feature_1 = require("../../types/Feature");
|
||||
class SubScriber extends Feature_1.Feature {
|
||||
eventCallbackMap = {
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
};
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
on(event, callback) {
|
||||
this.eventCallbackMap[event].push(callback);
|
||||
}
|
||||
off(event, callback) {
|
||||
(0, lodash_1.pull)(this.eventCallbackMap[event], callback);
|
||||
}
|
||||
async sub(data, callback) {
|
||||
console.log('data subscribe 在dev模式下不起作用');
|
||||
}
|
||||
async unsub(ids) { }
|
||||
getSubscriberId() {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
exports.SubScriber = SubScriber;
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.SubScriber = void 0;
|
||||
const tslib_1 = require("tslib");
|
||||
const assert_1 = require("oak-domain/lib/utils/assert");
|
||||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||||
const Exception_1 = require("oak-domain/lib/types/Exception");
|
||||
const socket_io_1 = tslib_1.__importDefault(require("../../utils/socket.io/socket.io"));
|
||||
const Feature_1 = require("../../types/Feature");
|
||||
class SubScriber extends Feature_1.Feature {
|
||||
cache;
|
||||
message;
|
||||
socketPoint;
|
||||
eventMap = {};
|
||||
url;
|
||||
path;
|
||||
socket;
|
||||
halted;
|
||||
reconnectInterval;
|
||||
reconnectTimer;
|
||||
count;
|
||||
socketState = 'unconnected';
|
||||
eventCallbackMap = {
|
||||
connect: [],
|
||||
disconnect: [],
|
||||
};
|
||||
constructor(cache, message, socketPoint) {
|
||||
super();
|
||||
this.cache = cache;
|
||||
this.message = message;
|
||||
this.socketPoint = socketPoint;
|
||||
this.halted = false;
|
||||
this.reconnectInterval = 10 * 1000; // 每隔10秒重连
|
||||
this.reconnectTimer = undefined;
|
||||
this.count = 0;
|
||||
}
|
||||
on(event, callback) {
|
||||
this.eventCallbackMap[event].push(callback);
|
||||
}
|
||||
off(event, callback) {
|
||||
(0, lodash_1.pull)(this.eventCallbackMap[event], callback);
|
||||
}
|
||||
emit(event, ...data) {
|
||||
this.eventCallbackMap[event].forEach((ele) => ele(data));
|
||||
}
|
||||
async initSocketPoint() {
|
||||
try {
|
||||
const { subscribeUrl, path } = await this.socketPoint.get();
|
||||
this.url = subscribeUrl;
|
||||
this.path = path;
|
||||
}
|
||||
catch (err) {
|
||||
this.url = undefined;
|
||||
this.path = undefined;
|
||||
throw new Exception_1.OakSocketConnectException();
|
||||
}
|
||||
}
|
||||
async connect() {
|
||||
if (this.halted) {
|
||||
return;
|
||||
}
|
||||
this.socketState = 'connecting';
|
||||
let optionInited = false;
|
||||
if (!this.url) {
|
||||
await this.initSocketPoint();
|
||||
optionInited = true;
|
||||
this.count = 0;
|
||||
}
|
||||
const url = this.url;
|
||||
const path = this.path;
|
||||
// import { io } from "socket.io-client";
|
||||
// const socket = io('https://example.com/order', {
|
||||
// path: '/my-custom-path/',
|
||||
// });
|
||||
// the Socket instance is attached to the "order" Namespace
|
||||
// the HTTP requests will look like: GET https://example.com/my-custom-path/?EIO=4&transport=polling&t=ML4jUwU
|
||||
// 文档 https://socket.io/docs/v4/client-options/
|
||||
if (!this.socket) {
|
||||
// 理论上有可能出现this.socket初始化时,url和path和这次connect时发生变化,暂时不处理。by Xc
|
||||
this.socket = (0, socket_io_1.default)(url, {
|
||||
path,
|
||||
});
|
||||
}
|
||||
const socket = this.socket;
|
||||
return new Promise((resolve, reject) => {
|
||||
/**
|
||||
* https://socket.io/zh-CN/docs/v4/client-socket-instance/
|
||||
*/
|
||||
socket.on('connect', async () => {
|
||||
this.socketState = 'connected';
|
||||
this.emit('connect');
|
||||
socket.off('connect');
|
||||
socket.on('disconnect', () => {
|
||||
this.socketState = 'unconnected';
|
||||
this.emit('disconnect');
|
||||
socket.removeAllListeners();
|
||||
if (Object.keys(this.eventMap).length > 0) {
|
||||
this.connect();
|
||||
}
|
||||
});
|
||||
socket.on('data', (opRecords, event) => {
|
||||
const registered = this.eventMap[event];
|
||||
if (registered) {
|
||||
registered.callbacks.forEach((ele) => ele(event, opRecords));
|
||||
}
|
||||
this.cache.sync(opRecords);
|
||||
});
|
||||
socket.on('error', (errString) => {
|
||||
console.error(errString);
|
||||
this.message.setMessage({
|
||||
type: 'error',
|
||||
title: '服务器subscriber抛出异常',
|
||||
content: errString,
|
||||
});
|
||||
});
|
||||
if (Object.keys(this.eventMap).length > 0) {
|
||||
socket.emit('sub', Object.keys(this.eventMap));
|
||||
resolve(undefined);
|
||||
}
|
||||
else {
|
||||
resolve(undefined);
|
||||
socket.disconnect();
|
||||
}
|
||||
});
|
||||
if (!optionInited) {
|
||||
socket.on('connect_error', async (err) => {
|
||||
this.count++;
|
||||
if (this.count > 50) {
|
||||
// 可能socket地址改变了,刷新重连
|
||||
this.url = undefined;
|
||||
}
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
this.socket = undefined;
|
||||
// 清除之前的重连定时器
|
||||
if (this.reconnectTimer) {
|
||||
clearInterval(this.reconnectTimer);
|
||||
}
|
||||
// 根据重连次数设置不同的重连间隔
|
||||
if (this.count <= 10) {
|
||||
this.reconnectInterval = 10 * 1000; // 10秒
|
||||
}
|
||||
else if (this.count <= 20) {
|
||||
this.reconnectInterval = 30 * 1000; // 30秒
|
||||
}
|
||||
else if (this.count <= 30) {
|
||||
this.reconnectInterval = 60 * 1000; // 60秒
|
||||
}
|
||||
else if (this.count <= 40) {
|
||||
this.reconnectInterval = 90 * 1000; // 90秒
|
||||
}
|
||||
else {
|
||||
this.reconnectInterval = 120 * 1000; // 2分钟
|
||||
}
|
||||
// 设置新的定时器
|
||||
this.reconnectTimer = setInterval(async () => {
|
||||
await this.connect();
|
||||
// resolve(undefined);
|
||||
}, this.reconnectInterval);
|
||||
resolve(undefined);
|
||||
});
|
||||
}
|
||||
socket.connect();
|
||||
});
|
||||
}
|
||||
async sub(events, callback) {
|
||||
const newEvents = [];
|
||||
events.forEach((event) => {
|
||||
const registered = this.eventMap[event];
|
||||
if (registered) {
|
||||
if (callback) {
|
||||
registered.callbacks.push(callback);
|
||||
}
|
||||
registered.count++;
|
||||
}
|
||||
else {
|
||||
this.eventMap[event] = {
|
||||
callbacks: callback ? [callback] : [],
|
||||
count: 1,
|
||||
};
|
||||
newEvents.push(event);
|
||||
}
|
||||
;
|
||||
});
|
||||
if (this.socketState === 'unconnected') {
|
||||
await this.connect();
|
||||
}
|
||||
else if (this.socketState === 'connected' && newEvents.length > 0) {
|
||||
await new Promise((resolve, reject) => {
|
||||
// this.socket!.emit('sub', newEvents, (result: string) => {
|
||||
// if (result) {
|
||||
// this.message.setMessage({
|
||||
// type: 'error',
|
||||
// title: 'sub data error',
|
||||
// content: result,
|
||||
// });
|
||||
// reject();
|
||||
// }
|
||||
// else {
|
||||
// resolve(undefined);
|
||||
// }
|
||||
// });
|
||||
// TODO 临时代码 后续结合文档再解决
|
||||
this.socket.emit('sub', newEvents);
|
||||
resolve(undefined);
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
this.unsub(events, callback);
|
||||
};
|
||||
}
|
||||
async unsub(events, callback) {
|
||||
const invalidEvents = [];
|
||||
events.forEach((event) => {
|
||||
const registered = this.eventMap[event];
|
||||
(0, assert_1.assert)(registered);
|
||||
registered.count--;
|
||||
if (registered.count > 0) {
|
||||
if (callback) {
|
||||
(0, lodash_1.pull)(registered.callbacks, callback);
|
||||
}
|
||||
}
|
||||
else {
|
||||
invalidEvents.push(event);
|
||||
}
|
||||
});
|
||||
invalidEvents.forEach(event => (0, lodash_1.unset)(this.eventMap, event));
|
||||
if (this.socketState === 'connected') {
|
||||
this.socket.emit('unsub', events);
|
||||
if (Object.keys(this.eventMap).length === 0) {
|
||||
this.socket.disconnect();
|
||||
this.socket.removeAllListeners();
|
||||
this.socketState = 'unconnected';
|
||||
}
|
||||
}
|
||||
}
|
||||
getSubscriberId() {
|
||||
if (this.socket) {
|
||||
return this.socket.id;
|
||||
}
|
||||
}
|
||||
setHalted(halted) {
|
||||
this.halted = halted;
|
||||
if (halted === false && Object.keys(this.eventMap).length > 0) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.SubScriber = SubScriber;
|
||||
|
|
@ -28,12 +28,13 @@ export declare function initialize<ED extends EntityDict & BaseEntityDict, Cxt e
|
|||
navigator: import("./features/navigator.web").Navigator;
|
||||
} & {
|
||||
cache: import("./features/cache").Cache<ED>;
|
||||
socket: import("./features/socket/socket").Socket;
|
||||
runningTree: import("./features/runningTree").RunningTree<ED>;
|
||||
locales: import("./features/locales").Locales<ED>;
|
||||
port: import("./features/port").Port<ED>;
|
||||
style: import("./features/style").Style<ED>;
|
||||
geo: import("./features/geo").Geo<ED>;
|
||||
contextMenuFactory: import("./features/contextMenuFactory").ContextMenuFactory<ED>;
|
||||
subscriber: import("./features/subscriber").SubScriber<ED>;
|
||||
subscriber: import("./features/socket/subscriber").SubScriber<ED>;
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -49,21 +49,21 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
|
|||
redirectTo<T2_1 extends keyof ED>(options: {
|
||||
url: string;
|
||||
} & OakNavigateToParameters<ED, T2_1>, state?: Record<string, any> | undefined, disableNamespace?: boolean | undefined): Promise<void>;
|
||||
addItem<T extends keyof ED>(data: Omit<ED[T]["CreateSingle"]["data"], "id"> & {
|
||||
addItem<T_1 extends keyof ED>(data: Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
|
||||
id?: string | undefined;
|
||||
}, path?: string | undefined): string;
|
||||
addItems<T_1 extends keyof ED>(data: (Omit<ED[T_1]["CreateSingle"]["data"], "id"> & {
|
||||
addItems<T_2 extends keyof ED>(data: (Omit<ED[T_2]["CreateSingle"]["data"], "id"> & {
|
||||
id?: string | undefined;
|
||||
})[], path?: string | undefined): string[];
|
||||
removeItem(id: string, path?: string | undefined): void;
|
||||
removeItems(ids: string[], path?: string | undefined): void;
|
||||
updateItem<T_2 extends keyof ED>(data: ED[T_2]["Update"]["data"], id: string, action?: ED[T_2]["Action"] | undefined, path?: string | undefined): void;
|
||||
updateItems<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], ids: string[], action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
|
||||
updateItem<T_3 extends keyof ED>(data: ED[T_3]["Update"]["data"], id: string, action?: ED[T_3]["Action"] | undefined, path?: string | undefined): void;
|
||||
updateItems<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], ids: string[], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
|
||||
recoverItem(id: string, path?: string | undefined): void;
|
||||
recoverItems(ids: string[], path?: string | undefined): void;
|
||||
resetItem(id: string, path?: string | undefined): void;
|
||||
update<T_4 extends keyof ED>(data: ED[T_4]["Update"]["data"], action?: ED[T_4]["Action"] | undefined, path?: string | undefined): void;
|
||||
create<T_5 extends keyof ED>(data: Omit<ED[T_5]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
|
||||
update<T_5 extends keyof ED>(data: ED[T_5]["Update"]["data"], action?: ED[T_5]["Action"] | undefined, path?: string | undefined): void;
|
||||
create<T_6 extends keyof ED>(data: Omit<ED[T_6]["CreateSingle"]["data"], "id">, path?: string | undefined): void;
|
||||
remove(path?: string | undefined): void;
|
||||
isCreation(path?: string | undefined): boolean;
|
||||
clean(lsn?: number | undefined, dontPublish?: true | undefined, path?: string | undefined): void;
|
||||
|
|
@ -77,7 +77,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
|
|||
getFreshValue(path?: string | undefined): Partial<import("oak-domain/lib/types").GeneralEntityShape> | Partial<import("oak-domain/lib/types").GeneralEntityShape>[] | undefined;
|
||||
checkOperation<T2_2 extends keyof ED>(entity: T2_2, operation: Omit<ED[T2_2]["Operation"], "id">, checkerTypes?: CheckerType[] | undefined): boolean | { [A in ED[T2_2]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException<ED>; }[ED[T2_2]["Action"]];
|
||||
tryExecute(path?: string | undefined, action?: string | undefined): boolean | { [A_1 in ED[keyof ED]["Action"]]: boolean | import("oak-domain/lib/types").OakUserException<ED>; }[ED[keyof ED]["Action"]];
|
||||
getOperations<T_6 extends keyof ED>(path?: string | undefined): {
|
||||
getOperations<T_7 extends keyof ED>(path?: string | undefined): {
|
||||
entity: keyof ED;
|
||||
operation: ED[keyof ED]["Operation"];
|
||||
}[] | undefined;
|
||||
|
|
@ -119,10 +119,7 @@ export declare function createComponent<IsList extends boolean, ED extends Entit
|
|||
componentWillReceiveProps?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextContext: any): void;
|
||||
UNSAFE_componentWillReceiveProps?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextContext: any): void;
|
||||
componentWillUpdate?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextState: Readonly<ComponentData<ED, T, FormedData, TData>>, nextContext: any): void;
|
||||
UNSAFE_componentWillUpdate?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextState: Readonly<ComponentData<ED, T, FormedData, TData>>, nextContext: any): void; /**
|
||||
* 实际中出现了setState后先调willUnmount析构,而callback不被调用的情况,此时willUnmount需要将runningTree上的node正确析构
|
||||
* by Xc 20240806
|
||||
*/
|
||||
UNSAFE_componentWillUpdate?(nextProps: Readonly<ComponentProps<ED, T, TProperty>>, nextState: Readonly<ComponentData<ED, T, FormedData, TData>>, nextContext: any): void;
|
||||
};
|
||||
contextType?: React.Context<any> | undefined;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ export default class DebugConnector<ED extends EntityDict & BaseEntityDict, Cxt
|
|||
parseRequest(): any;
|
||||
serializeResult(): any;
|
||||
serializeException(): any;
|
||||
getSubscribeRouter(): any;
|
||||
getSubscribePointRouter(): any;
|
||||
getSubscribePoint(): any;
|
||||
getSocketPath(): any;
|
||||
getSocketPointRouter(): any;
|
||||
getSocketPoint(): any;
|
||||
getBridgeRouter(): string;
|
||||
makeBridgeUrl(url: string): string;
|
||||
getEndpointRouter(): string;
|
||||
|
|
|
|||
|
|
@ -59,15 +59,15 @@ class DebugConnector {
|
|||
throw new Error('not available in debugConnector');
|
||||
return {};
|
||||
}
|
||||
getSubscribeRouter() {
|
||||
getSocketPath() {
|
||||
throw new Error('not available in debugConnector');
|
||||
return {};
|
||||
}
|
||||
getSubscribePointRouter() {
|
||||
getSocketPointRouter() {
|
||||
throw new Error('not available in debugConnector');
|
||||
return {};
|
||||
}
|
||||
getSubscribePoint() {
|
||||
getSocketPoint() {
|
||||
throw new Error('not available in debugConnector');
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
"oak-domain": "file:../oak-domain",
|
||||
"oak-memory-tree-store": "file:../oak-memory-tree-store",
|
||||
"ol": "^7.3.0",
|
||||
"rc-pagination": "^4.3.0",
|
||||
"react-activation": "^0.12.4",
|
||||
"react-native-device-info": "^10.12.0",
|
||||
"react-native-localize": "^3.0.4",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { EntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/types/Entity';
|
||||
import { SyncContext, SyncRowStore } from 'oak-domain/lib/store/SyncRowStore';
|
||||
import { SubScriber } from '../features/subscriber';
|
||||
import { SubScriber } from '../features/socket/subscriber';
|
||||
import { Environment } from '../features/environment';
|
||||
import { Navigator } from '../features/navigator';
|
||||
import { BriefEnv } from 'oak-domain/lib/types/Environment';
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ import { Navigator } from './navigator';
|
|||
import { Port } from './port';
|
||||
// import { RelationAuth } from './relationAuth';
|
||||
import { Style } from './style';
|
||||
import { SubScriber } from './subscriber';
|
||||
import { SubScriber } from './socket/subscriber';
|
||||
import SocketPoint from './socket/socketPoint';
|
||||
import { Socket } from './socket/socket';
|
||||
import { ContextMenuFactory } from './contextMenuFactory';
|
||||
import { Geo } from './geo';
|
||||
import { SyncContext } from 'oak-domain/lib/store/SyncRowStore';
|
||||
|
|
@ -38,10 +40,13 @@ export function initializeStep2<ED extends EntityDict & BaseEntityDict, FrontCxt
|
|||
const style = new Style<ED>(render.styleDict);
|
||||
const locales = new Locales(cache, localStorage, environment, 'zh-CN'); // 临时性代码,应由上层传入
|
||||
const contextMenuFactory = new ContextMenuFactory<ED>(cache);
|
||||
const subscriber = new SubScriber(cache, message, () => connector.getSubscribePoint());
|
||||
|
||||
const socketPoint = new SocketPoint(() => connector.getSocketPoint());
|
||||
const subscriber = new SubScriber(cache, message, socketPoint);
|
||||
const socket = new Socket(message, socketPoint);
|
||||
return {
|
||||
cache,
|
||||
// relationAuth,
|
||||
socket,
|
||||
runningTree,
|
||||
locales,
|
||||
port,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,137 @@
|
|||
import { EntityDict, OpRecord, SubDataDef } from 'oak-domain/lib/types';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { OakSocketConnectException } from 'oak-domain/lib/types/Exception';
|
||||
import { Message } from '../message';
|
||||
import { Feature } from '../../types/Feature';
|
||||
import io, { Socket as SocketIo } from '../../utils/socket.io/socket.io';
|
||||
import SocketPoint from './socketPoint';
|
||||
import assert from 'assert';
|
||||
|
||||
export type SocketEvent = {
|
||||
event: 'connect',
|
||||
} | {
|
||||
event: 'disconnect',
|
||||
} | {
|
||||
event: 'connect_error',
|
||||
reason: any,
|
||||
} | {
|
||||
event: 'data',
|
||||
data: any,
|
||||
};
|
||||
|
||||
export class Socket extends Feature {
|
||||
private socketPoint: SocketPoint;
|
||||
private url?: string;
|
||||
private path?: string;
|
||||
private io?: SocketIo;
|
||||
private message: Message;
|
||||
|
||||
constructor(message: Message, socketPoint: SocketPoint) {
|
||||
super();
|
||||
this.message = message;
|
||||
this.socketPoint = socketPoint;
|
||||
}
|
||||
|
||||
private async initSocketPoint() {
|
||||
try {
|
||||
const { socketUrl, path } = await this.socketPoint.get();
|
||||
this.url = socketUrl;
|
||||
this.path = path;
|
||||
}
|
||||
catch (err) {
|
||||
this.url = undefined;
|
||||
this.path = undefined;
|
||||
throw new OakSocketConnectException();
|
||||
}
|
||||
}
|
||||
|
||||
private connectQueue: Array<[(value?: any) => void, (reason?: any) => void]> = [];
|
||||
private ioState: 'unconnected' | 'connecting' | 'connected' = 'unconnected';
|
||||
|
||||
private onConnected() {
|
||||
this.io!.off('disconnect');
|
||||
this.io!.off('data');
|
||||
|
||||
this.io!.on('disconnect', () => {
|
||||
this.publish({
|
||||
event: 'disconnect',
|
||||
});
|
||||
this.ioState = 'unconnected';
|
||||
this.io = undefined;
|
||||
});
|
||||
|
||||
this.io!.on('data', (data: any) => {
|
||||
this.publish({
|
||||
event: 'data',
|
||||
data,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async connect() {
|
||||
if (this.ioState === 'connecting') {
|
||||
const p = new Promise(
|
||||
(resolve, reject) => {
|
||||
this.connectQueue.push([resolve, reject]);
|
||||
}
|
||||
);
|
||||
|
||||
return p;
|
||||
}
|
||||
else {
|
||||
assert(this.ioState === 'unconnected');
|
||||
this.ioState = 'connecting';
|
||||
await this.initSocketPoint();
|
||||
this.io = io(this.url!, {
|
||||
path: this.path!,
|
||||
});
|
||||
|
||||
return new Promise(
|
||||
(resolve, reject) => {
|
||||
this.io!.off('connect');
|
||||
this.io!.on('connect', () => {
|
||||
this.publish({
|
||||
event: 'connect',
|
||||
});
|
||||
this.ioState = 'connected';
|
||||
this.connectQueue.forEach(
|
||||
([resolve]) => resolve()
|
||||
);
|
||||
this.onConnected();
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
this.io!.on('connect_error', (err) => {
|
||||
this.message.setMessage({
|
||||
type: 'error',
|
||||
title: 'connect error',
|
||||
content: err.message,
|
||||
});
|
||||
this.publish({
|
||||
event: 'connect_error',
|
||||
reason: err,
|
||||
});
|
||||
this.ioState = 'unconnected';
|
||||
this.connectQueue.forEach(
|
||||
([resolve, reject]) => reject(err)
|
||||
);
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async send(data: any, callback?: Function) {
|
||||
if (!this.io) {
|
||||
await this.connect();
|
||||
}
|
||||
|
||||
this.io!.emit('data', data, callback);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
assert(this.ioState === 'connected');
|
||||
this.io!.disconnect();
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import { OakSocketConnectException } from "oak-domain/lib/types/Exception";
|
||||
|
||||
type SocketPointInfo = {
|
||||
path: string;
|
||||
subscribeUrl: string;
|
||||
socketUrl: string;
|
||||
}
|
||||
|
||||
export default class SocketPoint {
|
||||
getSocketPointFn: () => Promise<SocketPointInfo>;
|
||||
private socketPointInfo?: SocketPointInfo;
|
||||
|
||||
constructor(getSocketPointFn: () => Promise<SocketPointInfo>) {
|
||||
this.getSocketPointFn = getSocketPointFn;
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
this.socketPointInfo = await this.getSocketPointFn();
|
||||
}
|
||||
catch(err) {
|
||||
throw new OakSocketConnectException();
|
||||
}
|
||||
}
|
||||
|
||||
async get() {
|
||||
if (!this.socketPointInfo) {
|
||||
await this.init();
|
||||
}
|
||||
return this.socketPointInfo!;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { EntityDict, OpRecord, SubDataDef } from 'oak-domain/lib/types';
|
||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { pull } from 'oak-domain/lib/utils/lodash';
|
||||
import { Feature } from '../types/Feature';
|
||||
import { Feature } from '../../types/Feature';
|
||||
|
||||
type SubscribeEvent = 'connect' | 'disconnect';
|
||||
|
||||
|
|
@ -11,12 +11,7 @@ export class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature
|
|||
disconnect: [],
|
||||
};
|
||||
|
||||
constructor(
|
||||
getSubscribePointFn: () => Promise<{
|
||||
url: string;
|
||||
path: string;
|
||||
}>
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
|
@ -3,10 +3,11 @@ import { EntityDict, OpRecord } from 'oak-domain/lib/types';
|
|||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||
import { pull, unset } from 'oak-domain/lib/utils/lodash';
|
||||
import { OakSocketConnectException } from 'oak-domain/lib/types/Exception';
|
||||
import { Cache } from './cache';
|
||||
import { Message } from './message';
|
||||
import io, { Socket } from '../utils/socket.io/socket.io';
|
||||
import { Feature } from '../types/Feature';
|
||||
import { Cache } from '../cache';
|
||||
import { Message } from '../message';
|
||||
import io, { Socket } from '../../utils/socket.io/socket.io';
|
||||
import { Feature } from '../../types/Feature';
|
||||
import SocketPoint from './socketPoint';
|
||||
|
||||
type SubscribeEvent = 'connect' | 'disconnect';
|
||||
|
||||
|
|
@ -15,10 +16,7 @@ type Callback<ED extends EntityDict & BaseEntityDict> = (event: string, records:
|
|||
export class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature {
|
||||
private cache: Cache<ED>;
|
||||
private message: Message;
|
||||
private getSubscribePointFn: () => Promise<{
|
||||
url: string;
|
||||
path: string;
|
||||
}>;
|
||||
private socketPoint: SocketPoint;
|
||||
private eventMap: Record<string, {
|
||||
callbacks: Callback<ED>[];
|
||||
count: number;
|
||||
|
|
@ -42,15 +40,12 @@ export class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature
|
|||
constructor(
|
||||
cache: Cache<ED>,
|
||||
message: Message,
|
||||
getSubscribePointFn: () => Promise<{
|
||||
url: string;
|
||||
path: string;
|
||||
}>
|
||||
socketPoint: SocketPoint
|
||||
) {
|
||||
super();
|
||||
this.cache = cache;
|
||||
this.message = message;
|
||||
this.getSubscribePointFn = getSubscribePointFn;
|
||||
this.socketPoint = socketPoint;
|
||||
this.halted = false;
|
||||
this.reconnectInterval = 10 * 1000; // 每隔10秒重连
|
||||
this.reconnectTimer = undefined
|
||||
|
|
@ -71,8 +66,8 @@ export class SubScriber<ED extends EntityDict & BaseEntityDict> extends Feature
|
|||
|
||||
private async initSocketPoint() {
|
||||
try {
|
||||
const { url, path } = await this.getSubscribePointFn();
|
||||
this.url = url;
|
||||
const { subscribeUrl, path } = await this.socketPoint.get();
|
||||
this.url = subscribeUrl;
|
||||
this.path = path;
|
||||
}
|
||||
catch(err) {
|
||||
|
|
@ -87,15 +87,15 @@ export default class DebugConnector<ED extends EntityDict & BaseEntityDict, Cxt
|
|||
throw new Error('not available in debugConnector');
|
||||
return {} as any;
|
||||
}
|
||||
getSubscribeRouter() {
|
||||
getSocketPath() {
|
||||
throw new Error('not available in debugConnector');
|
||||
return {} as any;
|
||||
}
|
||||
getSubscribePointRouter() {
|
||||
getSocketPointRouter() {
|
||||
throw new Error('not available in debugConnector');
|
||||
return {} as any;
|
||||
}
|
||||
getSubscribePoint(){
|
||||
getSocketPoint(){
|
||||
throw new Error('not available in debugConnector');
|
||||
return {} as any;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue