Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2b82061
feat: enhance WebSocket dispatcher to include connection ID and impro…
AxiosLeo Apr 6, 2026
5e5d6a0
feat: add connection ID to WebSocketContext and implement methods for…
AxiosLeo Apr 6, 2026
7aca920
feat: add debug logging to WebSocket server for message broadcasting
AxiosLeo Apr 6, 2026
685bf1d
fix: update TypeScript definitions for send method to require WebSock…
AxiosLeo Apr 6, 2026
39bcd68
chore: update ECMAScript version in ESLint configuration from 2018 to…
AxiosLeo Apr 6, 2026
02b64a9
chore: update Node.js version requirements in package.json and CI wor…
AxiosLeo Apr 6, 2026
1703efc
feat: extend socket dispatcher to include connection ID in context fo…
AxiosLeo Apr 6, 2026
8300666
refactor: replace optional chaining with direct property access for a…
AxiosLeo Apr 6, 2026
0442187
feat: add application instance to KoaContext, SocketContext, and WebS…
AxiosLeo Apr 6, 2026
7090bf9
fix: ensure sendByConnectionId method checks for valid connection bef…
AxiosLeo Apr 6, 2026
90181cf
chore: lower Node.js version requirement to 16 in package.json and CI…
AxiosLeo Apr 6, 2026
63f59e9
feat: implement methods for sending, closing, and managing connection…
AxiosLeo Apr 6, 2026
b5097e1
feat: add ping configuration to SocketApplication and WebSocketApplic…
AxiosLeo Apr 7, 2026
1d64ee9
refactor: enhance SocketApplication and WebSocketApplication by addin…
AxiosLeo Apr 10, 2026
0cf61a8
docs: update project structure and add detailed documentation for Soc…
AxiosLeo Apr 10, 2026
e29063a
test: add comprehensive unit tests for Application, Controller, core …
AxiosLeo Apr 10, 2026
b47da55
refactor: update websocketOptions in WebSocketApplication to remove u…
AxiosLeo Apr 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
operating-system: [macos-latest, ubuntu-latest]
node-version: [16, 18, 20]
node-version: [16, 18, 20, 22, 24]
name: Node.js ${{ matrix.node-version }} Test on ${{ matrix.operating-system }}

steps:
Expand Down
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default [...compat.extends("eslint:recommended"), {
beforeEach: true,
},

ecmaVersion: 2018,
ecmaVersion: 2020,
sourceType: "commonjs",

parserOptions: {
Expand Down
7 changes: 7 additions & 0 deletions examples/websocket.server.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { debug } = require('@axiosleo/cli-tool');
const { WebSocketApplication } = require('../src/apps');
const root = require('./api.router');

Expand All @@ -11,4 +12,10 @@ const app = new WebSocketApplication({
}
});

setInterval(() => {
debug.log('send message');
const res = app.broadcast('Hello, world!', 'ok', 0, null);
debug.log('send message result:', res);
}, 1000);

app.start();
113 changes: 112 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ interface KoaContext<
TBody = any,
TQuery = any,
> extends AppContext<TParams, TBody, TQuery> {
/** Application instance */
app: KoaApplication;
/** Route parameters */
params?: TParams;
/** Application configuration */
Expand Down Expand Up @@ -533,6 +535,10 @@ export interface SocketContext<
TBody = any,
TQuery = any,
> extends AppContext<TParams, TBody, TQuery> {
/** Application instance */
app: SocketApplication;
/** Connection ID */
connection_id: string;
/** Route parameters */
params?: TParams;
/** Application configuration */
Expand Down Expand Up @@ -580,6 +586,10 @@ export interface WebSocketContext<
TBody = any,
TQuery = any,
> extends AppContext<TParams, TBody, TQuery> {
/** Application instance */
app: WebSocketApplication;
/** Connection ID */
connection_id: string;
/** Route parameters */
params?: TParams;
/** Application configuration */
Expand Down Expand Up @@ -1236,7 +1246,7 @@ export declare abstract class Application extends EventEmitter {
/** Application identifier */
app_id: string;
/** Application configuration */
config: Configuration;
config: AppConfiguration;

constructor(config: AppConfiguration);

Expand Down Expand Up @@ -1297,10 +1307,16 @@ export declare class SocketClient {
close(): void;
}

export type PingConfig = { open: boolean; interval: number; data: any };

/**
* Socket-based application
*/
export declare class SocketApplication extends Application {
config: AppConfiguration & {
ping?: PingConfig;
};
pingConfig: PingConfig;
constructor(config: SocketAppConfiguration);

/**
Expand All @@ -1322,12 +1338,60 @@ export declare class SocketApplication extends Application {
code?: number,
connections?: Socket[],
): void;

/**
* Send data to a specific connection
* @param connection Connection to send to
* @param data Data to send
* @param msg Message
* @param code Status code
*/
send(connection: Socket, data?: any, msg?: string, code?: number): boolean;

/**
* Send data to a specific connection by connection ID
* @param connection_id Connection ID to send to
* @param data Data to send
* @param msg Message
* @param code Status code
*/
sendByConnectionId(
connection_id: string,
data?: any,
msg?: string,
code?: number,
): boolean;

/**
* Close a specific connection
* @param connection Connection to close
*/
close(connection: Socket): boolean;

/**
* Close a specific connection by connection ID
*/
closeByConnectionId(connection_id: string): boolean;

/**
* Get a specific connection by connection ID
* @param connection_id Connection ID to get
* @returns Connection or null if not found
*/
getConnection(connection_id: string): Socket | null;

/**
* Ping a specific connection
* @param connection_id Connection ID to ping
*/
ping(connection_id: string): boolean;
}

/**
* WebSocket-based application
*/
export declare class WebSocketApplication extends Application {
pingConfig: { open: boolean; interval: number; data: any };
constructor(config: WebSocketAppConfiguration);

/**
Expand All @@ -1349,6 +1413,53 @@ export declare class WebSocketApplication extends Application {
code?: number,
connections?: WebSocket[],
): void;

/**
* Send data to a specific connection
* @param connection Connection to send to
* @param data Data to send
* @param msg Message
* @param code Status code
*/
send(connection: WebSocket, data?: any, msg?: string, code?: number): boolean;

/**
* Send data to a specific connection by connection ID
* @param connection_id Connection ID to send to
* @param data Data to send
* @param msg Message
* @param code Status code
*/
sendByConnectionId(
connection_id: string,
data?: any,
msg?: string,
code?: number,
): boolean;

/**
* Close a specific connection
* @param connection Connection to close
*/
close(connection: WebSocket): boolean;

/**
* Close a specific connection by connection ID
*/
closeByConnectionId(connection_id: string): boolean;

/**
* Get a specific connection by connection ID
* @param connection_id Connection ID to get
* @returns Connection or null if not found
*/
getConnection(connection_id: string): WebSocket | null;

/**
* Ping a specific connection
* @param connection_id Connection ID to ping
*/
ping(connection_id: string): boolean;
}

// ========================================
Expand Down
65 changes: 59 additions & 6 deletions src/apps/socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const operator = require('../workflows/socket.workflow');
const { _assign } = require('@axiosleo/cli-tool/src/helper/obj');
const { _sleep } = require('@axiosleo/cli-tool/src/helper/cmd');

const dispatcher = ({ app, app_id, workflow, connection }) => {
const dispatcher = ({ app, app_id, workflow, connection, connection_id }) => {
return async (ctx) => {
let context = initContext({
app,
Expand All @@ -21,6 +21,7 @@ const dispatcher = ({ app, app_id, workflow, connection }) => {
app_id,
});
context.socket = connection;
context.connection_id = connection_id;
context.query = ctx.query || {};
context.body = ctx.body || {};
try {
Expand Down Expand Up @@ -72,8 +73,8 @@ class SocketApplication extends Application {
this.connections = {};
this.on('response', handleRes);
this.workflow = new Workflow(operator);
this.ping = {};
_assign(this.ping, {
this.pingConfig = {};
_assign(this.pingConfig, {
open: false,
interval: 1000 * 60 * 5,
data: 'this is a ping message'
Expand All @@ -100,7 +101,8 @@ class SocketApplication extends Application {
app: self,
app_id: self.app_id,
workflow: self.workflow,
connection
connection,
connection_id
});
process.nextTick(callback, context);
} catch (err) {
Expand All @@ -123,11 +125,11 @@ class SocketApplication extends Application {
debug.error('[Socket App]', 'socket server error:', err);
}
});
if (this.ping.open) {
if (this.pingConfig.open) {
const self = this;
printer.info('[Socket App] ping is open.');
process.nextTick(() => {
ping.call(self, self.ping.data, self.ping.interval);
ping.call(self, self.pingConfig.data, self.pingConfig.interval);
});
}

Expand Down Expand Up @@ -157,6 +159,57 @@ class SocketApplication extends Application {
Object.keys(connections).map((id) => connections[id].write(data));
}
}

send(connection = null, data = '', msg = 'ok', code = 0) {
if (connection) {
data = JSON.stringify({
request_id: _uuid_salt(this.app_id),
timestamp: (new Date()).getTime(),
code,
message: msg,
data: data
});
connection.write(data + '@@@@@@');
return true;
}
return false;
}
Comment thread
cursor[bot] marked this conversation as resolved.

sendByConnectionId(connection_id = null, data = '', msg = 'ok', code = 0) {
if (connection_id && this.connections[connection_id]) {
return this.send(this.connections[connection_id], data, msg, code);
}
return false;
}

close(connection = null) {
if (connection) {
connection.end();
return true;
}
return false;
}

closeByConnectionId(connection_id = null) {
if (connection_id && this.connections[connection_id]) {
return this.close(this.connections[connection_id]);
}
return false;
}

getConnection(connection_id = null) {
if (connection_id && this.connections[connection_id]) {
return this.connections[connection_id];
}
return null;
}

ping(connection_id = null) {
if (connection_id && this.connections[connection_id]) {
return this.send(this.connections[connection_id], 'ping', 'ok', 0);
}
return false;
}
}

module.exports = SocketApplication;
Loading
Loading