@simplysm/service-server 13.0.0-beta.22 → 13.0.0-beta.23
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 +296 -71
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -16,61 +16,62 @@ pnpm add @simplysm/service-server
|
|
|
16
16
|
|
|
17
17
|
### Core Classes
|
|
18
18
|
|
|
19
|
-
| Module |
|
|
20
|
-
|
|
21
|
-
| `ServiceServer` |
|
|
22
|
-
| `ServiceBase` |
|
|
23
|
-
| `ServiceExecutor` |
|
|
19
|
+
| Module | Description |
|
|
20
|
+
|--------|------|
|
|
21
|
+
| `ServiceServer` | Main server class. Creates Fastify instance and configures routes/plugins |
|
|
22
|
+
| `ServiceBase` | Service base abstract class. All custom services must inherit from this |
|
|
23
|
+
| `ServiceExecutor` | Internal executor that handles service method discovery, auth checks, and execution |
|
|
24
24
|
|
|
25
25
|
### Authentication
|
|
26
26
|
|
|
27
|
-
| Module |
|
|
28
|
-
|
|
29
|
-
| `JwtManager` |
|
|
30
|
-
| `Authorize` |
|
|
31
|
-
| `
|
|
27
|
+
| Module | Description |
|
|
28
|
+
|--------|------|
|
|
29
|
+
| `JwtManager` | JWT token generation/verification/decoding based on jose library (HS256, 12-hour expiration) |
|
|
30
|
+
| `Authorize` | Stage 3 decorator. Sets authentication permissions at class or method level |
|
|
31
|
+
| `getAuthPermissions` | Queries auth permissions for a service class/method (used internally by `ServiceExecutor`) |
|
|
32
|
+
| `AuthTokenPayload` | JWT payload interface (includes `roles`, `data`) |
|
|
32
33
|
|
|
33
34
|
### Transport Layer - WebSocket
|
|
34
35
|
|
|
35
|
-
| Module |
|
|
36
|
-
|
|
37
|
-
| `WebSocketHandler` |
|
|
38
|
-
| `ServiceSocket` |
|
|
36
|
+
| Module | Description |
|
|
37
|
+
|--------|------|
|
|
38
|
+
| `WebSocketHandler` | Handles WebSocket connection management, message routing, and event distribution |
|
|
39
|
+
| `ServiceSocket` | Wraps individual WebSocket connections. Manages ping/pong, protocol encoding/decoding, event listener management |
|
|
39
40
|
|
|
40
41
|
### Transport Layer - HTTP
|
|
41
42
|
|
|
42
|
-
| Module |
|
|
43
|
-
|
|
44
|
-
| `HttpRequestHandler` |
|
|
45
|
-
| `UploadHandler` |
|
|
46
|
-
| `StaticFileHandler` |
|
|
43
|
+
| Module | Description |
|
|
44
|
+
|--------|------|
|
|
45
|
+
| `HttpRequestHandler` | Calls service methods via HTTP at `/api/:service/:method` route |
|
|
46
|
+
| `UploadHandler` | Handles multipart file upload at `/upload` route (auth required) |
|
|
47
|
+
| `StaticFileHandler` | Serves static files. Prevents path traversal and blocks hidden files |
|
|
47
48
|
|
|
48
49
|
### Protocol
|
|
49
50
|
|
|
50
|
-
| Module |
|
|
51
|
-
|
|
52
|
-
| `ProtocolWrapper` |
|
|
51
|
+
| Module | Description |
|
|
52
|
+
|--------|------|
|
|
53
|
+
| `ProtocolWrapper` | Message encoding/decoding wrapper. Messages over 30KB are processed in worker threads |
|
|
53
54
|
|
|
54
55
|
### Built-in Services
|
|
55
56
|
|
|
56
|
-
| Module |
|
|
57
|
-
|
|
58
|
-
| `OrmService` |
|
|
59
|
-
| `CryptoService` |
|
|
60
|
-
| `SmtpService` |
|
|
61
|
-
| `AutoUpdateService` |
|
|
57
|
+
| Module | Description |
|
|
58
|
+
|--------|------|
|
|
59
|
+
| `OrmService` | DB connection/transaction/query execution (WebSocket only, auth required) |
|
|
60
|
+
| `CryptoService` | SHA256 hash and AES-256-CBC encryption/decryption |
|
|
61
|
+
| `SmtpService` | nodemailer-based email sending |
|
|
62
|
+
| `AutoUpdateService` | App auto-update (provides latest version query and download path) |
|
|
62
63
|
|
|
63
64
|
### Utilities
|
|
64
65
|
|
|
65
|
-
| Module |
|
|
66
|
-
|
|
67
|
-
| `ConfigManager` |
|
|
66
|
+
| Module | Description |
|
|
67
|
+
|--------|------|
|
|
68
|
+
| `ConfigManager` | JSON config file loading/caching/real-time monitoring (auto expiration based on LazyGcMap) |
|
|
68
69
|
|
|
69
70
|
### Legacy
|
|
70
71
|
|
|
71
|
-
| Module |
|
|
72
|
-
|
|
73
|
-
| `handleV1Connection` |
|
|
72
|
+
| Module | Description |
|
|
73
|
+
|--------|------|
|
|
74
|
+
| `handleV1Connection` | V1 protocol client compatibility handling (supports auto-update only) |
|
|
74
75
|
|
|
75
76
|
## Usage
|
|
76
77
|
|
|
@@ -101,9 +102,41 @@ server.on("close", () => {
|
|
|
101
102
|
await server.close();
|
|
102
103
|
```
|
|
103
104
|
|
|
105
|
+
### ServiceServer
|
|
106
|
+
|
|
107
|
+
`ServiceServer<TAuthInfo>` extends `EventEmitter` and is the main entry point for creating a server.
|
|
108
|
+
|
|
109
|
+
**Properties:**
|
|
110
|
+
|
|
111
|
+
| Property | Type | Description |
|
|
112
|
+
|----------|------|------|
|
|
113
|
+
| `options` | `ServiceServerOptions` | Server configuration (read-only, passed via constructor) |
|
|
114
|
+
| `isOpen` | `boolean` | Whether the server is currently listening |
|
|
115
|
+
| `fastify` | `FastifyInstance` | Underlying Fastify instance (read-only, for advanced use) |
|
|
116
|
+
|
|
117
|
+
**Methods:**
|
|
118
|
+
|
|
119
|
+
| Method | Returns | Description |
|
|
120
|
+
|--------|---------|------|
|
|
121
|
+
| `listen()` | `Promise<void>` | Register all plugins/routes and start listening |
|
|
122
|
+
| `close()` | `Promise<void>` | Close all WebSocket connections and shut down the server |
|
|
123
|
+
| `generateAuthToken(payload)` | `Promise<string>` | Generate a JWT token (HS256, 12-hour expiration) |
|
|
124
|
+
| `verifyAuthToken(token)` | `Promise<AuthTokenPayload<TAuthInfo>>` | Verify and decode a JWT token |
|
|
125
|
+
| `emitEvent(eventType, infoSelector, data)` | `Promise<void>` | Publish an event to matching WebSocket clients |
|
|
126
|
+
| `broadcastReload(clientName, changedFileSet)` | `Promise<void>` | Send a reload command to all connected clients |
|
|
127
|
+
|
|
128
|
+
**Events:**
|
|
129
|
+
|
|
130
|
+
| Event | Payload | Description |
|
|
131
|
+
|-------|---------|------|
|
|
132
|
+
| `ready` | `void` | Emitted when the server starts listening |
|
|
133
|
+
| `close` | `void` | Emitted when the server is closed |
|
|
134
|
+
|
|
104
135
|
### Server Options (`ServiceServerOptions`)
|
|
105
136
|
|
|
106
137
|
```typescript
|
|
138
|
+
import type { ServiceServerOptions } from "@simplysm/service-server";
|
|
139
|
+
|
|
107
140
|
interface ServiceServerOptions {
|
|
108
141
|
/** Server root path (base directory for static files and config files) */
|
|
109
142
|
rootPath: string;
|
|
@@ -138,6 +171,7 @@ rootPath/
|
|
|
138
171
|
### SSL/HTTPS Server
|
|
139
172
|
|
|
140
173
|
```typescript
|
|
174
|
+
import { ServiceServer } from "@simplysm/service-server";
|
|
141
175
|
import { fsReadFile } from "@simplysm/core-node";
|
|
142
176
|
|
|
143
177
|
const pfxBytes = await fsReadFile("/path/to/cert.pfx");
|
|
@@ -174,22 +208,32 @@ class MyService extends ServiceBase {
|
|
|
174
208
|
}
|
|
175
209
|
```
|
|
176
210
|
|
|
177
|
-
|
|
211
|
+
#### ServiceBase Properties
|
|
212
|
+
|
|
213
|
+
`ServiceBase<TAuthInfo>` is an abstract class. The generic `TAuthInfo` type represents the shape of the authenticated user's data stored in the JWT token.
|
|
178
214
|
|
|
179
215
|
| Property | Type | Description |
|
|
180
|
-
|
|
181
|
-
| `this.server` | `ServiceServer
|
|
216
|
+
|----------|------|------|
|
|
217
|
+
| `this.server` | `ServiceServer<TAuthInfo>` | Server instance reference |
|
|
182
218
|
| `this.socket` | `ServiceSocket \| undefined` | WebSocket connection (`undefined` for HTTP calls) |
|
|
183
|
-
| `this.http` | `{ clientName
|
|
184
|
-
| `this.authInfo` | `TAuthInfo \| undefined` | Authenticated user
|
|
185
|
-
| `this.clientName` | `string \| undefined` | Client app name |
|
|
186
|
-
| `this.clientPath` | `string \| undefined` |
|
|
219
|
+
| `this.http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> } \| undefined` | HTTP request context |
|
|
220
|
+
| `this.authInfo` | `TAuthInfo \| undefined` | Authenticated user's custom data (from JWT `data` field) |
|
|
221
|
+
| `this.clientName` | `string \| undefined` | Client app name (validated against path traversal) |
|
|
222
|
+
| `this.clientPath` | `string \| undefined` | Resolved per-client directory path (`rootPath/www/{clientName}`) |
|
|
223
|
+
|
|
224
|
+
#### ServiceBase Methods
|
|
225
|
+
|
|
226
|
+
| Method | Returns | Description |
|
|
227
|
+
|--------|---------|------|
|
|
228
|
+
| `getConfig<T>(section)` | `Promise<T>` | Read a section from `.config.json` (root + client configs merged) |
|
|
187
229
|
|
|
188
230
|
### Config File Reference
|
|
189
231
|
|
|
190
232
|
Read sections from `.config.json` files using `ServiceBase.getConfig()`. Root and per-client configs are automatically merged.
|
|
191
233
|
|
|
192
234
|
```typescript
|
|
235
|
+
import { ServiceBase } from "@simplysm/service-server";
|
|
236
|
+
|
|
193
237
|
class MyService extends ServiceBase {
|
|
194
238
|
async getDbHost(): Promise<string> {
|
|
195
239
|
// Read "mySection" key from rootPath/.config.json or clientPath/.config.json
|
|
@@ -262,26 +306,65 @@ Decorator behavior:
|
|
|
262
306
|
|
|
263
307
|
Method-level decorators override class-level settings.
|
|
264
308
|
|
|
309
|
+
#### `getAuthPermissions`
|
|
310
|
+
|
|
311
|
+
Query auth permissions for a given service class and method. Primarily used internally by `ServiceExecutor`, but exported for advanced use cases.
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { getAuthPermissions } from "@simplysm/service-server";
|
|
315
|
+
|
|
316
|
+
// Returns string[] if permissions are set, or undefined for public (no decorator)
|
|
317
|
+
const perms = getAuthPermissions(UserService, "deleteUser");
|
|
318
|
+
// ["admin"]
|
|
319
|
+
|
|
320
|
+
const classPerms = getAuthPermissions(UserService);
|
|
321
|
+
// [] (empty array = login required, no specific role)
|
|
322
|
+
|
|
323
|
+
const publicPerms = getAuthPermissions(PublicService, "healthCheck");
|
|
324
|
+
// undefined (no auth required)
|
|
325
|
+
```
|
|
326
|
+
|
|
265
327
|
### JWT Token Management
|
|
266
328
|
|
|
267
|
-
|
|
329
|
+
#### JwtManager
|
|
330
|
+
|
|
331
|
+
`JwtManager<TAuthInfo>` handles JWT operations internally. Access its functionality through `ServiceServer` methods.
|
|
332
|
+
|
|
333
|
+
| Method | Returns | Description |
|
|
334
|
+
|--------|---------|------|
|
|
335
|
+
| `sign(payload)` | `Promise<string>` | Generate a JWT token (HS256, 12-hour expiration) |
|
|
336
|
+
| `verify(token)` | `Promise<AuthTokenPayload<TAuthInfo>>` | Verify token signature and expiration, return payload |
|
|
337
|
+
| `decode(token)` | `AuthTokenPayload<TAuthInfo>` | Decode token without verification (synchronous) |
|
|
338
|
+
|
|
339
|
+
Generate and verify JWT tokens through the `ServiceServer` instance:
|
|
268
340
|
|
|
269
341
|
```typescript
|
|
342
|
+
import { ServiceServer } from "@simplysm/service-server";
|
|
343
|
+
|
|
344
|
+
const server = new ServiceServer({
|
|
345
|
+
port: 8080,
|
|
346
|
+
rootPath: "/app/data",
|
|
347
|
+
auth: { jwtSecret: "my-secret-key" },
|
|
348
|
+
services: [],
|
|
349
|
+
});
|
|
350
|
+
|
|
270
351
|
// Generate token (12-hour expiration, HS256 algorithm)
|
|
271
352
|
const token = await server.generateAuthToken({
|
|
272
353
|
roles: ["admin", "user"],
|
|
273
|
-
data: { userId: 1, name: "
|
|
354
|
+
data: { userId: 1, name: "John" },
|
|
274
355
|
});
|
|
275
356
|
|
|
276
357
|
// Verify token
|
|
277
358
|
const payload = await server.verifyAuthToken(token);
|
|
278
359
|
// payload.roles: ["admin", "user"]
|
|
279
|
-
// payload.data: { userId: 1, name: "
|
|
360
|
+
// payload.data: { userId: 1, name: "John" }
|
|
280
361
|
```
|
|
281
362
|
|
|
282
|
-
`AuthTokenPayload`
|
|
363
|
+
#### `AuthTokenPayload`
|
|
283
364
|
|
|
284
365
|
```typescript
|
|
366
|
+
import type { AuthTokenPayload } from "@simplysm/service-server";
|
|
367
|
+
|
|
285
368
|
interface AuthTokenPayload<TAuthInfo = unknown> extends JWTPayload {
|
|
286
369
|
/** User role list (used for permission check in Authorize decorator) */
|
|
287
370
|
roles: string[];
|
|
@@ -290,6 +373,37 @@ interface AuthTokenPayload<TAuthInfo = unknown> extends JWTPayload {
|
|
|
290
373
|
}
|
|
291
374
|
```
|
|
292
375
|
|
|
376
|
+
### ServiceSocket
|
|
377
|
+
|
|
378
|
+
`ServiceSocket` extends `EventEmitter` and wraps an individual WebSocket connection. It is available in service methods as `this.socket` when the request comes via WebSocket.
|
|
379
|
+
|
|
380
|
+
**Properties:**
|
|
381
|
+
|
|
382
|
+
| Property | Type | Description |
|
|
383
|
+
|----------|------|------|
|
|
384
|
+
| `clientName` | `string` | Client app name (from WebSocket query parameter) |
|
|
385
|
+
| `connectedAtDateTime` | `DateTime` | Connection timestamp |
|
|
386
|
+
| `authTokenPayload` | `AuthTokenPayload \| undefined` | Authenticated token payload (set after `auth` message) |
|
|
387
|
+
| `connReq` | `FastifyRequest` | Original Fastify request that initiated the WebSocket upgrade |
|
|
388
|
+
|
|
389
|
+
**Methods:**
|
|
390
|
+
|
|
391
|
+
| Method | Returns | Description |
|
|
392
|
+
|--------|---------|------|
|
|
393
|
+
| `send(uuid, msg)` | `Promise<number>` | Send a message to this client. Returns total bytes sent |
|
|
394
|
+
| `close()` | `void` | Terminate the WebSocket connection |
|
|
395
|
+
| `addEventListener(key, eventName, info)` | `void` | Register an event listener for this socket |
|
|
396
|
+
| `removeEventListener(key)` | `void` | Remove an event listener by key |
|
|
397
|
+
| `getEventListeners(eventName)` | `{ key, info }[]` | Get all event listeners for a given event name |
|
|
398
|
+
|
|
399
|
+
**Events:**
|
|
400
|
+
|
|
401
|
+
| Event | Payload | Description |
|
|
402
|
+
|-------|---------|------|
|
|
403
|
+
| `error` | `Error` | WebSocket error occurred |
|
|
404
|
+
| `close` | `number` | Connection closed (payload is the close code) |
|
|
405
|
+
| `message` | `{ uuid: string; msg: ServiceClientMessage }` | Decoded message received from client |
|
|
406
|
+
|
|
293
407
|
### HTTP API Call
|
|
294
408
|
|
|
295
409
|
Service methods can also be called via HTTP through the `/api/:service/:method` path.
|
|
@@ -345,6 +459,7 @@ Uploaded files are stored in the `rootPath/www/uploads/` directory with UUID-bas
|
|
|
345
459
|
Publish events to connected clients from the server.
|
|
346
460
|
|
|
347
461
|
```typescript
|
|
462
|
+
import { ServiceServer } from "@simplysm/service-server";
|
|
348
463
|
import { ServiceEventListener } from "@simplysm/service-common";
|
|
349
464
|
|
|
350
465
|
// Event definition (from service-common)
|
|
@@ -371,6 +486,8 @@ await server.broadcastReload("my-app", new Set(["main.js"]));
|
|
|
371
486
|
Provides database connection/query/transaction via WebSocket. `@Authorize()` decorator is applied, requiring login.
|
|
372
487
|
|
|
373
488
|
```typescript
|
|
489
|
+
import { ServiceServer, OrmService } from "@simplysm/service-server";
|
|
490
|
+
|
|
374
491
|
const server = new ServiceServer({
|
|
375
492
|
port: 8080,
|
|
376
493
|
rootPath: "/app/data",
|
|
@@ -398,17 +515,17 @@ Define ORM config in `.config.json`:
|
|
|
398
515
|
|
|
399
516
|
Methods provided by `OrmService`:
|
|
400
517
|
|
|
401
|
-
| Method | Description |
|
|
402
|
-
|
|
403
|
-
| `getInfo(opt)` |
|
|
404
|
-
| `connect(opt)` | Create DB connection. Returns connection ID |
|
|
405
|
-
| `close(connId)` | Close DB connection |
|
|
406
|
-
| `beginTransaction(connId, isolationLevel?)` | Begin transaction |
|
|
407
|
-
| `commitTransaction(connId)` | Commit transaction |
|
|
408
|
-
| `rollbackTransaction(connId)` | Rollback transaction |
|
|
409
|
-
| `executeParametrized(connId, query, params?)` | Execute parameterized query |
|
|
410
|
-
| `executeDefs(connId, defs, options?)` | Execute QueryDef-based queries |
|
|
411
|
-
| `bulkInsert(connId, tableName, columnDefs, records)` | Bulk INSERT |
|
|
518
|
+
| Method | Returns | Description |
|
|
519
|
+
|--------|---------|------|
|
|
520
|
+
| `getInfo(opt)` | `Promise<{ dialect, database?, schema? }>` | Query DB connection info |
|
|
521
|
+
| `connect(opt)` | `Promise<number>` | Create DB connection. Returns connection ID |
|
|
522
|
+
| `close(connId)` | `Promise<void>` | Close DB connection |
|
|
523
|
+
| `beginTransaction(connId, isolationLevel?)` | `Promise<void>` | Begin transaction |
|
|
524
|
+
| `commitTransaction(connId)` | `Promise<void>` | Commit transaction |
|
|
525
|
+
| `rollbackTransaction(connId)` | `Promise<void>` | Rollback transaction |
|
|
526
|
+
| `executeParametrized(connId, query, params?)` | `Promise<unknown[][]>` | Execute parameterized query |
|
|
527
|
+
| `executeDefs(connId, defs, options?)` | `Promise<unknown[][]>` | Execute QueryDef-based queries |
|
|
528
|
+
| `bulkInsert(connId, tableName, columnDefs, records)` | `Promise<void>` | Bulk INSERT |
|
|
412
529
|
|
|
413
530
|
When a WebSocket connection is closed, all DB connections opened from that socket are automatically cleaned up.
|
|
414
531
|
|
|
@@ -417,6 +534,8 @@ When a WebSocket connection is closed, all DB connections opened from that socke
|
|
|
417
534
|
Provides SHA256 hash and AES-256-CBC symmetric key encryption/decryption.
|
|
418
535
|
|
|
419
536
|
```typescript
|
|
537
|
+
import { ServiceServer, CryptoService } from "@simplysm/service-server";
|
|
538
|
+
|
|
420
539
|
const server = new ServiceServer({
|
|
421
540
|
port: 8080,
|
|
422
541
|
rootPath: "/app/data",
|
|
@@ -434,17 +553,19 @@ const server = new ServiceServer({
|
|
|
434
553
|
}
|
|
435
554
|
```
|
|
436
555
|
|
|
437
|
-
| Method | Description |
|
|
438
|
-
|
|
439
|
-
| `encrypt(data)` | Generate SHA256 HMAC hash (one-way) |
|
|
440
|
-
| `encryptAes(data)` | AES-256-CBC encryption. Returns hex string in `iv:encrypted` format |
|
|
441
|
-
| `decryptAes(encText)` | AES-256-CBC decryption. Returns original binary |
|
|
556
|
+
| Method | Returns | Description |
|
|
557
|
+
|--------|---------|------|
|
|
558
|
+
| `encrypt(data)` | `Promise<string>` | Generate SHA256 HMAC hash (one-way). `data` is `string \| Uint8Array` |
|
|
559
|
+
| `encryptAes(data)` | `Promise<string>` | AES-256-CBC encryption. `data` is `Uint8Array`. Returns hex string in `iv:encrypted` format |
|
|
560
|
+
| `decryptAes(encText)` | `Promise<Uint8Array>` | AES-256-CBC decryption. Returns original binary |
|
|
442
561
|
|
|
443
562
|
### Built-in Service: SmtpService
|
|
444
563
|
|
|
445
564
|
A nodemailer-based email sending service. Can pass SMTP config directly or reference server config file.
|
|
446
565
|
|
|
447
566
|
```typescript
|
|
567
|
+
import { ServiceServer, SmtpService } from "@simplysm/service-server";
|
|
568
|
+
|
|
448
569
|
const server = new ServiceServer({
|
|
449
570
|
port: 8080,
|
|
450
571
|
rootPath: "/app/data",
|
|
@@ -470,10 +591,10 @@ const server = new ServiceServer({
|
|
|
470
591
|
}
|
|
471
592
|
```
|
|
472
593
|
|
|
473
|
-
| Method | Description |
|
|
474
|
-
|
|
475
|
-
| `send(options)` | Send email by directly passing SMTP config |
|
|
476
|
-
| `sendByConfig(configName, options)` | Send email by referencing SMTP config in config file |
|
|
594
|
+
| Method | Returns | Description |
|
|
595
|
+
|--------|---------|------|
|
|
596
|
+
| `send(options)` | `Promise<string>` | Send email by directly passing SMTP config. Returns message ID |
|
|
597
|
+
| `sendByConfig(configName, options)` | `Promise<string>` | Send email by referencing SMTP config in config file. Returns message ID |
|
|
477
598
|
|
|
478
599
|
`send()` options:
|
|
479
600
|
|
|
@@ -499,6 +620,8 @@ interface SmtpSendOption {
|
|
|
499
620
|
Supports auto-update for client apps. Searches for latest version files by platform in the client directory.
|
|
500
621
|
|
|
501
622
|
```typescript
|
|
623
|
+
import { ServiceServer, AutoUpdateService } from "@simplysm/service-server";
|
|
624
|
+
|
|
502
625
|
const server = new ServiceServer({
|
|
503
626
|
port: 8080,
|
|
504
627
|
rootPath: "/app/data",
|
|
@@ -516,16 +639,16 @@ rootPath/www/{clientName}/{platform}/updates/
|
|
|
516
639
|
1.0.1.apk
|
|
517
640
|
```
|
|
518
641
|
|
|
519
|
-
| Method | Description |
|
|
520
|
-
|
|
521
|
-
| `getLastVersion(platform)` | Returns latest version and download path for the platform. Returns `undefined` if no update |
|
|
642
|
+
| Method | Returns | Description |
|
|
643
|
+
|--------|---------|------|
|
|
644
|
+
| `getLastVersion(platform)` | `Promise<{ version: string; downloadPath: string } \| undefined>` | Returns latest version and download path for the platform. Returns `undefined` if no update |
|
|
522
645
|
|
|
523
|
-
Return value:
|
|
646
|
+
Return value example:
|
|
524
647
|
|
|
525
648
|
```typescript
|
|
526
649
|
{
|
|
527
|
-
version:
|
|
528
|
-
downloadPath:
|
|
650
|
+
version: "1.0.1",
|
|
651
|
+
downloadPath: "/my-app/android/updates/1.0.1.apk",
|
|
529
652
|
}
|
|
530
653
|
```
|
|
531
654
|
|
|
@@ -536,18 +659,47 @@ A static utility class that manages loading, caching, and real-time monitoring o
|
|
|
536
659
|
```typescript
|
|
537
660
|
import { ConfigManager } from "@simplysm/service-server";
|
|
538
661
|
|
|
662
|
+
// Returns undefined if the file does not exist
|
|
539
663
|
const config = await ConfigManager.getConfig<MyConfig>("/path/to/.config.json");
|
|
540
664
|
```
|
|
541
665
|
|
|
666
|
+
| Method | Returns | Description |
|
|
667
|
+
|--------|---------|------|
|
|
668
|
+
| `ConfigManager.getConfig<T>(filePath)` | `Promise<T \| undefined>` | Load and cache a JSON config file. Returns `undefined` if file not found |
|
|
669
|
+
|
|
542
670
|
Behavior:
|
|
543
671
|
- Caches file in `LazyGcMap` on first load.
|
|
544
672
|
- Registers file change watch (`FsWatcher`) to auto-refresh cache on changes.
|
|
545
673
|
- Cache auto-expires after 1 hour of no access, and associated watch is released.
|
|
674
|
+
- GC runs every 10 minutes to check for expired entries.
|
|
546
675
|
|
|
547
676
|
### ProtocolWrapper
|
|
548
677
|
|
|
549
678
|
Handles encoding/decoding of WebSocket messages. Automatically branches between main thread and worker thread based on message size.
|
|
550
679
|
|
|
680
|
+
```typescript
|
|
681
|
+
import { ProtocolWrapper } from "@simplysm/service-server";
|
|
682
|
+
|
|
683
|
+
const protocol = new ProtocolWrapper();
|
|
684
|
+
|
|
685
|
+
// Encode a message into chunks
|
|
686
|
+
const { chunks, totalSize } = await protocol.encode(uuid, message);
|
|
687
|
+
|
|
688
|
+
// Decode received bytes
|
|
689
|
+
const result = await protocol.decode(bytes);
|
|
690
|
+
|
|
691
|
+
// Clean up
|
|
692
|
+
protocol.dispose();
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
| Method | Returns | Description |
|
|
696
|
+
|--------|---------|------|
|
|
697
|
+
| `encode(uuid, message)` | `Promise<{ chunks: Uint8Array[]; totalSize: number }>` | Encode a message into transmittable chunks |
|
|
698
|
+
| `decode(bytes)` | `Promise<ServiceMessageDecodeResult>` | Decode received bytes into a message |
|
|
699
|
+
| `dispose()` | `void` | Clean up internal protocol resources |
|
|
700
|
+
|
|
701
|
+
Worker thread branching:
|
|
702
|
+
|
|
551
703
|
| Condition | Processing Method |
|
|
552
704
|
|------|-----------|
|
|
553
705
|
| 30KB or less | Processed directly in main thread |
|
|
@@ -555,6 +707,79 @@ Handles encoding/decoding of WebSocket messages. Automatically branches between
|
|
|
555
707
|
|
|
556
708
|
Messages containing large binary data (Uint8Array) also branch to worker thread.
|
|
557
709
|
|
|
710
|
+
### Legacy: handleV1Connection
|
|
711
|
+
|
|
712
|
+
Handles V1 protocol WebSocket clients. Only supports the `SdAutoUpdateService.getLastVersion` command. All other requests return an upgrade-required error.
|
|
713
|
+
|
|
714
|
+
```typescript
|
|
715
|
+
import { handleV1Connection, AutoUpdateService } from "@simplysm/service-server";
|
|
716
|
+
|
|
717
|
+
// Used internally by ServiceServer for WebSocket connections without ver=2 query parameter
|
|
718
|
+
handleV1Connection(webSocket, autoUpdateService);
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
## Full Server Example
|
|
722
|
+
|
|
723
|
+
```typescript
|
|
724
|
+
import { ServiceServer, ServiceBase, Authorize, OrmService, CryptoService } from "@simplysm/service-server";
|
|
725
|
+
import { ServiceEventListener } from "@simplysm/service-common";
|
|
726
|
+
|
|
727
|
+
// Define a custom service
|
|
728
|
+
@Authorize()
|
|
729
|
+
class UserService extends ServiceBase<{ userId: number; role: string }> {
|
|
730
|
+
async getProfile(): Promise<{ name: string }> {
|
|
731
|
+
const userId = this.authInfo?.userId;
|
|
732
|
+
// Use this.getConfig(), this.socket, this.server, etc.
|
|
733
|
+
return { name: "John" };
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
@Authorize(["admin"])
|
|
737
|
+
async deleteUser(targetId: number): Promise<void> {
|
|
738
|
+
// Admin-only operation
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
class PublicService extends ServiceBase {
|
|
743
|
+
async healthCheck(): Promise<string> {
|
|
744
|
+
return "OK";
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Create and start server
|
|
749
|
+
const server = new ServiceServer({
|
|
750
|
+
port: 8080,
|
|
751
|
+
rootPath: "/app/data",
|
|
752
|
+
auth: { jwtSecret: "my-secret-key" },
|
|
753
|
+
services: [UserService, PublicService, OrmService, CryptoService],
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
server.on("ready", () => {
|
|
757
|
+
console.log("Server is ready on port 8080");
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
await server.listen();
|
|
761
|
+
|
|
762
|
+
// Generate auth token for a user
|
|
763
|
+
const token = await server.generateAuthToken({
|
|
764
|
+
roles: ["admin"],
|
|
765
|
+
data: { userId: 1, role: "admin" },
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
// Emit events to connected clients
|
|
769
|
+
class UserUpdatedEvent extends ServiceEventListener<
|
|
770
|
+
{ userId: number },
|
|
771
|
+
{ action: string }
|
|
772
|
+
> {
|
|
773
|
+
readonly eventName = "UserUpdatedEvent";
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
await server.emitEvent(
|
|
777
|
+
UserUpdatedEvent,
|
|
778
|
+
(info) => info.userId === 1,
|
|
779
|
+
{ action: "profile-updated" },
|
|
780
|
+
);
|
|
781
|
+
```
|
|
782
|
+
|
|
558
783
|
## Server Route Structure
|
|
559
784
|
|
|
560
785
|
The following routes are automatically registered when `ServiceServer.listen()` is called:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/service-server",
|
|
3
3
|
"sideEffects": false,
|
|
4
|
-
"version": "13.0.0-beta.
|
|
4
|
+
"version": "13.0.0-beta.23",
|
|
5
5
|
"description": "심플리즘 패키지 - 서비스 모듈 (server)",
|
|
6
6
|
"author": "김석래",
|
|
7
7
|
"repository": {
|
|
@@ -33,11 +33,11 @@
|
|
|
33
33
|
"semver": "^7.7.4",
|
|
34
34
|
"utf-8-validate": "^6.0.6",
|
|
35
35
|
"ws": "^8.19.0",
|
|
36
|
-
"@simplysm/core-common": "13.0.0-beta.
|
|
37
|
-
"@simplysm/core-node": "13.0.0-beta.
|
|
38
|
-
"@simplysm/orm-common": "13.0.0-beta.
|
|
39
|
-
"@simplysm/orm-node": "13.0.0-beta.
|
|
40
|
-
"@simplysm/service-common": "13.0.0-beta.
|
|
36
|
+
"@simplysm/core-common": "13.0.0-beta.23",
|
|
37
|
+
"@simplysm/core-node": "13.0.0-beta.23",
|
|
38
|
+
"@simplysm/orm-common": "13.0.0-beta.23",
|
|
39
|
+
"@simplysm/orm-node": "13.0.0-beta.23",
|
|
40
|
+
"@simplysm/service-common": "13.0.0-beta.23"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/nodemailer": "^7.0.9",
|