@simplysm/service-server 14.0.4 → 14.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +121 -56
- package/docs/auth.md +33 -30
- package/docs/built-in-services.md +61 -0
- package/docs/config-manager.md +24 -0
- package/docs/http-handlers.md +83 -0
- package/docs/legacy-v1.md +25 -0
- package/docs/protocol-wrapper.md +37 -0
- package/docs/server-options.md +31 -0
- package/docs/service-definition.md +151 -0
- package/docs/service-executor.md +42 -0
- package/docs/service-server.md +80 -0
- package/docs/service-socket.md +73 -0
- package/docs/websocket-handler.md +58 -0
- package/package.json +6 -6
- package/docs/core.md +0 -192
- package/docs/legacy.md +0 -21
- package/docs/main.md +0 -81
- package/docs/protocol.md +0 -31
- package/docs/services.md +0 -63
- package/docs/transport.md +0 -169
- package/docs/types.md +0 -31
- package/docs/utilities.md +0 -27
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Service Definition/Context
|
|
2
|
+
|
|
3
|
+
## `ServiceContext`
|
|
4
|
+
|
|
5
|
+
Service execution context providing access to the server, socket, authentication info, and configuration.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
export interface ServiceContext<TAuthInfo = unknown> {
|
|
9
|
+
server: ServiceServer<TAuthInfo>;
|
|
10
|
+
socket?: ServiceSocket;
|
|
11
|
+
http?: {
|
|
12
|
+
clientName: string;
|
|
13
|
+
authTokenPayload?: AuthTokenPayload<TAuthInfo>;
|
|
14
|
+
};
|
|
15
|
+
legacy?: {
|
|
16
|
+
clientName?: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
get authInfo(): TAuthInfo | undefined;
|
|
20
|
+
get clientName(): string | undefined;
|
|
21
|
+
get clientPath(): string | undefined;
|
|
22
|
+
getConfig<T>(section: string): Promise<T>;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Member | Type | Description |
|
|
27
|
+
|--------|------|-------------|
|
|
28
|
+
| `server` | `ServiceServer<TAuthInfo>` | The server instance |
|
|
29
|
+
| `socket` | `ServiceSocket?` | WebSocket connection (undefined for HTTP requests) |
|
|
30
|
+
| `http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> }?` | HTTP request context (undefined for WebSocket requests) |
|
|
31
|
+
| `legacy` | `{ clientName?: string }?` | V1 legacy context (auto-update only) |
|
|
32
|
+
| `authInfo` | `TAuthInfo \| undefined` (getter) | Authenticated user data from socket or HTTP auth token |
|
|
33
|
+
| `clientName` | `string \| undefined` (getter) | Client name from socket, HTTP, or legacy context. Validates against path traversal |
|
|
34
|
+
| `clientPath` | `string \| undefined` (getter) | Resolved client path: `{rootPath}/www/{clientName}` |
|
|
35
|
+
| `getConfig<T>(section)` | `(section: string) => Promise<T>` | Loads config section from root and client `.config.json` files (merged) |
|
|
36
|
+
|
|
37
|
+
## `createServiceContext`
|
|
38
|
+
|
|
39
|
+
Creates a `ServiceContext` instance.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
export function createServiceContext<TAuthInfo = unknown>(
|
|
43
|
+
server: ServiceServer<TAuthInfo>,
|
|
44
|
+
socket?: ServiceSocket,
|
|
45
|
+
http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> },
|
|
46
|
+
legacy?: { clientName?: string },
|
|
47
|
+
): ServiceContext<TAuthInfo>;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Parameter | Type | Description |
|
|
51
|
+
|-----------|------|-------------|
|
|
52
|
+
| `server` | `ServiceServer<TAuthInfo>` | The server instance |
|
|
53
|
+
| `socket` | `ServiceSocket?` | WebSocket connection |
|
|
54
|
+
| `http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> }?` | HTTP request context |
|
|
55
|
+
| `legacy` | `{ clientName?: string }?` | V1 legacy context |
|
|
56
|
+
|
|
57
|
+
## `getServiceAuthPermissions`
|
|
58
|
+
|
|
59
|
+
Reads auth permissions from a function wrapped by `auth()`. Returns `undefined` for unwrapped functions.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
export function getServiceAuthPermissions(fn: Function): string[] | undefined;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| Parameter | Type | Description |
|
|
66
|
+
|-----------|------|-------------|
|
|
67
|
+
| `fn` | `Function` | The function to check for auth permissions |
|
|
68
|
+
|
|
69
|
+
**Returns:** `string[] | undefined` -- Permission array if auth-wrapped, `undefined` otherwise.
|
|
70
|
+
|
|
71
|
+
## `auth`
|
|
72
|
+
|
|
73
|
+
Authentication wrapper for service factories and methods. Marks functions as requiring authentication, optionally with specific role permissions.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// No role restriction (login required)
|
|
77
|
+
export function auth<TFunction extends (...args: any[]) => any>(fn: TFunction): TFunction;
|
|
78
|
+
|
|
79
|
+
// Role restriction (specific roles required)
|
|
80
|
+
export function auth<TFunction extends (...args: any[]) => any>(
|
|
81
|
+
permissions: string[],
|
|
82
|
+
fn: TFunction,
|
|
83
|
+
): TFunction;
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Usage levels:
|
|
87
|
+
- **Service level:** `auth((ctx) => ({ ... }))` -- All methods require login
|
|
88
|
+
- **Service level with roles:** `auth(["admin"], (ctx) => ({ ... }))` -- All methods require specific roles
|
|
89
|
+
- **Method level:** `auth(() => result)` -- Specific method requires login
|
|
90
|
+
- **Method level with roles:** `auth(["admin"], () => result)` -- Specific method requires specific roles
|
|
91
|
+
|
|
92
|
+
## `ServiceDefinition`
|
|
93
|
+
|
|
94
|
+
Service definition with a name and factory function.
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
export interface ServiceDefinition<TMethods = Record<string, (...args: any[]) => any>> {
|
|
98
|
+
name: string;
|
|
99
|
+
factory: (ctx: ServiceContext) => TMethods;
|
|
100
|
+
authPermissions?: string[];
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| Field | Type | Description |
|
|
105
|
+
|-------|------|-------------|
|
|
106
|
+
| `name` | `string` | Service name (used in RPC routing) |
|
|
107
|
+
| `factory` | `(ctx: ServiceContext) => TMethods` | Factory function that creates service methods from a context |
|
|
108
|
+
| `authPermissions` | `string[]?` | Service-level auth permissions (extracted from `auth()` wrapper) |
|
|
109
|
+
|
|
110
|
+
## `defineService`
|
|
111
|
+
|
|
112
|
+
Defines a named service with a factory function.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
export function defineService<TMethods extends Record<string, (...args: any[]) => any>>(
|
|
116
|
+
name: string,
|
|
117
|
+
factory: (ctx: ServiceContext) => TMethods,
|
|
118
|
+
): ServiceDefinition<TMethods>;
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Description |
|
|
122
|
+
|-----------|------|-------------|
|
|
123
|
+
| `name` | `string` | Service name |
|
|
124
|
+
| `factory` | `(ctx: ServiceContext) => TMethods` | Factory function creating service methods |
|
|
125
|
+
|
|
126
|
+
**Returns:** `ServiceDefinition<TMethods>`
|
|
127
|
+
|
|
128
|
+
**Example:**
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const UserService = defineService("User", auth((ctx) => ({
|
|
132
|
+
getProfile: () => ctx.authInfo,
|
|
133
|
+
adminOnly: auth(["admin"], () => "admin-data"),
|
|
134
|
+
})));
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## `ServiceMethods`
|
|
138
|
+
|
|
139
|
+
Type utility that extracts method signatures from a `ServiceDefinition`. Useful for sharing types between server and client.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
export type ServiceMethods<TDefinition> =
|
|
143
|
+
TDefinition extends ServiceDefinition<infer M> ? M : never;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Example:**
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
export type UserServiceType = ServiceMethods<typeof UserService>;
|
|
150
|
+
// Client: client.getService<UserServiceType>("User");
|
|
151
|
+
```
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Service Executor
|
|
2
|
+
|
|
3
|
+
## `executeServiceMethod`
|
|
4
|
+
|
|
5
|
+
Executes a service method with full validation and authentication checking.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
export async function executeServiceMethod(
|
|
9
|
+
server: ServiceServer,
|
|
10
|
+
def: {
|
|
11
|
+
serviceName: string;
|
|
12
|
+
methodName: string;
|
|
13
|
+
params: unknown[];
|
|
14
|
+
socket?: ServiceSocket;
|
|
15
|
+
http?: { clientName: string; authTokenPayload?: AuthTokenPayload };
|
|
16
|
+
},
|
|
17
|
+
): Promise<unknown>;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Parameter | Type | Description |
|
|
21
|
+
|-----------|------|-------------|
|
|
22
|
+
| `server` | `ServiceServer` | The server instance (used to find service definitions and check auth config) |
|
|
23
|
+
| `def.serviceName` | `string` | Service name to look up |
|
|
24
|
+
| `def.methodName` | `string` | Method name to invoke |
|
|
25
|
+
| `def.params` | `unknown[]` | Method parameters |
|
|
26
|
+
| `def.socket` | `ServiceSocket?` | WebSocket connection (for WebSocket requests) |
|
|
27
|
+
| `def.http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload }?` | HTTP context (for HTTP requests) |
|
|
28
|
+
|
|
29
|
+
**Returns:** `Promise<unknown>` -- The method return value.
|
|
30
|
+
|
|
31
|
+
**Execution flow:**
|
|
32
|
+
1. Finds the service definition by name (throws if not found)
|
|
33
|
+
2. Validates client name for path traversal attacks
|
|
34
|
+
3. Creates a `ServiceContext`
|
|
35
|
+
4. Calls the factory to create the method object
|
|
36
|
+
5. Looks up the method by name (throws if not a function)
|
|
37
|
+
6. Checks authentication:
|
|
38
|
+
- Method-level `auth()` permissions take precedence over service-level
|
|
39
|
+
- If `server.options.auth === undefined` and auth is required, throws configuration error
|
|
40
|
+
- If `server.options.auth === false`, skips all auth checks
|
|
41
|
+
- Otherwise, verifies `authTokenPayload` exists and has required roles
|
|
42
|
+
7. Invokes the method with parameters
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Main ServiceServer
|
|
2
|
+
|
|
3
|
+
## `ServiceServer`
|
|
4
|
+
|
|
5
|
+
Main server class built on Fastify with WebSocket support, JWT authentication, and graceful shutdown. Extends `EventEmitter` with `ready` and `close` events.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
export class ServiceServer<TAuthInfo = unknown> extends EventEmitter<{
|
|
9
|
+
ready: void;
|
|
10
|
+
close: void;
|
|
11
|
+
}> {
|
|
12
|
+
constructor(options: ServiceServerOptions);
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Events
|
|
17
|
+
|
|
18
|
+
| Event | Data Type | Description |
|
|
19
|
+
|-------|-----------|-------------|
|
|
20
|
+
| `ready` | `void` | Emitted when the server starts listening |
|
|
21
|
+
| `close` | `void` | Emitted when the server is closed |
|
|
22
|
+
|
|
23
|
+
### Properties
|
|
24
|
+
|
|
25
|
+
| Property | Type | Description |
|
|
26
|
+
|----------|------|-------------|
|
|
27
|
+
| `options` | `ServiceServerOptions` (readonly) | Server configuration options |
|
|
28
|
+
| `fastify` | `FastifyInstance` (readonly) | The underlying Fastify instance for custom route registration |
|
|
29
|
+
| `isOpen` | `boolean` | Whether the server is currently listening |
|
|
30
|
+
|
|
31
|
+
### Methods
|
|
32
|
+
|
|
33
|
+
| Method | Signature | Description |
|
|
34
|
+
|--------|-----------|-------------|
|
|
35
|
+
| `listen` | `listen(): Promise<void>` | Starts the server. Registers all plugins, routes, and WebSocket handlers. Listens on `0.0.0.0:{port}` |
|
|
36
|
+
| `close` | `close(): Promise<void>` | Closes all WebSocket connections and stops the Fastify server |
|
|
37
|
+
| `emitEvent` | `emitEvent<TInfo, TData>(eventDef: ServiceEventDef<TInfo, TData>, infoSelector: (item: TInfo) => boolean, data: TData): Promise<void>` | Broadcasts an event to connected clients matching the info selector |
|
|
38
|
+
| `signAuthToken` | `signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>` | Signs a JWT token using the server's secret |
|
|
39
|
+
| `verifyAuthToken` | `verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>` | Verifies a JWT token using the server's secret |
|
|
40
|
+
|
|
41
|
+
### Registered Routes
|
|
42
|
+
|
|
43
|
+
| Route | Method | Description |
|
|
44
|
+
|-------|--------|-------------|
|
|
45
|
+
| `/api/:service/:method` | GET, POST | HTTP RPC endpoint |
|
|
46
|
+
| `/upload` | POST | Multipart file upload endpoint |
|
|
47
|
+
| `/` | WebSocket | WebSocket endpoint (V1 and V2) |
|
|
48
|
+
| `/ws` | WebSocket | WebSocket endpoint (V1 and V2) |
|
|
49
|
+
| `/*` | GET, POST, PUT, DELETE, PATCH, HEAD | Static file handler |
|
|
50
|
+
|
|
51
|
+
### Registered Plugins
|
|
52
|
+
|
|
53
|
+
- `@fastify/websocket` -- WebSocket support
|
|
54
|
+
- `@fastify/helmet` -- Security headers (CSP configured for permissive defaults)
|
|
55
|
+
- `@fastify/multipart` -- File upload support
|
|
56
|
+
- `@fastify/static` -- Static file serving (manual serving via handler)
|
|
57
|
+
- `@fastify/cors` -- Cross-origin support (allows all origins)
|
|
58
|
+
|
|
59
|
+
### Graceful Shutdown
|
|
60
|
+
|
|
61
|
+
Registers `SIGINT` and `SIGTERM` handlers that:
|
|
62
|
+
1. Close all WebSocket connections
|
|
63
|
+
2. Stop the Fastify server
|
|
64
|
+
3. Force exit after 10 seconds if shutdown hangs
|
|
65
|
+
|
|
66
|
+
## `createServiceServer`
|
|
67
|
+
|
|
68
|
+
Factory function to create a `ServiceServer` instance.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
export function createServiceServer<TAuthInfo = unknown>(
|
|
72
|
+
options: ServiceServerOptions,
|
|
73
|
+
): ServiceServer<TAuthInfo>;
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
| Parameter | Type | Description |
|
|
77
|
+
|-----------|------|-------------|
|
|
78
|
+
| `options` | `ServiceServerOptions` | Server configuration options |
|
|
79
|
+
|
|
80
|
+
**Returns:** `ServiceServer<TAuthInfo>`
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Service Socket
|
|
2
|
+
|
|
3
|
+
## `ServiceSocket`
|
|
4
|
+
|
|
5
|
+
Manages a single WebSocket connection with protocol encoding/decoding, ping/pong keepalive, and event listener tracking.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
export interface ServiceSocket {
|
|
9
|
+
readonly connectedAtDateTime: DateTime;
|
|
10
|
+
readonly clientName: string;
|
|
11
|
+
readonly connReq: FastifyRequest;
|
|
12
|
+
authTokenPayload?: AuthTokenPayload;
|
|
13
|
+
|
|
14
|
+
close(): void;
|
|
15
|
+
send(uuid: string, msg: ServiceServerMessage): Promise<number>;
|
|
16
|
+
addListener(key: string, eventName: string, info: unknown): void;
|
|
17
|
+
removeListener(key: string): void;
|
|
18
|
+
getEventListeners(eventName: string): Array<{ key: string; info: unknown }>;
|
|
19
|
+
filterEventTargetKeys(targetKeys: string[]): string[];
|
|
20
|
+
on(event: "error", handler: (err: Error) => void): void;
|
|
21
|
+
on(event: "close", handler: (code: number) => void): void;
|
|
22
|
+
on(event: "message", handler: (data: { uuid: string; msg: ServiceClientMessage }) => void): void;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Properties
|
|
27
|
+
|
|
28
|
+
| Property | Type | Description |
|
|
29
|
+
|----------|------|-------------|
|
|
30
|
+
| `connectedAtDateTime` | `DateTime` (readonly) | Timestamp when the connection was established |
|
|
31
|
+
| `clientName` | `string` (readonly) | Client identifier name |
|
|
32
|
+
| `connReq` | `FastifyRequest` (readonly) | Original Fastify request object |
|
|
33
|
+
| `authTokenPayload` | `AuthTokenPayload?` | Authenticated token payload (set via auth message) |
|
|
34
|
+
|
|
35
|
+
### Methods
|
|
36
|
+
|
|
37
|
+
| Method | Parameters | Return | Description |
|
|
38
|
+
|--------|-----------|--------|-------------|
|
|
39
|
+
| `close` | none | `void` | Terminates the WebSocket connection |
|
|
40
|
+
| `send` | `uuid: string, msg: ServiceServerMessage` | `Promise<number>` | Sends a message to the client. Returns total bytes sent |
|
|
41
|
+
| `addListener` | `key: string, eventName: string, info: unknown` | `void` | Registers an event listener |
|
|
42
|
+
| `removeListener` | `key: string` | `void` | Removes an event listener by key |
|
|
43
|
+
| `getEventListeners` | `eventName: string` | `Array<{ key: string; info: unknown }>` | Gets all listeners for an event name |
|
|
44
|
+
| `filterEventTargetKeys` | `targetKeys: string[]` | `string[]` | Filters target keys that exist in this socket's listeners |
|
|
45
|
+
| `on("error")` | `handler: (err: Error) => void` | `void` | Registers error event handler |
|
|
46
|
+
| `on("close")` | `handler: (code: number) => void` | `void` | Registers close event handler |
|
|
47
|
+
| `on("message")` | `handler: (data: { uuid: string; msg: ServiceClientMessage }) => void` | `void` | Registers message event handler |
|
|
48
|
+
|
|
49
|
+
## `createServiceSocket`
|
|
50
|
+
|
|
51
|
+
Creates a `ServiceSocket` instance wrapping a raw WebSocket.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
export function createServiceSocket(
|
|
55
|
+
socket: WebSocket,
|
|
56
|
+
clientId: string,
|
|
57
|
+
clientName: string,
|
|
58
|
+
connReq: FastifyRequest,
|
|
59
|
+
): ServiceSocket;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Parameter | Type | Description |
|
|
63
|
+
|-----------|------|-------------|
|
|
64
|
+
| `socket` | `WebSocket` (from `ws`) | Raw WebSocket connection |
|
|
65
|
+
| `clientId` | `string` | Client unique identifier |
|
|
66
|
+
| `clientName` | `string` | Client name |
|
|
67
|
+
| `connReq` | `FastifyRequest` | Original request object |
|
|
68
|
+
|
|
69
|
+
Internal behavior:
|
|
70
|
+
- Ping interval: 5 seconds (terminates connection if no pong response)
|
|
71
|
+
- Responds to client ping (`0x01`) with pong (`0x02`)
|
|
72
|
+
- Uses `ServerProtocolWrapper` for encode/decode
|
|
73
|
+
- Reports chunk progress back to client during message reassembly
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# WebSocket Handler
|
|
2
|
+
|
|
3
|
+
## `WebSocketHandler`
|
|
4
|
+
|
|
5
|
+
Manages multiple WebSocket connections, routes messages to services, and handles event broadcasting.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
export interface WebSocketHandler {
|
|
9
|
+
addSocket(
|
|
10
|
+
socket: WebSocket,
|
|
11
|
+
clientId: string,
|
|
12
|
+
clientName: string,
|
|
13
|
+
connReq: FastifyRequest,
|
|
14
|
+
): void;
|
|
15
|
+
closeAll(): void;
|
|
16
|
+
emit<TInfo, TData>(
|
|
17
|
+
eventDef: ServiceEventDef<TInfo, TData>,
|
|
18
|
+
infoSelector: (item: TInfo) => boolean,
|
|
19
|
+
data: TData,
|
|
20
|
+
): Promise<void>;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
| Method | Parameters | Return | Description |
|
|
25
|
+
|--------|-----------|--------|-------------|
|
|
26
|
+
| `addSocket` | `socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest` | `void` | Registers a new WebSocket connection. Closes any existing connection with the same `clientId` |
|
|
27
|
+
| `closeAll` | none | `void` | Closes all active WebSocket connections |
|
|
28
|
+
| `emit` | `eventDef: ServiceEventDef<TInfo, TData>, infoSelector: (item: TInfo) => boolean, data: TData` | `Promise<void>` | Broadcasts an event to all sockets that have matching event listeners |
|
|
29
|
+
|
|
30
|
+
## `createWebSocketHandler`
|
|
31
|
+
|
|
32
|
+
Creates a `WebSocketHandler` instance.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
export function createWebSocketHandler(
|
|
36
|
+
runMethod: (def: {
|
|
37
|
+
serviceName: string;
|
|
38
|
+
methodName: string;
|
|
39
|
+
params: unknown[];
|
|
40
|
+
socket?: ServiceSocket;
|
|
41
|
+
}) => Promise<unknown>,
|
|
42
|
+
jwtSecret: string | undefined,
|
|
43
|
+
): WebSocketHandler;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
| Parameter | Type | Description |
|
|
47
|
+
|-----------|------|-------------|
|
|
48
|
+
| `runMethod` | `(def: { serviceName; methodName; params; socket? }) => Promise<unknown>` | Callback to execute service methods |
|
|
49
|
+
| `jwtSecret` | `string \| undefined` | JWT secret for auth message verification |
|
|
50
|
+
|
|
51
|
+
Message routing:
|
|
52
|
+
- `${service}.${method}` -- Invokes `runMethod` and sends response
|
|
53
|
+
- `evt:add` -- Registers an event listener on the socket
|
|
54
|
+
- `evt:remove` -- Removes an event listener from the socket
|
|
55
|
+
- `evt:gets` -- Returns all listener infos for an event name across all sockets
|
|
56
|
+
- `evt:emit` -- Dispatches an event to target listener keys across all sockets
|
|
57
|
+
- `auth` -- Verifies JWT token and stores payload on the socket
|
|
58
|
+
- Other -- Returns `BAD_MESSAGE` error
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/service-server",
|
|
3
|
-
"version": "14.0.
|
|
3
|
+
"version": "14.0.5",
|
|
4
4
|
"description": "심플리즘 패키지 - 서비스 (server)",
|
|
5
5
|
"author": "심플리즘",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"semver": "^7.7.4",
|
|
36
36
|
"utf-8-validate": "^6.0.6",
|
|
37
37
|
"ws": "^8.20.0",
|
|
38
|
-
"@simplysm/
|
|
39
|
-
"@simplysm/
|
|
40
|
-
"@simplysm/
|
|
41
|
-
"@simplysm/
|
|
42
|
-
"@simplysm/core-node": "14.0.
|
|
38
|
+
"@simplysm/orm-common": "14.0.5",
|
|
39
|
+
"@simplysm/core-common": "14.0.5",
|
|
40
|
+
"@simplysm/service-common": "14.0.5",
|
|
41
|
+
"@simplysm/orm-node": "14.0.5",
|
|
42
|
+
"@simplysm/core-node": "14.0.5"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/nodemailer": "^7.0.11",
|
package/docs/core.md
DELETED
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
# Core
|
|
2
|
-
|
|
3
|
-
Service definition, context, authentication wrapper, and method execution.
|
|
4
|
-
|
|
5
|
-
## ServiceContext\<TAuthInfo\>
|
|
6
|
-
|
|
7
|
-
Context object passed to service factory functions. Provides access to the server, socket, HTTP info, and authentication state.
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
interface ServiceContext<TAuthInfo = unknown> {
|
|
11
|
-
server: ServiceServer<TAuthInfo>;
|
|
12
|
-
socket?: ServiceSocket;
|
|
13
|
-
http?: {
|
|
14
|
-
clientName: string;
|
|
15
|
-
authTokenPayload?: AuthTokenPayload<TAuthInfo>;
|
|
16
|
-
};
|
|
17
|
-
legacy?: {
|
|
18
|
-
clientName?: string;
|
|
19
|
-
};
|
|
20
|
-
get authInfo(): TAuthInfo | undefined;
|
|
21
|
-
get clientName(): string | undefined;
|
|
22
|
-
get clientPath(): string | undefined;
|
|
23
|
-
getConfig<T>(section: string): Promise<T>;
|
|
24
|
-
}
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
| Member | Kind | Type | Description |
|
|
28
|
-
|--------|------|------|-------------|
|
|
29
|
-
| `server` | property | `ServiceServer<TAuthInfo>` | Reference to the server instance |
|
|
30
|
-
| `socket` | property | `ServiceSocket?` | WebSocket connection (present for WS calls) |
|
|
31
|
-
| `http` | property | `{ clientName; authTokenPayload? }?` | HTTP request info (present for HTTP calls) |
|
|
32
|
-
| `http.clientName` | field | `string` | Client identifier from HTTP header |
|
|
33
|
-
| `http.authTokenPayload` | field | `AuthTokenPayload<TAuthInfo>?` | Decoded auth token from HTTP request |
|
|
34
|
-
| `legacy` | property | `{ clientName? }?` | Legacy V1 connection info |
|
|
35
|
-
| `authInfo` | getter | `TAuthInfo \| undefined` | Authenticated user data (from socket or HTTP token) |
|
|
36
|
-
| `clientName` | getter | `string \| undefined` | Client name (from socket, HTTP, or legacy) |
|
|
37
|
-
| `clientPath` | getter | `string \| undefined` | Client path identifier |
|
|
38
|
-
| `getConfig` | method | `<T>(section: string) => Promise<T>` | Read a configuration section |
|
|
39
|
-
|
|
40
|
-
## createServiceContext
|
|
41
|
-
|
|
42
|
-
Create a `ServiceContext` instance.
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
function createServiceContext<TAuthInfo = unknown>(
|
|
46
|
-
server: ServiceServer<TAuthInfo>,
|
|
47
|
-
socket?: ServiceSocket,
|
|
48
|
-
http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> },
|
|
49
|
-
legacy?: { clientName?: string },
|
|
50
|
-
): ServiceContext<TAuthInfo>;
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
| Parameter | Type | Description |
|
|
54
|
-
|-----------|------|-------------|
|
|
55
|
-
| `server` | `ServiceServer<TAuthInfo>` | Server instance |
|
|
56
|
-
| `socket` | `ServiceSocket?` | WebSocket connection |
|
|
57
|
-
| `http` | `{ clientName; authTokenPayload? }?` | HTTP request context |
|
|
58
|
-
| `legacy` | `{ clientName? }?` | Legacy V1 context |
|
|
59
|
-
|
|
60
|
-
## auth
|
|
61
|
-
|
|
62
|
-
Authentication wrapper for service factories and methods. Marks a function as requiring authentication. Optionally restricts access to specific roles.
|
|
63
|
-
|
|
64
|
-
```ts
|
|
65
|
-
// No role restriction -- just requires authentication
|
|
66
|
-
function auth<TFunction extends (...args: any[]) => any>(fn: TFunction): TFunction;
|
|
67
|
-
|
|
68
|
-
// With role restriction
|
|
69
|
-
function auth<TFunction extends (...args: any[]) => any>(permissions: string[], fn: TFunction): TFunction;
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
Can be applied at the service level (wrapping the factory) or at individual method level.
|
|
73
|
-
|
|
74
|
-
```ts
|
|
75
|
-
const MyService = defineService("My", (ctx) => ({
|
|
76
|
-
// Public method -- no auth required
|
|
77
|
-
publicMethod() { return "open"; },
|
|
78
|
-
|
|
79
|
-
// Requires authentication (any role)
|
|
80
|
-
protectedMethod: auth(() => "protected"),
|
|
81
|
-
|
|
82
|
-
// Requires "admin" role
|
|
83
|
-
adminMethod: auth(["admin"], () => "admin only"),
|
|
84
|
-
}));
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
## getServiceAuthPermissions
|
|
88
|
-
|
|
89
|
-
Read the authentication permissions from a function wrapped with `auth()`. Returns `undefined` if the function is not wrapped.
|
|
90
|
-
|
|
91
|
-
```ts
|
|
92
|
-
function getServiceAuthPermissions(fn: Function): string[] | undefined;
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
| Parameter | Type | Description |
|
|
96
|
-
|-----------|------|-------------|
|
|
97
|
-
| `fn` | `Function` | A potentially auth-wrapped function |
|
|
98
|
-
|
|
99
|
-
Returns `string[]` (role list, possibly empty for auth-only) or `undefined` (not wrapped).
|
|
100
|
-
|
|
101
|
-
## ServiceDefinition\<TMethods\>
|
|
102
|
-
|
|
103
|
-
A named service with a factory function and optional auth permissions.
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
interface ServiceDefinition<TMethods = Record<string, (...args: any[]) => any>> {
|
|
107
|
-
name: string;
|
|
108
|
-
factory: (ctx: ServiceContext) => TMethods;
|
|
109
|
-
authPermissions?: string[];
|
|
110
|
-
}
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
| Field | Type | Description |
|
|
114
|
-
|-------|------|-------------|
|
|
115
|
-
| `name` | `string` | Service name used for RPC routing |
|
|
116
|
-
| `factory` | `(ctx: ServiceContext) => TMethods` | Factory function that creates service methods |
|
|
117
|
-
| `authPermissions` | `string[]?` | Service-level auth permissions (from `auth()` wrapper) |
|
|
118
|
-
|
|
119
|
-
## defineService
|
|
120
|
-
|
|
121
|
-
Define a named service from a factory function.
|
|
122
|
-
|
|
123
|
-
```ts
|
|
124
|
-
function defineService<TMethods extends Record<string, (...args: any[]) => any>>(
|
|
125
|
-
name: string,
|
|
126
|
-
factory: (ctx: ServiceContext) => TMethods,
|
|
127
|
-
): ServiceDefinition<TMethods>;
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
| Parameter | Type | Description |
|
|
131
|
-
|-----------|------|-------------|
|
|
132
|
-
| `name` | `string` | Service name |
|
|
133
|
-
| `factory` | `(ctx: ServiceContext) => TMethods` | Factory that receives context and returns methods |
|
|
134
|
-
|
|
135
|
-
```ts
|
|
136
|
-
const UserService = defineService("User", (ctx) => ({
|
|
137
|
-
async getUser(id: number) {
|
|
138
|
-
// ctx.authInfo, ctx.server, ctx.getConfig, etc.
|
|
139
|
-
return { id, name: "Alice" };
|
|
140
|
-
},
|
|
141
|
-
}));
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
## ServiceMethods\<TDefinition\>
|
|
145
|
-
|
|
146
|
-
Utility type that extracts method signatures from a `ServiceDefinition`. Useful for sharing types with the client.
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
type ServiceMethods<TDefinition> = TDefinition extends ServiceDefinition<infer M> ? M : never;
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
```ts
|
|
153
|
-
// Server
|
|
154
|
-
const UserService = defineService("User", (ctx) => ({
|
|
155
|
-
getUser(id: number) { return { id, name: "Alice" }; },
|
|
156
|
-
}));
|
|
157
|
-
|
|
158
|
-
// Shared type for client
|
|
159
|
-
type UserServiceType = ServiceMethods<typeof UserService>;
|
|
160
|
-
|
|
161
|
-
// Client
|
|
162
|
-
const userSvc = client.getService<UserServiceType>("User");
|
|
163
|
-
const user = await userSvc.getUser(1); // typed as { id: number; name: string }
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## executeServiceMethod
|
|
167
|
-
|
|
168
|
-
Execute a service method by name. Used internally by the transport layer; can also be called directly for testing or custom routing.
|
|
169
|
-
|
|
170
|
-
```ts
|
|
171
|
-
async function executeServiceMethod(
|
|
172
|
-
server: ServiceServer,
|
|
173
|
-
def: {
|
|
174
|
-
serviceName: string;
|
|
175
|
-
methodName: string;
|
|
176
|
-
params: unknown[];
|
|
177
|
-
socket?: ServiceSocket;
|
|
178
|
-
http?: { clientName: string; authTokenPayload?: AuthTokenPayload };
|
|
179
|
-
},
|
|
180
|
-
): Promise<unknown>;
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
| Parameter | Type | Description |
|
|
184
|
-
|-----------|------|-------------|
|
|
185
|
-
| `server` | `ServiceServer` | Server instance containing registered services |
|
|
186
|
-
| `def.serviceName` | `string` | Name of the service to invoke |
|
|
187
|
-
| `def.methodName` | `string` | Method name within the service |
|
|
188
|
-
| `def.params` | `unknown[]` | Method arguments |
|
|
189
|
-
| `def.socket` | `ServiceSocket?` | WebSocket context (for WS calls) |
|
|
190
|
-
| `def.http` | `{ clientName; authTokenPayload? }?` | HTTP context (for HTTP calls) |
|
|
191
|
-
|
|
192
|
-
Returns the method return value.
|
package/docs/legacy.md
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Legacy
|
|
2
|
-
|
|
3
|
-
## handleV1Connection
|
|
4
|
-
|
|
5
|
-
V1 legacy client handler. Only supports auto-update requests; all other requests receive an upgrade-required error.
|
|
6
|
-
|
|
7
|
-
```ts
|
|
8
|
-
function handleV1Connection(
|
|
9
|
-
socket: WebSocket,
|
|
10
|
-
autoUpdateMethods: { getLastVersion: (platform: string) => Promise<any> },
|
|
11
|
-
clientNameSetter?: (clientName: string | undefined) => void,
|
|
12
|
-
): void;
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
| Parameter | Type | Description |
|
|
16
|
-
|-----------|------|-------------|
|
|
17
|
-
| `socket` | `WebSocket` | Raw WebSocket connection from the V1 client |
|
|
18
|
-
| `autoUpdateMethods` | `{ getLastVersion: (platform: string) => Promise<any> }` | Auto-update method implementation |
|
|
19
|
-
| `clientNameSetter` | `((clientName: string \| undefined) => void)?` | Optional callback invoked with the client name when connected (or `undefined` on disconnect) |
|
|
20
|
-
|
|
21
|
-
This handler is provided for backward compatibility with V1 protocol clients. New clients should use the V2 binary protocol.
|