@simplysm/service-server 13.0.0-beta.6
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/.cache/typecheck-node.tsbuildinfo +1 -0
- package/README.md +587 -0
- package/dist/auth/auth-token-payload.js +1 -0
- package/dist/auth/auth-token-payload.js.map +7 -0
- package/dist/auth/auth.decorators.js +46 -0
- package/dist/auth/auth.decorators.js.map +7 -0
- package/dist/auth/jwt-manager.js +35 -0
- package/dist/auth/jwt-manager.js.map +7 -0
- package/dist/core/service-base.js +47 -0
- package/dist/core/service-base.js.map +7 -0
- package/dist/core/service-executor.js +46 -0
- package/dist/core/service-executor.js.map +7 -0
- package/dist/core-common/src/common.types.d.ts +74 -0
- package/dist/core-common/src/common.types.d.ts.map +1 -0
- package/dist/core-common/src/env.d.ts +6 -0
- package/dist/core-common/src/env.d.ts.map +1 -0
- package/dist/core-common/src/errors/argument-error.d.ts +25 -0
- package/dist/core-common/src/errors/argument-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/not-implemented-error.d.ts +29 -0
- package/dist/core-common/src/errors/not-implemented-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/sd-error.d.ts +27 -0
- package/dist/core-common/src/errors/sd-error.d.ts.map +1 -0
- package/dist/core-common/src/errors/timeout-error.d.ts +31 -0
- package/dist/core-common/src/errors/timeout-error.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.d.ts +15 -0
- package/dist/core-common/src/extensions/arr-ext.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.helpers.d.ts +19 -0
- package/dist/core-common/src/extensions/arr-ext.helpers.d.ts.map +1 -0
- package/dist/core-common/src/extensions/arr-ext.types.d.ts +215 -0
- package/dist/core-common/src/extensions/arr-ext.types.d.ts.map +1 -0
- package/dist/core-common/src/extensions/map-ext.d.ts +57 -0
- package/dist/core-common/src/extensions/map-ext.d.ts.map +1 -0
- package/dist/core-common/src/extensions/set-ext.d.ts +36 -0
- package/dist/core-common/src/extensions/set-ext.d.ts.map +1 -0
- package/dist/core-common/src/features/debounce-queue.d.ts +53 -0
- package/dist/core-common/src/features/debounce-queue.d.ts.map +1 -0
- package/dist/core-common/src/features/event-emitter.d.ts +66 -0
- package/dist/core-common/src/features/event-emitter.d.ts.map +1 -0
- package/dist/core-common/src/features/serial-queue.d.ts +47 -0
- package/dist/core-common/src/features/serial-queue.d.ts.map +1 -0
- package/dist/core-common/src/index.d.ts +32 -0
- package/dist/core-common/src/index.d.ts.map +1 -0
- package/dist/core-common/src/types/date-only.d.ts +152 -0
- package/dist/core-common/src/types/date-only.d.ts.map +1 -0
- package/dist/core-common/src/types/date-time.d.ts +96 -0
- package/dist/core-common/src/types/date-time.d.ts.map +1 -0
- package/dist/core-common/src/types/lazy-gc-map.d.ts +80 -0
- package/dist/core-common/src/types/lazy-gc-map.d.ts.map +1 -0
- package/dist/core-common/src/types/time.d.ts +68 -0
- package/dist/core-common/src/types/time.d.ts.map +1 -0
- package/dist/core-common/src/types/uuid.d.ts +35 -0
- package/dist/core-common/src/types/uuid.d.ts.map +1 -0
- package/dist/core-common/src/utils/bytes.d.ts +51 -0
- package/dist/core-common/src/utils/bytes.d.ts.map +1 -0
- package/dist/core-common/src/utils/date-format.d.ts +90 -0
- package/dist/core-common/src/utils/date-format.d.ts.map +1 -0
- package/dist/core-common/src/utils/json.d.ts +34 -0
- package/dist/core-common/src/utils/json.d.ts.map +1 -0
- package/dist/core-common/src/utils/num.d.ts +60 -0
- package/dist/core-common/src/utils/num.d.ts.map +1 -0
- package/dist/core-common/src/utils/obj.d.ts +258 -0
- package/dist/core-common/src/utils/obj.d.ts.map +1 -0
- package/dist/core-common/src/utils/path.d.ts +23 -0
- package/dist/core-common/src/utils/path.d.ts.map +1 -0
- package/dist/core-common/src/utils/primitive.d.ts +18 -0
- package/dist/core-common/src/utils/primitive.d.ts.map +1 -0
- package/dist/core-common/src/utils/str.d.ts +103 -0
- package/dist/core-common/src/utils/str.d.ts.map +1 -0
- package/dist/core-common/src/utils/template-strings.d.ts +84 -0
- package/dist/core-common/src/utils/template-strings.d.ts.map +1 -0
- package/dist/core-common/src/utils/transferable.d.ts +47 -0
- package/dist/core-common/src/utils/transferable.d.ts.map +1 -0
- package/dist/core-common/src/utils/wait.d.ts +19 -0
- package/dist/core-common/src/utils/wait.d.ts.map +1 -0
- package/dist/core-common/src/utils/xml.d.ts +36 -0
- package/dist/core-common/src/utils/xml.d.ts.map +1 -0
- package/dist/core-common/src/zip/sd-zip.d.ts +80 -0
- package/dist/core-common/src/zip/sd-zip.d.ts.map +1 -0
- package/dist/core-node/src/features/fs-watcher.d.ts +70 -0
- package/dist/core-node/src/features/fs-watcher.d.ts.map +1 -0
- package/dist/core-node/src/index.d.ts +7 -0
- package/dist/core-node/src/index.d.ts.map +1 -0
- package/dist/core-node/src/utils/fs.d.ts +197 -0
- package/dist/core-node/src/utils/fs.d.ts.map +1 -0
- package/dist/core-node/src/utils/path.d.ts +75 -0
- package/dist/core-node/src/utils/path.d.ts.map +1 -0
- package/dist/core-node/src/worker/create-worker.d.ts +23 -0
- package/dist/core-node/src/worker/create-worker.d.ts.map +1 -0
- package/dist/core-node/src/worker/types.d.ts +67 -0
- package/dist/core-node/src/worker/types.d.ts.map +1 -0
- package/dist/core-node/src/worker/worker.d.ts +27 -0
- package/dist/core-node/src/worker/worker.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +7 -0
- package/dist/legacy/v1-auto-update-handler.js +38 -0
- package/dist/legacy/v1-auto-update-handler.js.map +7 -0
- package/dist/orm-common/src/db-context.d.ts +669 -0
- package/dist/orm-common/src/db-context.d.ts.map +1 -0
- package/dist/orm-common/src/errors/db-transaction-error.d.ts +51 -0
- package/dist/orm-common/src/errors/db-transaction-error.d.ts.map +1 -0
- package/dist/orm-common/src/exec/executable.d.ts +79 -0
- package/dist/orm-common/src/exec/executable.d.ts.map +1 -0
- package/dist/orm-common/src/exec/queryable.d.ts +708 -0
- package/dist/orm-common/src/exec/queryable.d.ts.map +1 -0
- package/dist/orm-common/src/exec/search-parser.d.ts +72 -0
- package/dist/orm-common/src/exec/search-parser.d.ts.map +1 -0
- package/dist/orm-common/src/expr/expr-unit.d.ts +25 -0
- package/dist/orm-common/src/expr/expr-unit.d.ts.map +1 -0
- package/dist/orm-common/src/expr/expr.d.ts +1369 -0
- package/dist/orm-common/src/expr/expr.d.ts.map +1 -0
- package/dist/orm-common/src/index.d.ts +32 -0
- package/dist/orm-common/src/index.d.ts.map +1 -0
- package/dist/orm-common/src/models/system-migration.d.ts +10 -0
- package/dist/orm-common/src/models/system-migration.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/base/expr-renderer-base.d.ts +95 -0
- package/dist/orm-common/src/query-builder/base/expr-renderer-base.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/base/query-builder-base.d.ts +66 -0
- package/dist/orm-common/src/query-builder/base/query-builder-base.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/mssql/mssql-expr-renderer.d.ts +84 -0
- package/dist/orm-common/src/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/mssql/mssql-query-builder.d.ts +45 -0
- package/dist/orm-common/src/query-builder/mssql/mssql-query-builder.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/mysql/mysql-expr-renderer.d.ts +84 -0
- package/dist/orm-common/src/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/mysql/mysql-query-builder.d.ts +54 -0
- package/dist/orm-common/src/query-builder/mysql/mysql-query-builder.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/postgresql/postgresql-expr-renderer.d.ts +84 -0
- package/dist/orm-common/src/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/postgresql/postgresql-query-builder.d.ts +52 -0
- package/dist/orm-common/src/query-builder/postgresql/postgresql-query-builder.d.ts.map +1 -0
- package/dist/orm-common/src/query-builder/query-builder.d.ts +7 -0
- package/dist/orm-common/src/query-builder/query-builder.d.ts.map +1 -0
- package/dist/orm-common/src/schema/factory/column-builder.d.ts +394 -0
- package/dist/orm-common/src/schema/factory/column-builder.d.ts.map +1 -0
- package/dist/orm-common/src/schema/factory/index-builder.d.ts +151 -0
- package/dist/orm-common/src/schema/factory/index-builder.d.ts.map +1 -0
- package/dist/orm-common/src/schema/factory/relation-builder.d.ts +337 -0
- package/dist/orm-common/src/schema/factory/relation-builder.d.ts.map +1 -0
- package/dist/orm-common/src/schema/procedure-builder.d.ts +202 -0
- package/dist/orm-common/src/schema/procedure-builder.d.ts.map +1 -0
- package/dist/orm-common/src/schema/table-builder.d.ts +259 -0
- package/dist/orm-common/src/schema/table-builder.d.ts.map +1 -0
- package/dist/orm-common/src/schema/view-builder.d.ts +183 -0
- package/dist/orm-common/src/schema/view-builder.d.ts.map +1 -0
- package/dist/orm-common/src/types/column.d.ts +172 -0
- package/dist/orm-common/src/types/column.d.ts.map +1 -0
- package/dist/orm-common/src/types/db.d.ts +175 -0
- package/dist/orm-common/src/types/db.d.ts.map +1 -0
- package/dist/orm-common/src/types/expr.d.ts +474 -0
- package/dist/orm-common/src/types/expr.d.ts.map +1 -0
- package/dist/orm-common/src/types/query-def.d.ts +351 -0
- package/dist/orm-common/src/types/query-def.d.ts.map +1 -0
- package/dist/orm-common/src/utils/result-parser.d.ts +38 -0
- package/dist/orm-common/src/utils/result-parser.d.ts.map +1 -0
- package/dist/orm-node/src/connections/mssql-db-conn.d.ts +44 -0
- package/dist/orm-node/src/connections/mssql-db-conn.d.ts.map +1 -0
- package/dist/orm-node/src/connections/mysql-db-conn.d.ts +38 -0
- package/dist/orm-node/src/connections/mysql-db-conn.d.ts.map +1 -0
- package/dist/orm-node/src/connections/postgresql-db-conn.d.ts +39 -0
- package/dist/orm-node/src/connections/postgresql-db-conn.d.ts.map +1 -0
- package/dist/orm-node/src/db-conn-factory.d.ts +25 -0
- package/dist/orm-node/src/db-conn-factory.d.ts.map +1 -0
- package/dist/orm-node/src/index.d.ts +9 -0
- package/dist/orm-node/src/index.d.ts.map +1 -0
- package/dist/orm-node/src/node-db-context-executor.d.ts +77 -0
- package/dist/orm-node/src/node-db-context-executor.d.ts.map +1 -0
- package/dist/orm-node/src/pooled-db-conn.d.ts +79 -0
- package/dist/orm-node/src/pooled-db-conn.d.ts.map +1 -0
- package/dist/orm-node/src/sd-orm.d.ts +78 -0
- package/dist/orm-node/src/sd-orm.d.ts.map +1 -0
- package/dist/orm-node/src/types/db-conn.d.ts +159 -0
- package/dist/orm-node/src/types/db-conn.d.ts.map +1 -0
- package/dist/protocol/protocol-wrapper.js +64 -0
- package/dist/protocol/protocol-wrapper.js.map +7 -0
- package/dist/service-common/src/index.d.ts +8 -0
- package/dist/service-common/src/index.d.ts.map +1 -0
- package/dist/service-common/src/protocol/protocol.types.d.ts +100 -0
- package/dist/service-common/src/protocol/protocol.types.d.ts.map +1 -0
- package/dist/service-common/src/protocol/service-protocol.d.ts +63 -0
- package/dist/service-common/src/protocol/service-protocol.d.ts.map +1 -0
- package/dist/service-common/src/service-types/auto-update-service.types.d.ts +17 -0
- package/dist/service-common/src/service-types/auto-update-service.types.d.ts.map +1 -0
- package/dist/service-common/src/service-types/crypto-service.types.d.ts +22 -0
- package/dist/service-common/src/service-types/crypto-service.types.d.ts.map +1 -0
- package/dist/service-common/src/service-types/orm-service.types.d.ts +30 -0
- package/dist/service-common/src/service-types/orm-service.types.d.ts.map +1 -0
- package/dist/service-common/src/service-types/smtp-service.types.d.ts +55 -0
- package/dist/service-common/src/service-types/smtp-service.types.d.ts.map +1 -0
- package/dist/service-common/src/types.d.ts +43 -0
- package/dist/service-common/src/types.d.ts.map +1 -0
- package/dist/service-server/src/auth/auth-token-payload.d.ts +6 -0
- package/dist/service-server/src/auth/auth-token-payload.d.ts.map +1 -0
- package/dist/service-server/src/auth/auth.decorators.d.ts +19 -0
- package/dist/service-server/src/auth/auth.decorators.d.ts.map +1 -0
- package/dist/service-server/src/auth/jwt-manager.d.ts +10 -0
- package/dist/service-server/src/auth/jwt-manager.d.ts.map +1 -0
- package/dist/service-server/src/core/service-base.d.ts +19 -0
- package/dist/service-server/src/core/service-base.d.ts.map +1 -0
- package/dist/service-server/src/core/service-executor.d.ts +18 -0
- package/dist/service-server/src/core/service-executor.d.ts.map +1 -0
- package/dist/service-server/src/index.d.ts +20 -0
- package/dist/service-server/src/index.d.ts.map +1 -0
- package/dist/service-server/src/legacy/v1-auto-update-handler.d.ts +8 -0
- package/dist/service-server/src/legacy/v1-auto-update-handler.d.ts.map +1 -0
- package/dist/service-server/src/protocol/protocol-wrapper.d.ts +25 -0
- package/dist/service-server/src/protocol/protocol-wrapper.d.ts.map +1 -0
- package/dist/service-server/src/service-server.d.ts +29 -0
- package/dist/service-server/src/service-server.d.ts.map +1 -0
- package/dist/service-server/src/services/auto-update-service.d.ts +9 -0
- package/dist/service-server/src/services/auto-update-service.d.ts.map +1 -0
- package/dist/service-server/src/services/crypto-service.d.ts +10 -0
- package/dist/service-server/src/services/crypto-service.d.ts.map +1 -0
- package/dist/service-server/src/services/orm-service.d.ts +28 -0
- package/dist/service-server/src/services/orm-service.d.ts.map +1 -0
- package/dist/service-server/src/services/smtp-service.d.ts +7 -0
- package/dist/service-server/src/services/smtp-service.d.ts.map +1 -0
- package/dist/service-server/src/transport/http/http-request-handler.d.ts +12 -0
- package/dist/service-server/src/transport/http/http-request-handler.d.ts.map +1 -0
- package/dist/service-server/src/transport/http/static-file-handler.d.ts +9 -0
- package/dist/service-server/src/transport/http/static-file-handler.d.ts.map +1 -0
- package/dist/service-server/src/transport/http/upload-handler.d.ts +10 -0
- package/dist/service-server/src/transport/http/upload-handler.d.ts.map +1 -0
- package/dist/service-server/src/transport/socket/service-socket.d.ts +41 -0
- package/dist/service-server/src/transport/socket/service-socket.d.ts.map +1 -0
- package/dist/service-server/src/transport/socket/websocket-handler.d.ts +18 -0
- package/dist/service-server/src/transport/socket/websocket-handler.d.ts.map +1 -0
- package/dist/service-server/src/types/server-options.d.ts +15 -0
- package/dist/service-server/src/types/server-options.d.ts.map +1 -0
- package/dist/service-server/src/utils/config-manager.d.ts +7 -0
- package/dist/service-server/src/utils/config-manager.d.ts.map +1 -0
- package/dist/service-server/src/workers/service-protocol.worker.d.ts +15 -0
- package/dist/service-server/src/workers/service-protocol.worker.d.ts.map +1 -0
- package/dist/service-server.js +165 -0
- package/dist/service-server.js.map +7 -0
- package/dist/services/auto-update-service.js +39 -0
- package/dist/services/auto-update-service.js.map +7 -0
- package/dist/services/crypto-service.js +32 -0
- package/dist/services/crypto-service.js.map +7 -0
- package/dist/services/orm-service.js +186 -0
- package/dist/services/orm-service.js.map +7 -0
- package/dist/services/smtp-service.js +47 -0
- package/dist/services/smtp-service.js.map +7 -0
- package/dist/transport/http/http-request-handler.js +57 -0
- package/dist/transport/http/http-request-handler.js.map +7 -0
- package/dist/transport/http/static-file-handler.js +57 -0
- package/dist/transport/http/static-file-handler.js.map +7 -0
- package/dist/transport/http/upload-handler.js +71 -0
- package/dist/transport/http/upload-handler.js.map +7 -0
- package/dist/transport/socket/service-socket.js +105 -0
- package/dist/transport/socket/service-socket.js.map +7 -0
- package/dist/transport/socket/websocket-handler.js +144 -0
- package/dist/transport/socket/websocket-handler.js.map +7 -0
- package/dist/types/server-options.js +1 -0
- package/dist/types/server-options.js.map +7 -0
- package/dist/utils/config-manager.js +62 -0
- package/dist/utils/config-manager.js.map +7 -0
- package/dist/workers/service-protocol.worker.js +15 -0
- package/dist/workers/service-protocol.worker.js.map +7 -0
- package/package.json +44 -0
- package/src/auth/auth-token-payload.ts +6 -0
- package/src/auth/auth.decorators.ts +71 -0
- package/src/auth/jwt-manager.ts +44 -0
- package/src/core/service-base.ts +66 -0
- package/src/core/service-executor.ts +70 -0
- package/src/index.ts +38 -0
- package/src/legacy/v1-auto-update-handler.ts +64 -0
- package/src/protocol/protocol-wrapper.ts +75 -0
- package/src/service-server.ts +235 -0
- package/src/services/auto-update-service.ts +52 -0
- package/src/services/crypto-service.ts +38 -0
- package/src/services/orm-service.ts +170 -0
- package/src/services/smtp-service.ts +58 -0
- package/src/transport/http/http-request-handler.ts +69 -0
- package/src/transport/http/static-file-handler.ts +66 -0
- package/src/transport/http/upload-handler.ts +87 -0
- package/src/transport/socket/service-socket.ts +136 -0
- package/src/transport/socket/websocket-handler.ts +181 -0
- package/src/types/server-options.ts +15 -0
- package/src/utils/config-manager.ts +70 -0
- package/src/workers/service-protocol.worker.ts +15 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { fsExists, fsStat } from "@simplysm/core-node";
|
|
3
|
+
import { createConsola } from "consola";
|
|
4
|
+
const logger = createConsola().withTag("service-server:StaticFileHandler");
|
|
5
|
+
class StaticFileHandler {
|
|
6
|
+
constructor(_server) {
|
|
7
|
+
this._server = _server;
|
|
8
|
+
}
|
|
9
|
+
async handle(req, reply, urlPath) {
|
|
10
|
+
let targetFilePath = path.resolve(this._server.options.rootPath, "www", urlPath);
|
|
11
|
+
const allowedRootPath = path.resolve(this._server.options.rootPath, "www");
|
|
12
|
+
if (!targetFilePath.startsWith(allowedRootPath)) {
|
|
13
|
+
throw new Error("Access denied");
|
|
14
|
+
}
|
|
15
|
+
if (await fsExists(targetFilePath) && (await fsStat(targetFilePath)).isDirectory()) {
|
|
16
|
+
targetFilePath = path.resolve(targetFilePath, "index.html");
|
|
17
|
+
}
|
|
18
|
+
if (path.basename(targetFilePath).startsWith(".")) {
|
|
19
|
+
const errorMessage = "\uD30C\uC77C\uC744 \uC0AC\uC6A9\uD560 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
20
|
+
this._responseErrorHtml(reply, 403, errorMessage);
|
|
21
|
+
logger.warn(`[403] ${errorMessage} (${targetFilePath})`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const filename = path.basename(targetFilePath);
|
|
25
|
+
const directory = path.dirname(targetFilePath);
|
|
26
|
+
try {
|
|
27
|
+
return await reply.sendFile(filename, directory);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
const error = err;
|
|
30
|
+
if (error.code === "ENOENT") {
|
|
31
|
+
const errorMessage = "\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.";
|
|
32
|
+
this._responseErrorHtml(reply, 404, errorMessage);
|
|
33
|
+
logger.warn(`[404] ${errorMessage} (${targetFilePath})`);
|
|
34
|
+
} else {
|
|
35
|
+
const errorMessage = "\uD30C\uC77C \uC804\uC1A1 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.";
|
|
36
|
+
this._responseErrorHtml(reply, 500, errorMessage);
|
|
37
|
+
logger.error(`[500] ${errorMessage}`, err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
_responseErrorHtml(reply, code, message) {
|
|
42
|
+
reply.status(code).type("text/html").send(`
|
|
43
|
+
<!DOCTYPE html>
|
|
44
|
+
<html>
|
|
45
|
+
<head>
|
|
46
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
47
|
+
<meta charset="UTF-8">
|
|
48
|
+
<title>${code}: ${message}</title>
|
|
49
|
+
</head>
|
|
50
|
+
<body>${code}: ${message}</body>
|
|
51
|
+
</html>`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
StaticFileHandler
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=static-file-handler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/transport/http/static-file-handler.ts"],
|
|
4
|
+
"sourcesContent": ["import path from \"path\";\nimport { fsExists, fsStat } from \"@simplysm/core-node\";\nimport type { ServiceServer } from \"../../service-server\";\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\nimport { createConsola } from \"consola\";\n\nconst logger = createConsola().withTag(\"service-server:StaticFileHandler\");\n\nexport class StaticFileHandler {\n constructor(private readonly _server: ServiceServer) {}\n\n async handle(req: FastifyRequest, reply: FastifyReply, urlPath: string): Promise<void> {\n let targetFilePath = path.resolve(this._server.options.rootPath, \"www\", urlPath);\n const allowedRootPath = path.resolve(this._server.options.rootPath, \"www\");\n\n // targetPath \uBCF4\uC548 \uBC29\uC5B4 (Path Traversal \uBC29\uC9C0)\n if (!targetFilePath.startsWith(allowedRootPath)) {\n throw new Error(\"Access denied\");\n }\n\n // \uB514\uB809\uD1A0\uB9AC\uBA74 index.html\uB85C\n if ((await fsExists(targetFilePath)) && (await fsStat(targetFilePath)).isDirectory()) {\n targetFilePath = path.resolve(targetFilePath, \"index.html\");\n }\n\n // \uAD8C\uD55C \uD655\uC778 (\uC228\uAE40 \uD30C\uC77C \uB4F1)\n if (path.basename(targetFilePath).startsWith(\".\")) {\n const errorMessage = \"\uD30C\uC77C\uC744 \uC0AC\uC6A9\uD560 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\";\n this._responseErrorHtml(reply, 403, errorMessage);\n logger.warn(`[403] ${errorMessage} (${targetFilePath})`);\n return;\n }\n\n // \uD30C\uC77C \uC804\uC1A1\n const filename = path.basename(targetFilePath);\n const directory = path.dirname(targetFilePath);\n\n try {\n return await reply.sendFile(filename, directory);\n } catch (err: unknown) {\n const error = err as { code?: string };\n if (error.code === \"ENOENT\") {\n const errorMessage = \"\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.\";\n this._responseErrorHtml(reply, 404, errorMessage);\n logger.warn(`[404] ${errorMessage} (${targetFilePath})`);\n } else {\n const errorMessage = \"\uD30C\uC77C \uC804\uC1A1 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.\";\n this._responseErrorHtml(reply, 500, errorMessage);\n logger.error(`[500] ${errorMessage}`, err);\n }\n }\n }\n\n private _responseErrorHtml(reply: FastifyReply, code: number, message: string) {\n reply.status(code).type(\"text/html\").send(`\n<!DOCTYPE html>\n<html>\n<head>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta charset=\"UTF-8\">\n <title>${code}: ${message}</title>\n</head>\n<body>${code}: ${message}</body>\n</html>`);\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,UAAU;AACjB,SAAS,UAAU,cAAc;AAGjC,SAAS,qBAAqB;AAE9B,MAAM,SAAS,cAAc,EAAE,QAAQ,kCAAkC;AAElE,MAAM,kBAAkB;AAAA,EAC7B,YAA6B,SAAwB;AAAxB;AAAA,EAAyB;AAAA,EAEtD,MAAM,OAAO,KAAqB,OAAqB,SAAgC;AACrF,QAAI,iBAAiB,KAAK,QAAQ,KAAK,QAAQ,QAAQ,UAAU,OAAO,OAAO;AAC/E,UAAM,kBAAkB,KAAK,QAAQ,KAAK,QAAQ,QAAQ,UAAU,KAAK;AAGzE,QAAI,CAAC,eAAe,WAAW,eAAe,GAAG;AAC/C,YAAM,IAAI,MAAM,eAAe;AAAA,IACjC;AAGA,QAAK,MAAM,SAAS,cAAc,MAAO,MAAM,OAAO,cAAc,GAAG,YAAY,GAAG;AACpF,uBAAiB,KAAK,QAAQ,gBAAgB,YAAY;AAAA,IAC5D;AAGA,QAAI,KAAK,SAAS,cAAc,EAAE,WAAW,GAAG,GAAG;AACjD,YAAM,eAAe;AACrB,WAAK,mBAAmB,OAAO,KAAK,YAAY;AAChD,aAAO,KAAK,SAAS,YAAY,KAAK,cAAc,GAAG;AACvD;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,SAAS,cAAc;AAC7C,UAAM,YAAY,KAAK,QAAQ,cAAc;AAE7C,QAAI;AACF,aAAO,MAAM,MAAM,SAAS,UAAU,SAAS;AAAA,IACjD,SAAS,KAAc;AACrB,YAAM,QAAQ;AACd,UAAI,MAAM,SAAS,UAAU;AAC3B,cAAM,eAAe;AACrB,aAAK,mBAAmB,OAAO,KAAK,YAAY;AAChD,eAAO,KAAK,SAAS,YAAY,KAAK,cAAc,GAAG;AAAA,MACzD,OAAO;AACL,cAAM,eAAe;AACrB,aAAK,mBAAmB,OAAO,KAAK,YAAY;AAChD,eAAO,MAAM,SAAS,YAAY,IAAI,GAAG;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,mBAAmB,OAAqB,MAAc,SAAiB;AAC7E,UAAM,OAAO,IAAI,EAAE,KAAK,WAAW,EAAE,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aAMjC,IAAI,KAAK,OAAO;AAAA;AAAA,QAErB,IAAI,KAAK,OAAO;AAAA,QAChB;AAAA,EACN;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import { createWriteStream } from "fs";
|
|
3
|
+
import { pipeline } from "stream/promises";
|
|
4
|
+
import { Uuid } from "@simplysm/core-common";
|
|
5
|
+
import { fsMkdir, fsStat, fsRm } from "@simplysm/core-node";
|
|
6
|
+
import { createConsola } from "consola";
|
|
7
|
+
const logger = createConsola().withTag("service-server:UploadHandler");
|
|
8
|
+
class UploadHandler {
|
|
9
|
+
constructor(_server, _jwt) {
|
|
10
|
+
this._server = _server;
|
|
11
|
+
this._jwt = _jwt;
|
|
12
|
+
}
|
|
13
|
+
async handle(req, reply) {
|
|
14
|
+
if (!req.isMultipart()) {
|
|
15
|
+
reply.status(400).send("Multipart request expected");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
const authHeader = req.headers.authorization;
|
|
20
|
+
if (authHeader == null) {
|
|
21
|
+
throw new Error("\uC778\uC99D \uD1A0\uD070\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.");
|
|
22
|
+
}
|
|
23
|
+
const token = authHeader.split(" ")[1];
|
|
24
|
+
await this._jwt.verify(token);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
reply.status(401).send({
|
|
27
|
+
error: "Unauthorized",
|
|
28
|
+
message: err instanceof Error ? err.message : String(err)
|
|
29
|
+
});
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const result = [];
|
|
33
|
+
const uploadDir = path.resolve(this._server.options.rootPath, "www", "uploads");
|
|
34
|
+
await fsMkdir(uploadDir);
|
|
35
|
+
let currentSavePath;
|
|
36
|
+
try {
|
|
37
|
+
for await (const part of req.parts()) {
|
|
38
|
+
if (part.type === "file") {
|
|
39
|
+
const originalFilename = part.filename;
|
|
40
|
+
const extension = path.extname(originalFilename);
|
|
41
|
+
const saveName = `${Uuid.new().toString()}${extension}`;
|
|
42
|
+
currentSavePath = path.join(uploadDir, saveName);
|
|
43
|
+
await pipeline(part.file, createWriteStream(currentSavePath));
|
|
44
|
+
if (part.file.truncated) {
|
|
45
|
+
throw new Error(`File limit exceeded: ${originalFilename}`);
|
|
46
|
+
}
|
|
47
|
+
const stats = await fsStat(currentSavePath);
|
|
48
|
+
result.push({
|
|
49
|
+
path: `uploads/${saveName}`,
|
|
50
|
+
filename: originalFilename,
|
|
51
|
+
size: stats.size
|
|
52
|
+
});
|
|
53
|
+
currentSavePath = void 0;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
reply.send(result);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
logger.error("Upload Error", err);
|
|
59
|
+
if (currentSavePath != null) {
|
|
60
|
+
await fsRm(currentSavePath).catch(() => {
|
|
61
|
+
});
|
|
62
|
+
logger.warn(`Incomplete file deleted: ${currentSavePath}`);
|
|
63
|
+
}
|
|
64
|
+
reply.code(500).send("Upload Failed");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
UploadHandler
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=upload-handler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/transport/http/upload-handler.ts"],
|
|
4
|
+
"sourcesContent": ["import path from \"path\";\nimport { createWriteStream } from \"fs\";\nimport { pipeline } from \"stream/promises\";\nimport { Uuid } from \"@simplysm/core-common\";\nimport { fsMkdir, fsStat, fsRm } from \"@simplysm/core-node\";\nimport type { ServiceServer } from \"../../service-server\";\nimport type { FastifyReply, FastifyRequest } from \"fastify\";\nimport type { ServiceUploadResult } from \"@simplysm/service-common\";\nimport type { JwtManager } from \"../../auth/jwt-manager\";\nimport { createConsola } from \"consola\";\n\nconst logger = createConsola().withTag(\"service-server:UploadHandler\");\n\nexport class UploadHandler {\n constructor(\n private readonly _server: ServiceServer,\n private readonly _jwt: JwtManager,\n ) {}\n\n async handle(req: FastifyRequest, reply: FastifyReply): Promise<void> {\n if (!req.isMultipart()) {\n reply.status(400).send(\"Multipart request expected\");\n return;\n }\n\n // \uC778\uC99D \uAC80\uC0AC\n try {\n const authHeader = req.headers.authorization;\n if (authHeader == null) {\n throw new Error(\"\uC778\uC99D \uD1A0\uD070\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.\");\n }\n const token = authHeader.split(\" \")[1];\n await this._jwt.verify(token);\n } catch (err) {\n reply.status(401).send({\n error: \"Unauthorized\",\n message: err instanceof Error ? err.message : String(err),\n });\n return;\n }\n\n const result: ServiceUploadResult[] = [];\n const uploadDir = path.resolve(this._server.options.rootPath, \"www\", \"uploads\");\n\n await fsMkdir(uploadDir);\n\n let currentSavePath: string | undefined;\n\n try {\n for await (const part of req.parts()) {\n if (part.type === \"file\") {\n const originalFilename = part.filename;\n const extension = path.extname(originalFilename);\n const saveName = `${Uuid.new().toString()}${extension}`;\n currentSavePath = path.join(uploadDir, saveName);\n\n await pipeline(part.file, createWriteStream(currentSavePath));\n\n if (part.file.truncated) {\n throw new Error(`File limit exceeded: ${originalFilename}`);\n }\n\n const stats = await fsStat(currentSavePath);\n\n result.push({\n path: `uploads/${saveName}`,\n filename: originalFilename,\n size: stats.size,\n });\n\n currentSavePath = undefined;\n }\n }\n\n reply.send(result);\n } catch (err) {\n logger.error(\"Upload Error\", err);\n\n if (currentSavePath != null) {\n await fsRm(currentSavePath).catch(() => {});\n logger.warn(`Incomplete file deleted: ${currentSavePath}`);\n }\n\n reply.code(500).send(\"Upload Failed\");\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,OAAO,UAAU;AACjB,SAAS,yBAAyB;AAClC,SAAS,gBAAgB;AACzB,SAAS,YAAY;AACrB,SAAS,SAAS,QAAQ,YAAY;AAKtC,SAAS,qBAAqB;AAE9B,MAAM,SAAS,cAAc,EAAE,QAAQ,8BAA8B;AAE9D,MAAM,cAAc;AAAA,EACzB,YACmB,SACA,MACjB;AAFiB;AACA;AAAA,EAChB;AAAA,EAEH,MAAM,OAAO,KAAqB,OAAoC;AACpE,QAAI,CAAC,IAAI,YAAY,GAAG;AACtB,YAAM,OAAO,GAAG,EAAE,KAAK,4BAA4B;AACnD;AAAA,IACF;AAGA,QAAI;AACF,YAAM,aAAa,IAAI,QAAQ;AAC/B,UAAI,cAAc,MAAM;AACtB,cAAM,IAAI,MAAM,2DAAc;AAAA,MAChC;AACA,YAAM,QAAQ,WAAW,MAAM,GAAG,EAAE,CAAC;AACrC,YAAM,KAAK,KAAK,OAAO,KAAK;AAAA,IAC9B,SAAS,KAAK;AACZ,YAAM,OAAO,GAAG,EAAE,KAAK;AAAA,QACrB,OAAO;AAAA,QACP,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC1D,CAAC;AACD;AAAA,IACF;AAEA,UAAM,SAAgC,CAAC;AACvC,UAAM,YAAY,KAAK,QAAQ,KAAK,QAAQ,QAAQ,UAAU,OAAO,SAAS;AAE9E,UAAM,QAAQ,SAAS;AAEvB,QAAI;AAEJ,QAAI;AACF,uBAAiB,QAAQ,IAAI,MAAM,GAAG;AACpC,YAAI,KAAK,SAAS,QAAQ;AACxB,gBAAM,mBAAmB,KAAK;AAC9B,gBAAM,YAAY,KAAK,QAAQ,gBAAgB;AAC/C,gBAAM,WAAW,GAAG,KAAK,IAAI,EAAE,SAAS,CAAC,GAAG,SAAS;AACrD,4BAAkB,KAAK,KAAK,WAAW,QAAQ;AAE/C,gBAAM,SAAS,KAAK,MAAM,kBAAkB,eAAe,CAAC;AAE5D,cAAI,KAAK,KAAK,WAAW;AACvB,kBAAM,IAAI,MAAM,wBAAwB,gBAAgB,EAAE;AAAA,UAC5D;AAEA,gBAAM,QAAQ,MAAM,OAAO,eAAe;AAE1C,iBAAO,KAAK;AAAA,YACV,MAAM,WAAW,QAAQ;AAAA,YACzB,UAAU;AAAA,YACV,MAAM,MAAM;AAAA,UACd,CAAC;AAED,4BAAkB;AAAA,QACpB;AAAA,MACF;AAEA,YAAM,KAAK,MAAM;AAAA,IACnB,SAAS,KAAK;AACZ,aAAO,MAAM,gBAAgB,GAAG;AAEhC,UAAI,mBAAmB,MAAM;AAC3B,cAAM,KAAK,eAAe,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAC1C,eAAO,KAAK,4BAA4B,eAAe,EAAE;AAAA,MAC3D;AAEA,YAAM,KAAK,GAAG,EAAE,KAAK,eAAe;AAAA,IACtC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { DateTime, EventEmitter } from "@simplysm/core-common";
|
|
2
|
+
import { clearInterval } from "node:timers";
|
|
3
|
+
import { createConsola } from "consola";
|
|
4
|
+
import { WebSocket } from "ws";
|
|
5
|
+
import { ProtocolWrapper } from "../../protocol/protocol-wrapper";
|
|
6
|
+
const logger = createConsola().withTag("service-server:ServiceSocket");
|
|
7
|
+
class ServiceSocket extends EventEmitter {
|
|
8
|
+
constructor(_socket, _clientId, clientName, connReq) {
|
|
9
|
+
super();
|
|
10
|
+
this._socket = _socket;
|
|
11
|
+
this._clientId = _clientId;
|
|
12
|
+
this.clientName = clientName;
|
|
13
|
+
this.connReq = connReq;
|
|
14
|
+
this._socket.on("close", this._onClose.bind(this));
|
|
15
|
+
this._socket.on("error", this._onError.bind(this));
|
|
16
|
+
this._socket.on("message", this._onMessage.bind(this));
|
|
17
|
+
this._socket.on("pong", () => {
|
|
18
|
+
this._isAlive = true;
|
|
19
|
+
});
|
|
20
|
+
this._pingTimer = setInterval(() => {
|
|
21
|
+
if (!this._isAlive) {
|
|
22
|
+
this.close();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this._isAlive = false;
|
|
26
|
+
this._socket.ping();
|
|
27
|
+
}, this._PING_INTERVAL);
|
|
28
|
+
}
|
|
29
|
+
_PING_INTERVAL = 5e3;
|
|
30
|
+
// 5초마다 핑 전송
|
|
31
|
+
_PONG_PACKET = new Uint8Array([2]);
|
|
32
|
+
_protocol = new ProtocolWrapper();
|
|
33
|
+
_listenerInfos = [];
|
|
34
|
+
_isAlive = true;
|
|
35
|
+
_pingTimer;
|
|
36
|
+
connectedAtDateTime = new DateTime();
|
|
37
|
+
authTokenPayload;
|
|
38
|
+
close() {
|
|
39
|
+
this._socket.terminate();
|
|
40
|
+
}
|
|
41
|
+
async send(uuid, msg) {
|
|
42
|
+
return this._send(uuid, msg);
|
|
43
|
+
}
|
|
44
|
+
async _send(uuid, msg) {
|
|
45
|
+
if (this._socket.readyState !== WebSocket.OPEN) return 0;
|
|
46
|
+
const { chunks } = await this._protocol.encode(uuid, msg);
|
|
47
|
+
for (const chunk of chunks) {
|
|
48
|
+
this._socket.send(chunk);
|
|
49
|
+
}
|
|
50
|
+
return chunks.reduce((acc, item) => acc + item.length, 0);
|
|
51
|
+
}
|
|
52
|
+
addEventListener(key, eventName, info) {
|
|
53
|
+
this._listenerInfos.push({ key, eventName, info });
|
|
54
|
+
}
|
|
55
|
+
removeEventListener(key) {
|
|
56
|
+
const idx = this._listenerInfos.findIndex((item) => item.key === key);
|
|
57
|
+
if (idx >= 0) {
|
|
58
|
+
this._listenerInfos.splice(idx, 1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
getEventListeners(eventName) {
|
|
62
|
+
return this._listenerInfos.filter((item) => item.eventName === eventName).map((item) => ({ key: item.key, info: item.info }));
|
|
63
|
+
}
|
|
64
|
+
filterEventTargetKeys(targetKeys) {
|
|
65
|
+
return this._listenerInfos.filter((item) => targetKeys.includes(item.key)).map((item) => item.key);
|
|
66
|
+
}
|
|
67
|
+
_onError(err) {
|
|
68
|
+
logger.error("WebSocket \uD074\uB77C\uC774\uC5B8\uD2B8 \uC624\uB958 \uBC1C\uC0DD", err);
|
|
69
|
+
this.emit("error", err);
|
|
70
|
+
}
|
|
71
|
+
_onClose(code) {
|
|
72
|
+
clearInterval(this._pingTimer);
|
|
73
|
+
this._protocol.dispose();
|
|
74
|
+
this.emit("close", code);
|
|
75
|
+
}
|
|
76
|
+
async _onMessage(msgBuffer) {
|
|
77
|
+
try {
|
|
78
|
+
if (msgBuffer.length === 1 && msgBuffer[0] === 1) {
|
|
79
|
+
if (this._socket.readyState === WebSocket.OPEN) {
|
|
80
|
+
this._socket.send(this._PONG_PACKET);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const decodeResult = await this._protocol.decode(msgBuffer);
|
|
85
|
+
if (decodeResult.type === "progress") {
|
|
86
|
+
await this._send(decodeResult.uuid, {
|
|
87
|
+
name: "progress",
|
|
88
|
+
body: {
|
|
89
|
+
totalSize: decodeResult.totalSize,
|
|
90
|
+
completedSize: decodeResult.completedSize
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
const msg = decodeResult.message;
|
|
95
|
+
this.emit("message", { uuid: decodeResult.uuid, msg });
|
|
96
|
+
}
|
|
97
|
+
} catch (err) {
|
|
98
|
+
logger.error("WebSocket \uBA54\uC2DC\uC9C0 \uCC98\uB9AC \uC911 \uC624\uB958 \uBC1C\uC0DD", err);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
ServiceSocket
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=service-socket.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/transport/socket/service-socket.ts"],
|
|
4
|
+
"sourcesContent": ["import type { Bytes } from \"@simplysm/core-common\";\nimport { DateTime, EventEmitter } from \"@simplysm/core-common\";\nimport type { FastifyRequest } from \"fastify\";\nimport { clearInterval } from \"node:timers\";\nimport { createConsola } from \"consola\";\nimport { WebSocket } from \"ws\";\nimport type { AuthTokenPayload } from \"../../auth/auth-token-payload\";\nimport { ProtocolWrapper } from \"../../protocol/protocol-wrapper\";\nimport type { ServiceClientMessage, ServiceServerMessage, ServiceServerRawMessage } from \"@simplysm/service-common\";\n\nconst logger = createConsola().withTag(\"service-server:ServiceSocket\");\n\nexport class ServiceSocket extends EventEmitter<{\n error: Error;\n close: number;\n message: { uuid: string; msg: ServiceClientMessage };\n}> {\n private readonly _PING_INTERVAL = 5000; // 5\uCD08\uB9C8\uB2E4 \uD551 \uC804\uC1A1\n private readonly _PONG_PACKET = new Uint8Array([0x02]);\n\n private readonly _protocol = new ProtocolWrapper();\n private readonly _listenerInfos: { eventName: string; key: string; info: unknown }[] = [];\n\n private _isAlive = true;\n private readonly _pingTimer: NodeJS.Timeout;\n\n readonly connectedAtDateTime = new DateTime();\n\n authTokenPayload?: AuthTokenPayload;\n\n constructor(\n private readonly _socket: WebSocket,\n private readonly _clientId: string,\n public readonly clientName: string,\n public readonly connReq: FastifyRequest,\n ) {\n super();\n\n this._socket.on(\"close\", this._onClose.bind(this));\n this._socket.on(\"error\", this._onError.bind(this));\n this._socket.on(\"message\", this._onMessage.bind(this));\n\n this._socket.on(\"pong\", () => {\n this._isAlive = true;\n });\n\n this._pingTimer = setInterval(() => {\n if (!this._isAlive) {\n this.close();\n return;\n }\n\n this._isAlive = false;\n this._socket.ping();\n }, this._PING_INTERVAL);\n }\n\n close() {\n this._socket.terminate();\n }\n\n async send(uuid: string, msg: ServiceServerMessage) {\n return this._send(uuid, msg);\n }\n\n private async _send(uuid: string, msg: ServiceServerRawMessage) {\n if (this._socket.readyState !== WebSocket.OPEN) return 0;\n\n const { chunks } = await this._protocol.encode(uuid, msg);\n for (const chunk of chunks) {\n this._socket.send(chunk);\n }\n\n return chunks.reduce((acc, item) => acc + item.length, 0);\n }\n\n addEventListener(key: string, eventName: string, info: unknown) {\n this._listenerInfos.push({ key, eventName, info });\n }\n\n removeEventListener(key: string) {\n const idx = this._listenerInfos.findIndex((item) => item.key === key);\n if (idx >= 0) {\n this._listenerInfos.splice(idx, 1);\n }\n }\n\n getEventListeners(eventName: string) {\n return this._listenerInfos\n .filter((item) => item.eventName === eventName)\n .map((item) => ({ key: item.key, info: item.info }));\n }\n\n filterEventTargetKeys(targetKeys: string[]) {\n return this._listenerInfos.filter((item) => targetKeys.includes(item.key)).map((item) => item.key);\n }\n\n private _onError(err: Error) {\n logger.error(\"WebSocket \uD074\uB77C\uC774\uC5B8\uD2B8 \uC624\uB958 \uBC1C\uC0DD\", err);\n this.emit(\"error\", err);\n }\n\n private _onClose(code: number) {\n clearInterval(this._pingTimer);\n this._protocol.dispose();\n this.emit(\"close\", code);\n }\n\n private async _onMessage(msgBuffer: Bytes) {\n try {\n // ping\uC5D0 \uB300\uD55C pong\uCC98\uB9AC\n if (msgBuffer.length === 1 && msgBuffer[0] === 0x01) {\n if (this._socket.readyState === WebSocket.OPEN) {\n this._socket.send(this._PONG_PACKET);\n }\n return;\n }\n\n const decodeResult = await this._protocol.decode(msgBuffer);\n if (decodeResult.type === \"progress\") {\n await this._send(decodeResult.uuid, {\n name: \"progress\",\n body: {\n totalSize: decodeResult.totalSize,\n completedSize: decodeResult.completedSize,\n },\n });\n } else {\n const msg = decodeResult.message as ServiceClientMessage;\n this.emit(\"message\", { uuid: decodeResult.uuid, msg });\n }\n } catch (err) {\n logger.error(\"WebSocket \uBA54\uC2DC\uC9C0 \uCC98\uB9AC \uC911 \uC624\uB958 \uBC1C\uC0DD\", err);\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Uuid } from "@simplysm/core-common";
|
|
2
|
+
import { ServiceSocket } from "./service-socket";
|
|
3
|
+
import { createConsola } from "consola";
|
|
4
|
+
const logger = createConsola().withTag("service-server:WebSocketHandler");
|
|
5
|
+
class WebSocketHandler {
|
|
6
|
+
constructor(_executor, _jwt) {
|
|
7
|
+
this._executor = _executor;
|
|
8
|
+
this._jwt = _jwt;
|
|
9
|
+
}
|
|
10
|
+
_socketMap = /* @__PURE__ */ new Map();
|
|
11
|
+
addSocket(socket, clientId, clientName, connReq) {
|
|
12
|
+
try {
|
|
13
|
+
const serviceSocket = new ServiceSocket(socket, clientId, clientName, connReq);
|
|
14
|
+
const prevServiceSocket = this._socketMap.get(clientId);
|
|
15
|
+
if (prevServiceSocket != null) {
|
|
16
|
+
prevServiceSocket.close();
|
|
17
|
+
const connectionDateTimeText = prevServiceSocket.connectedAtDateTime.toFormatString("yyyy:MM:dd HH:mm:ss.fff");
|
|
18
|
+
logger.debug(`\uD074\uB77C\uC774\uC5B8\uD2B8 \uAE30\uC874\uC5F0\uACB0 \uB04A\uC74C: ${clientId}: ${connectionDateTimeText}`);
|
|
19
|
+
}
|
|
20
|
+
this._socketMap.set(clientId, serviceSocket);
|
|
21
|
+
serviceSocket.on("close", (code) => {
|
|
22
|
+
logger.debug(`\uD074\uB77C\uC774\uC5B8\uD2B8 \uC5F0\uACB0 \uB04A\uAE40: (code: ${code})`);
|
|
23
|
+
if (this._socketMap.get(clientId) !== serviceSocket) return;
|
|
24
|
+
this._socketMap.delete(clientId);
|
|
25
|
+
});
|
|
26
|
+
serviceSocket.on("message", async ({ uuid, msg }) => {
|
|
27
|
+
logger.debug("\uC694\uCCAD \uC218\uC2E0", msg);
|
|
28
|
+
const sentSize = await this._processRequest(serviceSocket, uuid, msg);
|
|
29
|
+
logger.debug(`\uC751\uB2F5 \uC804\uC1A1 (size: ${sentSize})`);
|
|
30
|
+
});
|
|
31
|
+
logger.debug("\uD074\uB77C\uC774\uC5B8\uD2B8 \uC5F0\uACB0\uB428", {
|
|
32
|
+
clientId,
|
|
33
|
+
remoteAddress: connReq.socket.remoteAddress,
|
|
34
|
+
socketSize: this._socketMap.size
|
|
35
|
+
});
|
|
36
|
+
} catch (err) {
|
|
37
|
+
logger.error("\uC5F0\uACB0 \uCC98\uB9AC \uC911 \uC624\uB958 \uBC1C\uC0DD", err);
|
|
38
|
+
socket.terminate();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
closeAll() {
|
|
42
|
+
for (const serviceSocket of this._socketMap.values()) {
|
|
43
|
+
serviceSocket.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async broadcastReload(clientName, changedFileSet) {
|
|
47
|
+
for (const serviceSocket of this._socketMap.values()) {
|
|
48
|
+
await serviceSocket.send(Uuid.new().toString(), {
|
|
49
|
+
name: "reload",
|
|
50
|
+
body: {
|
|
51
|
+
clientName,
|
|
52
|
+
changedFileSet
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async emitToServer(eventType, infoSelector, data) {
|
|
58
|
+
const eventName = eventType.prototype.eventName;
|
|
59
|
+
const targetKeys = Array.from(this._socketMap.values()).flatMap((subSock) => subSock.getEventListeners(eventName)).filter((item) => infoSelector(item.info)).map((item) => item.key);
|
|
60
|
+
for (const subSock of this._socketMap.values()) {
|
|
61
|
+
const subTargetKeys = subSock.filterEventTargetKeys(targetKeys);
|
|
62
|
+
if (subTargetKeys.length > 0) {
|
|
63
|
+
await subSock.send(Uuid.new().toString(), {
|
|
64
|
+
name: "evt:on",
|
|
65
|
+
body: {
|
|
66
|
+
keys: subTargetKeys,
|
|
67
|
+
data
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async _processRequest(serviceSocket, uuid, message) {
|
|
74
|
+
try {
|
|
75
|
+
if (message.name.includes(".") && Array.isArray(message.body)) {
|
|
76
|
+
const [serviceName, methodName] = message.name.split(".");
|
|
77
|
+
const result = await this._executor.runMethod({
|
|
78
|
+
serviceName,
|
|
79
|
+
methodName,
|
|
80
|
+
params: message.body,
|
|
81
|
+
socket: serviceSocket
|
|
82
|
+
});
|
|
83
|
+
return await serviceSocket.send(uuid, { name: "response", body: result });
|
|
84
|
+
} else if (message.name === "evt:add") {
|
|
85
|
+
const { key, name, info } = message.body;
|
|
86
|
+
serviceSocket.addEventListener(key, name, info);
|
|
87
|
+
return await serviceSocket.send(uuid, { name: "response" });
|
|
88
|
+
} else if (message.name === "evt:remove") {
|
|
89
|
+
const { key } = message.body;
|
|
90
|
+
serviceSocket.removeEventListener(key);
|
|
91
|
+
return await serviceSocket.send(uuid, { name: "response" });
|
|
92
|
+
} else if (message.name === "evt:gets") {
|
|
93
|
+
const { name } = message.body;
|
|
94
|
+
const infos = Array.from(this._socketMap.values()).flatMap((subSock) => subSock.getEventListeners(name));
|
|
95
|
+
return await serviceSocket.send(uuid, { name: "response", body: infos });
|
|
96
|
+
} else if (message.name === "evt:emit") {
|
|
97
|
+
const { keys, data } = message.body;
|
|
98
|
+
for (const subSock of this._socketMap.values()) {
|
|
99
|
+
const targetKeys = subSock.filterEventTargetKeys(keys);
|
|
100
|
+
if (targetKeys.length > 0) {
|
|
101
|
+
await subSock.send(uuid, {
|
|
102
|
+
name: "evt:on",
|
|
103
|
+
body: {
|
|
104
|
+
keys: targetKeys,
|
|
105
|
+
data
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return await serviceSocket.send(uuid, { name: "response" });
|
|
111
|
+
} else if (message.name === "auth") {
|
|
112
|
+
const token = message.body;
|
|
113
|
+
serviceSocket.authTokenPayload = await this._jwt.verify(token);
|
|
114
|
+
return await serviceSocket.send(uuid, { name: "response" });
|
|
115
|
+
} else {
|
|
116
|
+
const err = new Error("\uC694\uCCAD\uC774 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
|
|
117
|
+
return await serviceSocket.send(uuid, {
|
|
118
|
+
name: "error",
|
|
119
|
+
body: {
|
|
120
|
+
name: err.name,
|
|
121
|
+
message: err.message,
|
|
122
|
+
stack: err.stack,
|
|
123
|
+
code: "BAD_MESSAGE"
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
const error = err instanceof Error ? err : new Error(typeof err === "string" ? err : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD558\uC600\uC2B5\uB2C8\uB2E4.");
|
|
129
|
+
return serviceSocket.send(uuid, {
|
|
130
|
+
name: "error",
|
|
131
|
+
body: {
|
|
132
|
+
name: error.name,
|
|
133
|
+
message: error.message,
|
|
134
|
+
code: "INTERNAL_ERROR",
|
|
135
|
+
stack: error.stack
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
WebSocketHandler
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=websocket-handler.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/transport/socket/websocket-handler.ts"],
|
|
4
|
+
"sourcesContent": ["import type { WebSocket } from \"ws\";\nimport type { Type } from \"@simplysm/core-common\";\nimport { Uuid } from \"@simplysm/core-common\";\nimport type { ServiceEventListener, ServiceClientMessage } from \"@simplysm/service-common\";\nimport type { ServiceExecutor } from \"../../core/service-executor\";\nimport { ServiceSocket } from \"./service-socket\";\nimport type { JwtManager } from \"../../auth/jwt-manager\";\nimport type { FastifyRequest } from \"fastify\";\nimport { createConsola } from \"consola\";\n\nconst logger = createConsola().withTag(\"service-server:WebSocketHandler\");\n\nexport class WebSocketHandler {\n private readonly _socketMap = new Map<string, ServiceSocket>();\n\n constructor(\n private readonly _executor: ServiceExecutor,\n private readonly _jwt: JwtManager,\n ) {}\n\n addSocket(socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest) {\n try {\n const serviceSocket = new ServiceSocket(socket, clientId, clientName, connReq);\n\n // \uAE30\uC874 \uC5F0\uACB0 \uB04A\uAE30\n const prevServiceSocket = this._socketMap.get(clientId);\n if (prevServiceSocket != null) {\n prevServiceSocket.close();\n\n const connectionDateTimeText = prevServiceSocket.connectedAtDateTime.toFormatString(\"yyyy:MM:dd HH:mm:ss.fff\");\n logger.debug(`\uD074\uB77C\uC774\uC5B8\uD2B8 \uAE30\uC874\uC5F0\uACB0 \uB04A\uC74C: ${clientId}: ${connectionDateTimeText}`);\n }\n\n this._socketMap.set(clientId, serviceSocket);\n\n serviceSocket.on(\"close\", (code) => {\n logger.debug(`\uD074\uB77C\uC774\uC5B8\uD2B8 \uC5F0\uACB0 \uB04A\uAE40: (code: ${code})`);\n\n if (this._socketMap.get(clientId) !== serviceSocket) return;\n this._socketMap.delete(clientId);\n });\n\n serviceSocket.on(\"message\", async ({ uuid, msg }) => {\n logger.debug(\"\uC694\uCCAD \uC218\uC2E0\", msg);\n const sentSize = await this._processRequest(serviceSocket, uuid, msg);\n logger.debug(`\uC751\uB2F5 \uC804\uC1A1 (size: ${sentSize})`);\n });\n\n logger.debug(\"\uD074\uB77C\uC774\uC5B8\uD2B8 \uC5F0\uACB0\uB428\", {\n clientId,\n remoteAddress: connReq.socket.remoteAddress,\n socketSize: this._socketMap.size,\n });\n } catch (err) {\n logger.error(\"\uC5F0\uACB0 \uCC98\uB9AC \uC911 \uC624\uB958 \uBC1C\uC0DD\", err);\n socket.terminate();\n }\n }\n\n closeAll() {\n for (const serviceSocket of this._socketMap.values()) {\n serviceSocket.close();\n }\n }\n\n async broadcastReload(clientName: string | undefined, changedFileSet: Set<string>) {\n for (const serviceSocket of this._socketMap.values()) {\n await serviceSocket.send(Uuid.new().toString(), {\n name: \"reload\",\n body: {\n clientName,\n changedFileSet,\n },\n });\n }\n }\n\n async emitToServer<T extends ServiceEventListener<unknown, unknown>>(\n eventType: Type<T>,\n infoSelector: (item: T[\"$info\"]) => boolean,\n data: T[\"$data\"],\n ) {\n const eventName = eventType.prototype.eventName as string;\n const targetKeys = Array.from(this._socketMap.values())\n .flatMap((subSock) => subSock.getEventListeners(eventName))\n .filter((item) => infoSelector(item.info as T[\"$info\"]))\n .map((item) => item.key);\n\n for (const subSock of this._socketMap.values()) {\n const subTargetKeys = subSock.filterEventTargetKeys(targetKeys);\n if (subTargetKeys.length > 0) {\n await subSock.send(Uuid.new().toString(), {\n name: \"evt:on\",\n body: {\n keys: subTargetKeys,\n data,\n },\n });\n }\n }\n }\n\n private async _processRequest(\n serviceSocket: ServiceSocket,\n uuid: string,\n message: ServiceClientMessage,\n ): Promise<number> {\n try {\n if (message.name.includes(\".\") && Array.isArray(message.body)) {\n const [serviceName, methodName] = message.name.split(\".\");\n\n const result = await this._executor.runMethod({\n serviceName,\n methodName,\n params: message.body,\n socket: serviceSocket,\n });\n\n return await serviceSocket.send(uuid, { name: \"response\", body: result });\n } else if (message.name === \"evt:add\") {\n const { key, name, info } = message.body as { key: string; name: string; info: unknown };\n serviceSocket.addEventListener(key, name, info);\n return await serviceSocket.send(uuid, { name: \"response\" });\n } else if (message.name === \"evt:remove\") {\n const { key } = message.body as { key: string };\n serviceSocket.removeEventListener(key);\n return await serviceSocket.send(uuid, { name: \"response\" });\n } else if (message.name === \"evt:gets\") {\n const { name } = message.body as { name: string };\n const infos = Array.from(this._socketMap.values()).flatMap((subSock) => subSock.getEventListeners(name));\n return await serviceSocket.send(uuid, { name: \"response\", body: infos });\n } else if (message.name === \"evt:emit\") {\n const { keys, data } = message.body as { keys: string[]; data: unknown };\n\n for (const subSock of this._socketMap.values()) {\n const targetKeys = subSock.filterEventTargetKeys(keys);\n if (targetKeys.length > 0) {\n await subSock.send(uuid, {\n name: \"evt:on\",\n body: {\n keys: targetKeys,\n data,\n },\n });\n }\n }\n\n return await serviceSocket.send(uuid, { name: \"response\" });\n } else if (message.name === \"auth\") {\n const token = message.body;\n serviceSocket.authTokenPayload = await this._jwt.verify(token);\n return await serviceSocket.send(uuid, { name: \"response\" });\n } else {\n const err = new Error(\"\uC694\uCCAD\uC774 \uC798\uBABB\uB418\uC5C8\uC2B5\uB2C8\uB2E4.\");\n\n return await serviceSocket.send(uuid, {\n name: \"error\",\n body: {\n name: err.name,\n message: err.message,\n stack: err.stack,\n code: \"BAD_MESSAGE\",\n },\n });\n }\n } catch (err) {\n const error =\n err instanceof Error ? err : new Error(typeof err === \"string\" ? err : \"\uC54C \uC218 \uC5C6\uB294 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD558\uC600\uC2B5\uB2C8\uB2E4.\");\n\n return serviceSocket.send(uuid, {\n name: \"error\",\n body: {\n name: error.name,\n message: error.message,\n code: \"INTERNAL_ERROR\",\n stack: error.stack,\n },\n });\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=server-options.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { LazyGcMap } from "@simplysm/core-common";
|
|
2
|
+
import { fsExists, fsReadJson, FsWatcher } from "@simplysm/core-node";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { createConsola } from "consola";
|
|
5
|
+
const logger = createConsola().withTag("service-server:ConfigManager");
|
|
6
|
+
class ConfigManager {
|
|
7
|
+
// 값: Config 객체, 키: 파일 경로
|
|
8
|
+
static _cache = new LazyGcMap({
|
|
9
|
+
gcInterval: 10 * 60 * 1e3,
|
|
10
|
+
// 10분마다
|
|
11
|
+
expireTime: 60 * 60 * 1e3,
|
|
12
|
+
// 1시간 만료
|
|
13
|
+
onExpire: async (filePath) => {
|
|
14
|
+
logger.debug(`\uC124\uC815 \uCE90\uC2DC \uB9CC\uB8CC \uBC0F \uAC10\uC2DC \uD574\uC81C: ${path.basename(filePath)}`);
|
|
15
|
+
await ConfigManager._closeWatcher(filePath);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
static _watchers = /* @__PURE__ */ new Map();
|
|
19
|
+
static async getConfig(filePath) {
|
|
20
|
+
if (this._cache.has(filePath)) {
|
|
21
|
+
return this._cache.get(filePath);
|
|
22
|
+
}
|
|
23
|
+
if (!await fsExists(filePath)) return void 0;
|
|
24
|
+
const config = await fsReadJson(filePath);
|
|
25
|
+
this._cache.set(filePath, config);
|
|
26
|
+
if (!this._watchers.has(filePath)) {
|
|
27
|
+
try {
|
|
28
|
+
const watcher = await FsWatcher.watch([filePath]);
|
|
29
|
+
this._watchers.set(filePath, watcher);
|
|
30
|
+
watcher.onChange({ delay: 100 }, async () => {
|
|
31
|
+
if (!await fsExists(filePath)) {
|
|
32
|
+
this._cache.delete(filePath);
|
|
33
|
+
await this._closeWatcher(filePath);
|
|
34
|
+
logger.debug(`\uC124\uC815 \uD30C\uC77C \uC0AD\uC81C\uB428: ${path.basename(filePath)}`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const newConfig = await fsReadJson(filePath);
|
|
39
|
+
this._cache.set(filePath, newConfig);
|
|
40
|
+
logger.debug(`\uC124\uC815 \uD30C\uC77C \uC2E4\uC2DC\uAC04 \uAC31\uC2E0: ${path.basename(filePath)}`);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
logger.warn(`\uC124\uC815 \uD30C\uC77C \uAC31\uC2E0 \uC2E4\uD328: ${filePath}`, err);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
} catch (err) {
|
|
46
|
+
logger.error(`\uAC10\uC2DC \uC2E4\uD328: ${filePath}`, err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
static async _closeWatcher(filePath) {
|
|
52
|
+
const watcher = this._watchers.get(filePath);
|
|
53
|
+
if (watcher != null) {
|
|
54
|
+
await watcher.close();
|
|
55
|
+
this._watchers.delete(filePath);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
ConfigManager
|
|
61
|
+
};
|
|
62
|
+
//# sourceMappingURL=config-manager.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/utils/config-manager.ts"],
|
|
4
|
+
"sourcesContent": ["import { LazyGcMap } from \"@simplysm/core-common\";\nimport { fsExists, fsReadJson, FsWatcher } from \"@simplysm/core-node\";\nimport path from \"path\";\nimport { createConsola } from \"consola\";\n\nconst logger = createConsola().withTag(\"service-server:ConfigManager\");\n\nexport class ConfigManager {\n // \uAC12: Config \uAC1D\uCCB4, \uD0A4: \uD30C\uC77C \uACBD\uB85C\n private static readonly _cache = new LazyGcMap<string, unknown>({\n gcInterval: 10 * 60 * 1000, // 10\uBD84\uB9C8\uB2E4\n expireTime: 60 * 60 * 1000, // 1\uC2DC\uAC04 \uB9CC\uB8CC\n onExpire: async (filePath) => {\n logger.debug(`\uC124\uC815 \uCE90\uC2DC \uB9CC\uB8CC \uBC0F \uAC10\uC2DC \uD574\uC81C: ${path.basename(filePath)}`);\n await ConfigManager._closeWatcher(filePath);\n },\n });\n\n private static readonly _watchers = new Map<string, FsWatcher>();\n\n static async getConfig<T>(filePath: string): Promise<T | undefined> {\n // 1. \uCE90\uC2DC \uC801\uC911 (\uC2DC\uAC04 \uC790\uB3D9 \uAC31\uC2E0)\n if (this._cache.has(filePath)) {\n return this._cache.get(filePath) as T;\n }\n\n if (!(await fsExists(filePath))) return undefined;\n\n // 2. \uB85C\uB4DC \uBC0F \uCE90\uC2DC\n const config = await fsReadJson(filePath);\n this._cache.set(filePath, config);\n\n // 3. Watcher \uB4F1\uB85D\n if (!this._watchers.has(filePath)) {\n try {\n const watcher = await FsWatcher.watch([filePath]);\n this._watchers.set(filePath, watcher);\n\n watcher.onChange({ delay: 100 }, async () => {\n if (!(await fsExists(filePath))) {\n this._cache.delete(filePath);\n await this._closeWatcher(filePath);\n logger.debug(`\uC124\uC815 \uD30C\uC77C \uC0AD\uC81C\uB428: ${path.basename(filePath)}`);\n return;\n }\n\n try {\n const newConfig = await fsReadJson(filePath);\n this._cache.set(filePath, newConfig);\n logger.debug(`\uC124\uC815 \uD30C\uC77C \uC2E4\uC2DC\uAC04 \uAC31\uC2E0: ${path.basename(filePath)}`);\n } catch (err) {\n logger.warn(`\uC124\uC815 \uD30C\uC77C \uAC31\uC2E0 \uC2E4\uD328: ${filePath}`, err);\n }\n });\n } catch (err) {\n logger.error(`\uAC10\uC2DC \uC2E4\uD328: ${filePath}`, err);\n }\n }\n\n return config as T;\n }\n\n private static async _closeWatcher(filePath: string) {\n const watcher = this._watchers.get(filePath);\n if (watcher != null) {\n await watcher.close();\n this._watchers.delete(filePath);\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,UAAU,YAAY,iBAAiB;AAChD,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,SAAS,cAAc,EAAE,QAAQ,8BAA8B;AAE9D,MAAM,cAAc;AAAA;AAAA,EAEzB,OAAwB,SAAS,IAAI,UAA2B;AAAA,IAC9D,YAAY,KAAK,KAAK;AAAA;AAAA,IACtB,YAAY,KAAK,KAAK;AAAA;AAAA,IACtB,UAAU,OAAO,aAAa;AAC5B,aAAO,MAAM,4EAAqB,KAAK,SAAS,QAAQ,CAAC,EAAE;AAC3D,YAAM,cAAc,cAAc,QAAQ;AAAA,IAC5C;AAAA,EACF,CAAC;AAAA,EAED,OAAwB,YAAY,oBAAI,IAAuB;AAAA,EAE/D,aAAa,UAAa,UAA0C;AAElE,QAAI,KAAK,OAAO,IAAI,QAAQ,GAAG;AAC7B,aAAO,KAAK,OAAO,IAAI,QAAQ;AAAA,IACjC;AAEA,QAAI,CAAE,MAAM,SAAS,QAAQ,EAAI,QAAO;AAGxC,UAAM,SAAS,MAAM,WAAW,QAAQ;AACxC,SAAK,OAAO,IAAI,UAAU,MAAM;AAGhC,QAAI,CAAC,KAAK,UAAU,IAAI,QAAQ,GAAG;AACjC,UAAI;AACF,cAAM,UAAU,MAAM,UAAU,MAAM,CAAC,QAAQ,CAAC;AAChD,aAAK,UAAU,IAAI,UAAU,OAAO;AAEpC,gBAAQ,SAAS,EAAE,OAAO,IAAI,GAAG,YAAY;AAC3C,cAAI,CAAE,MAAM,SAAS,QAAQ,GAAI;AAC/B,iBAAK,OAAO,OAAO,QAAQ;AAC3B,kBAAM,KAAK,cAAc,QAAQ;AACjC,mBAAO,MAAM,iDAAc,KAAK,SAAS,QAAQ,CAAC,EAAE;AACpD;AAAA,UACF;AAEA,cAAI;AACF,kBAAM,YAAY,MAAM,WAAW,QAAQ;AAC3C,iBAAK,OAAO,IAAI,UAAU,SAAS;AACnC,mBAAO,MAAM,8DAAiB,KAAK,SAAS,QAAQ,CAAC,EAAE;AAAA,UACzD,SAAS,KAAK;AACZ,mBAAO,KAAK,wDAAgB,QAAQ,IAAI,GAAG;AAAA,UAC7C;AAAA,QACF,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,eAAO,MAAM,8BAAU,QAAQ,IAAI,GAAG;AAAA,MACxC;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,aAAqB,cAAc,UAAkB;AACnD,UAAM,UAAU,KAAK,UAAU,IAAI,QAAQ;AAC3C,QAAI,WAAW,MAAM;AACnB,YAAM,QAAQ,MAAM;AACpB,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createWorker } from "@simplysm/core-node";
|
|
2
|
+
import { ServiceProtocol } from "@simplysm/service-common";
|
|
3
|
+
const protocol = new ServiceProtocol();
|
|
4
|
+
var service_protocol_worker_default = createWorker({
|
|
5
|
+
encode: (uuid, message) => {
|
|
6
|
+
return protocol.encode(uuid, message);
|
|
7
|
+
},
|
|
8
|
+
decode: (bytes) => {
|
|
9
|
+
return protocol.decode(bytes);
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
export {
|
|
13
|
+
service_protocol_worker_default as default
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=service-protocol.worker.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/workers/service-protocol.worker.ts"],
|
|
4
|
+
"sourcesContent": ["import { createWorker } from \"@simplysm/core-node\";\nimport type { Bytes } from \"@simplysm/core-common\";\nimport type { ServiceMessageDecodeResult, ServiceMessage } from \"@simplysm/service-common\";\nimport { ServiceProtocol } from \"@simplysm/service-common\";\n\nconst protocol = new ServiceProtocol();\n\nexport default createWorker({\n encode: (uuid: string, message: ServiceMessage): { chunks: Bytes[]; totalSize: number } => {\n return protocol.encode(uuid, message);\n },\n decode: (bytes: Bytes): ServiceMessageDecodeResult<ServiceMessage> => {\n return protocol.decode(bytes);\n },\n});\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAG7B,SAAS,uBAAuB;AAEhC,MAAM,WAAW,IAAI,gBAAgB;AAErC,IAAO,kCAAQ,aAAa;AAAA,EAC1B,QAAQ,CAAC,MAAc,YAAoE;AACzF,WAAO,SAAS,OAAO,MAAM,OAAO;AAAA,EACtC;AAAA,EACA,QAAQ,CAAC,UAA6D;AACpE,WAAO,SAAS,OAAO,KAAK;AAAA,EAC9B;AACF,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@simplysm/service-server",
|
|
3
|
+
"sideEffects": false,
|
|
4
|
+
"version": "13.0.0-beta.6",
|
|
5
|
+
"description": "심플리즘 패키지 - 서비스 모듈 (server)",
|
|
6
|
+
"author": "김석래",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/kslhunter/simplysm.git",
|
|
10
|
+
"directory": "packages/service-server"
|
|
11
|
+
},
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@fastify/cors": "^11.2.0",
|
|
18
|
+
"@fastify/helmet": "^13.0.2",
|
|
19
|
+
"@fastify/middie": "^9.1.0",
|
|
20
|
+
"@fastify/multipart": "^9.4.0",
|
|
21
|
+
"@fastify/reply-from": "^12.5.0",
|
|
22
|
+
"@fastify/static": "^9.0.0",
|
|
23
|
+
"@fastify/websocket": "^11.2.0",
|
|
24
|
+
"@simplysm/core-common": "workspace:*",
|
|
25
|
+
"@simplysm/core-node": "workspace:*",
|
|
26
|
+
"@simplysm/orm-common": "workspace:*",
|
|
27
|
+
"@simplysm/orm-node": "workspace:*",
|
|
28
|
+
"@simplysm/service-common": "workspace:*",
|
|
29
|
+
"bufferutil": "^4.1.0",
|
|
30
|
+
"consola": "^3.4.2",
|
|
31
|
+
"fastify": "^5.7.1",
|
|
32
|
+
"jose": "^6.1.3",
|
|
33
|
+
"mime": "^4.1.0",
|
|
34
|
+
"nodemailer": "^7.0.12",
|
|
35
|
+
"semver": "^7.7.3",
|
|
36
|
+
"utf-8-validate": "^6.0.6",
|
|
37
|
+
"ws": "^8.19.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/nodemailer": "^7.0.5",
|
|
41
|
+
"@types/semver": "^7.7.1",
|
|
42
|
+
"@types/ws": "^8.18.1"
|
|
43
|
+
}
|
|
44
|
+
}
|