node-server-engine 2.0.2 → 2.2.0

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 CHANGED
@@ -528,22 +528,24 @@ const server = new Server({
528
528
  });
529
529
  ```
530
530
 
531
- | Parameter | Type | Description | Default |
532
- | ----------------- | ------------------------------------------ | --------------------------------------------------------- | ------------ |
533
- | handlers | Array\<[MessageHandler](#message-handler)> | A list of message handlers to use | **required** |
534
- | authCallbacks | Function \| Array\<Function> | Callbacks called when a client successfully authenticates | [] |
535
- | initCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is created | [] |
536
- | shutdownCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is destroyed | [] |
531
+ | Parameter | Type | Description | Default |
532
+ | ----------------- | ------------------------------------------ | --------------------------------------------------------------------------------- | ------------ |
533
+ | handlers | Array\<[MessageHandler](#message-handler)> | A list of message handlers to use | **required** |
534
+ | authCallbacks | Function \| Array\<Function> | Callbacks called when a client successfully authenticates | [] |
535
+ | initCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is created | [] |
536
+ | shutdownCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is destroyed | [] |
537
+ | authTimeoutMs | Number | Close the socket with code 4401 if it has not authenticated within this time (ms) | never |
537
538
 
538
539
  #### Client Properties & Methods
539
540
 
540
- | Property/Method | Type | Description |
541
- | ----------------- | ----------------------------- | ----------------------------------------------------------------- |
542
- | id | String | Unique identifier for the connection |
543
- | establishedAt | Date | Timestamp when connection was established |
544
- | isAuthenticated() | () => Boolean | Check if client is authenticated |
545
- | getUser() | () => SocketUser \| undefined | Get authenticated user data (userId, deviceId, tokenId, audience) |
546
- | sendMessage() | (type, payload, options) | Send a message to the client |
541
+ | Property/Method | Type | Description |
542
+ | ----------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------- |
543
+ | id | String | Unique identifier for the connection |
544
+ | establishedAt | Date | Timestamp when connection was established |
545
+ | isAuthenticated() | () => Boolean | Check if client is authenticated |
546
+ | getUser() | () => SocketUser \| undefined | Get authenticated user data (userId, deviceId, tokenId, audience, the token's `user` claim, `expiresAt`) |
547
+ | sendMessage() | (type, payload, options) | Send a message to the client |
548
+ | close() | (code?, reason?) | Close the connection, e.g. `close(4403, 'Not allowed')` from an `authCallbacks` check |
547
549
 
548
550
  #### Client Authentication
549
551
 
@@ -1055,14 +1057,19 @@ Sequelize.closeSequelize();
1055
1057
 
1056
1058
  It can be configured through environment variables
1057
1059
 
1058
- | env | description | default |
1059
- | ------------ | ------------------------------------------------- | -------- |
1060
- | SQL_HOST | Host to which connect to | |
1061
- | SQL_PORT | Port on which SQL is served | 5432 |
1062
- | SQL_PASSWORD | Password used to authenticate with the SQL server | |
1063
- | SQL_USER | User used to authenticate with the SQL server | |
1064
- | SQL_DB | Database to which connect to | |
1065
- | SQL_TYPE | SQL type which connect to | postgres |
1060
+ | env | description | default |
1061
+ | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
1062
+ | SQL_HOST | Host to which connect to | |
1063
+ | SQL_PORT | Port on which SQL is served | 5432 |
1064
+ | SQL_PASSWORD | Password used to authenticate with the SQL server | |
1065
+ | SQL_USER | User used to authenticate with the SQL server | |
1066
+ | SQL_DB | Database to which connect to | |
1067
+ | SQL_TYPE | SQL type which connect to | postgres |
1068
+ | SQL_ENCRYPT | `mssql` only: set to `false` to disable TLS | true |
1069
+ | SQL_LOWERCASE_GUIDS | `mssql` only: `true` returns UNIQUEIDENTIFIER values in lower case, like JavaScript UUIDs | false |
1070
+ | SQL_TRUST_SERVER_CERT | `mssql` only: `true` skips certificate verification (self-signed development servers only) | false |
1071
+ | SQL_POOL_MAX | Maximum connections in the pool (always 1 when `NODE_ENV=test`) | 5 |
1072
+ | SQL_SYNC_MODELS | `true` creates missing tables from the models in `addModels()`; always on when `NODE_ENV=test`, off otherwise so the migrations own the schema | false |
1066
1073
 
1067
1074
  ---
1068
1075
 
@@ -1812,11 +1819,15 @@ if (!user) {
1812
1819
 
1813
1820
  In addition to the [common options](#common-options), WebError defines some other options specific for error reporting to clients.
1814
1821
 
1815
- | option | definition | example | default |
1816
- | ---------- | ---------------------------------------------------------------- | ------------ | ------------ |
1817
- | errorCode | A machine readable error code that will be parsed by the client. | unknown-user | **required** |
1818
- | statusCode | HTTP status code of the response. | 400 | 500 |
1819
- | hint | A human readable error message that is intended for developers | |
1822
+ | option | definition | example | default |
1823
+ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ------------ |
1824
+ | errorCode | A machine readable error code that will be parsed by the client. | unknown-user | **required** |
1825
+ | statusCode | HTTP status code of the response. | 400 | 500 |
1826
+ | hint | A human readable error message that is intended for developers | |
1827
+ | details | Public structured data the client acts on, returned as `details` (attempts left, per-row errors...). Never put secrets here; `data` stays in the logs only. | `{ attemptsLeft: 2 }` | |
1828
+ | retryAfterSec | Seconds before the client may retry, sent as the `Retry-After` header | 60 | |
1829
+
1830
+ The error middleware answers `{ errorCode, hint, details? }` with the given status code.
1820
1831
 
1821
1832
  ## Middlewares
1822
1833
 
@@ -33,7 +33,7 @@ const config = {
33
33
  },
34
34
  dialectOptions: {},
35
35
  pool: {
36
- max: process.env.NODE_ENV === 'test' ? 1 : 5,
36
+ max: process.env.NODE_ENV === 'test' ? 1 : Number(process.env.SQL_POOL_MAX) || 5,
37
37
  min: 0,
38
38
  acquire: process.env.NODE_ENV === 'test' ? 1000 : 30000,
39
39
  // Reduce acquire timeout in test
@@ -45,14 +45,20 @@ const config = {
45
45
 
46
46
  // Additional configurations for MSSQL
47
47
  if (config.dialect === 'mssql') {
48
+ // tedious connection options live under dialectOptions.options
48
49
  config.dialectOptions = {
49
- requestTimeout: 30000,
50
- // Increase request timeout for MSSQL
51
- connectTimeout: 30000,
52
- // Increase connection timeout for MSSQL
53
- encrypt: true,
54
- // Use encryption for MSSQL (set to false if not needed)
55
- trustServerCertificate: true // Set to true if using self-signed certificates
50
+ options: {
51
+ requestTimeout: 30000,
52
+ // Increase request timeout for MSSQL
53
+ connectTimeout: 30000,
54
+ // Increase connection timeout for MSSQL
55
+ // Encrypted connections by default; SQL_ENCRYPT=false only for servers without TLS
56
+ encrypt: process.env.SQL_ENCRYPT !== 'false',
57
+ // The server certificate is verified unless SQL_TRUST_SERVER_CERT=true (self-signed development servers)
58
+ trustServerCertificate: process.env.SQL_TRUST_SERVER_CERT === 'true',
59
+ // UNIQUEIDENTIFIER values come back upper case unless SQL_LOWERCASE_GUIDS=true (matches JS / client UUIDs)
60
+ lowerCaseGuids: process.env.SQL_LOWERCASE_GUIDS === 'true'
61
+ }
56
62
  };
57
63
  }
58
64
 
@@ -1 +1 @@
1
- {"version":3,"file":"Sequelize.config.js","names":["_Sequelize","require","validateSequelizeEnvironment","isUnixSocket","process","env","SQL_HOST","startsWith","config","username","SQL_USER","password","SQL_PASSWORD","database","SQL_DB","host","port","undefined","SQL_PORT","parseInt","dialect","SQL_TYPE","logging","SQL_LOGGING","console","log","define","freezeTableName","charset","collate","retry","max","NODE_ENV","dialectOptions","pool","min","acquire","idle","evict","requestTimeout","connectTimeout","encrypt","trustServerCertificate","timezone","ssl","PG_SSL","rejectUnauthorized","development","exports","staging","production","test","_default","default"],"sources":["../../../src/entities/Sequelize/Sequelize.config.ts"],"sourcesContent":["import { Options, Dialect } from 'sequelize';\nimport { validateSequelizeEnvironment } from './Sequelize.validate';\n\n// Check that environment variables are correctly set\nvalidateSequelizeEnvironment();\n\n// Check if SQL_HOST is a Unix socket path (Cloud SQL)\nconst isUnixSocket = process.env.SQL_HOST?.startsWith('/');\n\nconst config: Options = {\n username: process.env.SQL_USER,\n password: process.env.SQL_PASSWORD,\n database: process.env.SQL_DB,\n host: process.env.SQL_HOST,\n // Don't set port for Unix socket connections (Cloud SQL)\n port: isUnixSocket\n ? undefined\n : process.env.SQL_PORT\n ? parseInt(process.env.SQL_PORT)\n : 5432,\n dialect: (process.env.SQL_TYPE ?? 'postgres') as Dialect, // Dynamically set the dialect (postgres, mysql, mssql)\n logging: process.env.SQL_LOGGING === 'true' ? console.log : false, // Enable SQL logging with SQL_LOGGING=true\n define: {\n freezeTableName: false, // Prevent automatic pluralization of table names\n charset: 'utf8mb4', // Set character set for MySQL/MariaDB\n collate: 'utf8mb4_unicode_ci' // Set collation for MySQL/MariaDB\n },\n retry: {\n max: process.env.NODE_ENV === 'test' ? 0 : 3 // Disable retries in test environment to prevent unhandled rejections\n },\n dialectOptions: {},\n pool: {\n max: process.env.NODE_ENV === 'test' ? 1 : 5,\n min: 0,\n acquire: process.env.NODE_ENV === 'test' ? 1000 : 30000, // Reduce acquire timeout in test\n idle: process.env.NODE_ENV === 'test' ? 1000 : 10000, // Reduce idle time in test\n evict: process.env.NODE_ENV === 'test' ? 100 : 1000 // How often to check for idle connections\n }\n};\n\n// Additional configurations for MSSQL\nif (config.dialect === 'mssql') {\n config.dialectOptions = {\n requestTimeout: 30000, // Increase request timeout for MSSQL\n connectTimeout: 30000, // Increase connection timeout for MSSQL\n encrypt: true, // Use encryption for MSSQL (set to false if not needed)\n trustServerCertificate: true // Set to true if using self-signed certificates\n };\n}\n\n// Add specific dialect configurations if necessary for MySQL/PostgreSQL\nif (config.dialect === 'mysql') {\n config.dialectOptions = {\n timezone: 'Z' // Set timezone for MySQL\n };\n}\n\nif (config.dialect === 'postgres') {\n config.dialectOptions = {\n ssl: process.env.PG_SSL === 'true' ? { rejectUnauthorized: false } : false // SSL options for PostgreSQL (if required)\n };\n}\n\nexport const development = config;\nexport const staging = config;\nexport const production = config;\nexport const test = config;\nexport default config;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"Sequelize.config.js","names":["_Sequelize","require","validateSequelizeEnvironment","isUnixSocket","process","env","SQL_HOST","startsWith","config","username","SQL_USER","password","SQL_PASSWORD","database","SQL_DB","host","port","undefined","SQL_PORT","parseInt","dialect","SQL_TYPE","logging","SQL_LOGGING","console","log","define","freezeTableName","charset","collate","retry","max","NODE_ENV","dialectOptions","pool","Number","SQL_POOL_MAX","min","acquire","idle","evict","options","requestTimeout","connectTimeout","encrypt","SQL_ENCRYPT","trustServerCertificate","SQL_TRUST_SERVER_CERT","lowerCaseGuids","SQL_LOWERCASE_GUIDS","timezone","ssl","PG_SSL","rejectUnauthorized","development","exports","staging","production","test","_default","default"],"sources":["../../../src/entities/Sequelize/Sequelize.config.ts"],"sourcesContent":["import { Options, Dialect } from 'sequelize';\nimport { validateSequelizeEnvironment } from './Sequelize.validate';\n\n// Check that environment variables are correctly set\nvalidateSequelizeEnvironment();\n\n// Check if SQL_HOST is a Unix socket path (Cloud SQL)\nconst isUnixSocket = process.env.SQL_HOST?.startsWith('/');\n\nconst config: Options = {\n username: process.env.SQL_USER,\n password: process.env.SQL_PASSWORD,\n database: process.env.SQL_DB,\n host: process.env.SQL_HOST,\n // Don't set port for Unix socket connections (Cloud SQL)\n port: isUnixSocket\n ? undefined\n : process.env.SQL_PORT\n ? parseInt(process.env.SQL_PORT)\n : 5432,\n dialect: (process.env.SQL_TYPE ?? 'postgres') as Dialect, // Dynamically set the dialect (postgres, mysql, mssql)\n logging: process.env.SQL_LOGGING === 'true' ? console.log : false, // Enable SQL logging with SQL_LOGGING=true\n define: {\n freezeTableName: false, // Prevent automatic pluralization of table names\n charset: 'utf8mb4', // Set character set for MySQL/MariaDB\n collate: 'utf8mb4_unicode_ci' // Set collation for MySQL/MariaDB\n },\n retry: {\n max: process.env.NODE_ENV === 'test' ? 0 : 3 // Disable retries in test environment to prevent unhandled rejections\n },\n dialectOptions: {},\n pool: {\n max:\n process.env.NODE_ENV === 'test'\n ? 1\n : Number(process.env.SQL_POOL_MAX) || 5,\n min: 0,\n acquire: process.env.NODE_ENV === 'test' ? 1000 : 30000, // Reduce acquire timeout in test\n idle: process.env.NODE_ENV === 'test' ? 1000 : 10000, // Reduce idle time in test\n evict: process.env.NODE_ENV === 'test' ? 100 : 1000 // How often to check for idle connections\n }\n};\n\n// Additional configurations for MSSQL\nif (config.dialect === 'mssql') {\n // tedious connection options live under dialectOptions.options\n config.dialectOptions = {\n options: {\n requestTimeout: 30000, // Increase request timeout for MSSQL\n connectTimeout: 30000, // Increase connection timeout for MSSQL\n // Encrypted connections by default; SQL_ENCRYPT=false only for servers without TLS\n encrypt: process.env.SQL_ENCRYPT !== 'false',\n // The server certificate is verified unless SQL_TRUST_SERVER_CERT=true (self-signed development servers)\n trustServerCertificate: process.env.SQL_TRUST_SERVER_CERT === 'true',\n // UNIQUEIDENTIFIER values come back upper case unless SQL_LOWERCASE_GUIDS=true (matches JS / client UUIDs)\n lowerCaseGuids: process.env.SQL_LOWERCASE_GUIDS === 'true'\n }\n };\n}\n\n// Add specific dialect configurations if necessary for MySQL/PostgreSQL\nif (config.dialect === 'mysql') {\n config.dialectOptions = {\n timezone: 'Z' // Set timezone for MySQL\n };\n}\n\nif (config.dialect === 'postgres') {\n config.dialectOptions = {\n ssl: process.env.PG_SSL === 'true' ? { rejectUnauthorized: false } : false // SSL options for PostgreSQL (if required)\n };\n}\n\nexport const development = config;\nexport const staging = config;\nexport const production = config;\nexport const test = config;\nexport default config;\n"],"mappings":"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","ignoreList":[]}
@@ -141,7 +141,15 @@ function addModels(models) {
141
141
  convertToPlain(result);
142
142
  }
143
143
  });
144
- sequelizeClient.sync();
144
+
145
+ // Creating tables from the models is for the in-memory test database (or when explicitly asked).
146
+ // Elsewhere the schema belongs to the migrations: a sync racing them would create tables without
147
+ // their constraints and triggers.
148
+ if (process.env.NODE_ENV === 'test' || process.env.SQL_SYNC_MODELS === 'true') {
149
+ sequelizeClient.sync().catch(error => {
150
+ (0, _report.reportError)(error);
151
+ });
152
+ }
145
153
  }
146
154
 
147
155
  /** Create Sequelize client */
@@ -1 +1 @@
1
- {"version":3,"file":"Sequelize.js","names":["_path","_interopRequireDefault","require","_sequelizeTypescript","_Sequelize","_Sequelize2","_EngineError","_LifecycleController","_report","sequelizeClient","exports","sequelize","Proxy","get","target","property","init","shutdown","addModels","EngineError","message","reportInfo","data","host","process","env","SQL_HOST","database","SQL_DB","user","SQL_USER","port","SQL_PORT","dialect","SQL_TYPE","isUnixSocket","startsWith","sqlStorage","toLowerCase","SQL_STORAGE","path","join","cwd","undefined","createSequelizeClient","NODE_ENV","authenticate","getDialect","err","reportError","error","Error","String","stack","LifecycleController","register","close","console","models","addHook","result","convertToPlain","instance","modelInstance","toJSON","plain","Object","keys","forEach","key","Array","isArray","sync","Sequelize","storage","logging","sqlType","trim","length","SQL_LOGGING","log","validateSequelizeEnvironment","username","dbConfig","poolMax","pool","max","poolMin","min","SQL_PASSWORD"],"sources":["../../../src/entities/Sequelize/Sequelize.ts"],"sourcesContent":["import path from 'path';\nimport { Sequelize, ModelCtor } from 'sequelize-typescript';\nimport dbConfig from './Sequelize.config';\nimport { Sequelize as SequelizeInterface } from './Sequelize.types';\nimport { validateSequelizeEnvironment } from './Sequelize.validate';\nimport { EngineError } from 'entities/EngineError';\nimport { LifecycleController } from 'entities/LifecycleController';\nimport { reportInfo, reportError } from 'utils/report';\n\nexport let sequelizeClient: undefined | Sequelize;\n\n/**\n * We use a proxy to extend the Sequelize behavior and allow initialization of the client only when needed while keeping the same interface\n */\nexport const sequelize = new Proxy<SequelizeInterface>(\n // Due to how we do the proxying, we are forced to ignore ts-errors, we could never create an object that respects the interface properly\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n {},\n {\n get: function (target, property): unknown {\n if (property === 'init') return init;\n if (property === 'shutdown') return shutdown;\n if (property === 'addModels') return addModels;\n // Create the sequelize client if it does not exist\n if (!sequelizeClient)\n throw new EngineError({\n message: 'Sequelize client was not initialized'\n });\n // Call the function on the sequelize client\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n return sequelizeClient[property] as unknown;\n }\n }\n);\n\n/** On init create client and connect */\nexport async function init(): Promise<void> {\n // Ignore if already initialized\n if (sequelizeClient) return;\n\n reportInfo({\n message: 'Initializing Sequelize client',\n data: {\n host: process.env.SQL_HOST,\n database: process.env.SQL_DB,\n user: process.env.SQL_USER,\n port: process.env.SQL_PORT,\n dialect: process.env.SQL_TYPE || 'postgres',\n isUnixSocket: process.env.SQL_HOST?.startsWith('/'),\n sqlStorage:\n (process.env.SQL_TYPE ?? '').toLowerCase() === 'sqlite'\n ? process.env.SQL_STORAGE ||\n path.join(process.cwd(), 'database.sqlite')\n : undefined\n }\n });\n\n sequelizeClient = createSequelizeClient();\n\n // In test environment, authentication happens automatically with SQLite\n // For other environments, authenticate and log the result\n if (process.env.NODE_ENV !== 'test') {\n reportInfo({\n message: 'Starting database authentication'\n });\n\n try {\n await sequelizeClient.authenticate();\n reportInfo({\n message: 'Connected to database successfully',\n data: {\n host: process.env.SQL_HOST,\n database: process.env.SQL_DB,\n dialect: sequelizeClient?.getDialect()\n }\n });\n } catch (err) {\n reportError({\n message: 'Failed to connect to database',\n data: {\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n host: process.env.SQL_HOST,\n database: process.env.SQL_DB,\n user: process.env.SQL_USER,\n port: process.env.SQL_PORT\n }\n });\n if (err instanceof Error) {\n reportError(err);\n }\n throw err;\n }\n }\n\n LifecycleController.register(sequelize);\n}\n\n/** On shutdown close connection and clear client */\nexport async function shutdown(): Promise<void> {\n if (sequelizeClient) {\n try {\n // For SQLite, just close the client (closing connection manager first causes SQLITE_MISUSE)\n // For other dialects, sequelize.close() handles everything properly\n await sequelizeClient.close();\n } catch (error) {\n // Ignore errors during shutdown\n if (process.env.NODE_ENV !== 'test') {\n console.error('Error closing sequelize client:', error);\n }\n }\n }\n sequelizeClient = undefined;\n}\n\nexport function addModels(models: Array<ModelCtor>): void {\n if (!sequelizeClient)\n throw new EngineError({\n message: 'Sequelize client was not initialized'\n });\n sequelizeClient.addModels(models);\n\n // Override toJSON behavior globally to ensure consistency between dev and prod\n // In dev (ts-node), property access works directly on instances\n // In prod (compiled), we need explicit serialization\n // This hook intercepts all afterFind results and converts them to plain objects\n sequelizeClient.addHook('afterFind', (result) => {\n if (!result) return;\n\n const convertToPlain = (instance: unknown) => {\n if (!instance || typeof instance !== 'object') return instance;\n const modelInstance = instance as {\n toJSON?: () => Record<string, unknown>;\n } & Record<string, unknown>;\n if (!modelInstance.toJSON) return instance;\n const plain = modelInstance.toJSON();\n // Copy the plain object properties back to the instance\n // This makes property access work without .toJSON() in compiled code\n Object.keys(plain).forEach((key) => {\n modelInstance[key] = plain[key];\n });\n };\n\n if (Array.isArray(result)) {\n result.forEach(convertToPlain);\n } else {\n convertToPlain(result);\n }\n });\n\n sequelizeClient.sync();\n}\n\n/** Create Sequelize client */\nexport function createSequelizeClient(): Sequelize {\n // Use in-memory SQLite for test environment to avoid real database connections\n if (process.env.NODE_ENV === 'test') {\n return new Sequelize({\n dialect: 'sqlite',\n storage: ':memory:',\n logging: false\n });\n }\n\n // File-based SQLite for local / offline dev (no SQL_HOST / live server)\n const sqlType = (process.env.SQL_TYPE ?? '').toLowerCase();\n if (sqlType === 'sqlite') {\n const storage =\n process.env.SQL_STORAGE && process.env.SQL_STORAGE.trim().length > 0\n ? process.env.SQL_STORAGE.trim()\n : path.join(process.cwd(), 'database.sqlite');\n reportInfo({\n message: 'Creating Sequelize client (SQLite file)',\n data: { storage, dialect: 'sqlite' }\n });\n return new Sequelize({\n dialect: 'sqlite',\n storage,\n logging: process.env.SQL_LOGGING === 'true' ? console.log : false\n });\n }\n\n // Throw an error if there is no host initial\n validateSequelizeEnvironment();\n\n reportInfo({\n message: 'Creating Sequelize client with config',\n data: {\n database: process.env.SQL_DB,\n username: process.env.SQL_USER,\n host: process.env.SQL_HOST,\n port: dbConfig.port,\n dialect: dbConfig.dialect,\n isUnixSocket: process.env.SQL_HOST?.startsWith('/'),\n poolMax: dbConfig.pool?.max,\n poolMin: dbConfig.pool?.min\n }\n });\n\n return new Sequelize(\n process.env.SQL_DB as string,\n process.env.SQL_USER as string,\n process.env.SQL_PASSWORD as string,\n dbConfig\n );\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"Sequelize.js","names":["_path","_interopRequireDefault","require","_sequelizeTypescript","_Sequelize","_Sequelize2","_EngineError","_LifecycleController","_report","sequelizeClient","exports","sequelize","Proxy","get","target","property","init","shutdown","addModels","EngineError","message","reportInfo","data","host","process","env","SQL_HOST","database","SQL_DB","user","SQL_USER","port","SQL_PORT","dialect","SQL_TYPE","isUnixSocket","startsWith","sqlStorage","toLowerCase","SQL_STORAGE","path","join","cwd","undefined","createSequelizeClient","NODE_ENV","authenticate","getDialect","err","reportError","error","Error","String","stack","LifecycleController","register","close","console","models","addHook","result","convertToPlain","instance","modelInstance","toJSON","plain","Object","keys","forEach","key","Array","isArray","SQL_SYNC_MODELS","sync","catch","Sequelize","storage","logging","sqlType","trim","length","SQL_LOGGING","log","validateSequelizeEnvironment","username","dbConfig","poolMax","pool","max","poolMin","min","SQL_PASSWORD"],"sources":["../../../src/entities/Sequelize/Sequelize.ts"],"sourcesContent":["import path from 'path';\nimport { Sequelize, ModelCtor } from 'sequelize-typescript';\nimport dbConfig from './Sequelize.config';\nimport { Sequelize as SequelizeInterface } from './Sequelize.types';\nimport { validateSequelizeEnvironment } from './Sequelize.validate';\nimport { EngineError } from 'entities/EngineError';\nimport { LifecycleController } from 'entities/LifecycleController';\nimport { reportInfo, reportError } from 'utils/report';\n\nexport let sequelizeClient: undefined | Sequelize;\n\n/**\n * We use a proxy to extend the Sequelize behavior and allow initialization of the client only when needed while keeping the same interface\n */\nexport const sequelize = new Proxy<SequelizeInterface>(\n // Due to how we do the proxying, we are forced to ignore ts-errors, we could never create an object that respects the interface properly\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n {},\n {\n get: function (target, property): unknown {\n if (property === 'init') return init;\n if (property === 'shutdown') return shutdown;\n if (property === 'addModels') return addModels;\n // Create the sequelize client if it does not exist\n if (!sequelizeClient)\n throw new EngineError({\n message: 'Sequelize client was not initialized'\n });\n // Call the function on the sequelize client\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n return sequelizeClient[property] as unknown;\n }\n }\n);\n\n/** On init create client and connect */\nexport async function init(): Promise<void> {\n // Ignore if already initialized\n if (sequelizeClient) return;\n\n reportInfo({\n message: 'Initializing Sequelize client',\n data: {\n host: process.env.SQL_HOST,\n database: process.env.SQL_DB,\n user: process.env.SQL_USER,\n port: process.env.SQL_PORT,\n dialect: process.env.SQL_TYPE || 'postgres',\n isUnixSocket: process.env.SQL_HOST?.startsWith('/'),\n sqlStorage:\n (process.env.SQL_TYPE ?? '').toLowerCase() === 'sqlite'\n ? process.env.SQL_STORAGE ||\n path.join(process.cwd(), 'database.sqlite')\n : undefined\n }\n });\n\n sequelizeClient = createSequelizeClient();\n\n // In test environment, authentication happens automatically with SQLite\n // For other environments, authenticate and log the result\n if (process.env.NODE_ENV !== 'test') {\n reportInfo({\n message: 'Starting database authentication'\n });\n\n try {\n await sequelizeClient.authenticate();\n reportInfo({\n message: 'Connected to database successfully',\n data: {\n host: process.env.SQL_HOST,\n database: process.env.SQL_DB,\n dialect: sequelizeClient?.getDialect()\n }\n });\n } catch (err) {\n reportError({\n message: 'Failed to connect to database',\n data: {\n error: err instanceof Error ? err.message : String(err),\n stack: err instanceof Error ? err.stack : undefined,\n host: process.env.SQL_HOST,\n database: process.env.SQL_DB,\n user: process.env.SQL_USER,\n port: process.env.SQL_PORT\n }\n });\n if (err instanceof Error) {\n reportError(err);\n }\n throw err;\n }\n }\n\n LifecycleController.register(sequelize);\n}\n\n/** On shutdown close connection and clear client */\nexport async function shutdown(): Promise<void> {\n if (sequelizeClient) {\n try {\n // For SQLite, just close the client (closing connection manager first causes SQLITE_MISUSE)\n // For other dialects, sequelize.close() handles everything properly\n await sequelizeClient.close();\n } catch (error) {\n // Ignore errors during shutdown\n if (process.env.NODE_ENV !== 'test') {\n console.error('Error closing sequelize client:', error);\n }\n }\n }\n sequelizeClient = undefined;\n}\n\nexport function addModels(models: Array<ModelCtor>): void {\n if (!sequelizeClient)\n throw new EngineError({\n message: 'Sequelize client was not initialized'\n });\n sequelizeClient.addModels(models);\n\n // Override toJSON behavior globally to ensure consistency between dev and prod\n // In dev (ts-node), property access works directly on instances\n // In prod (compiled), we need explicit serialization\n // This hook intercepts all afterFind results and converts them to plain objects\n sequelizeClient.addHook('afterFind', (result) => {\n if (!result) return;\n\n const convertToPlain = (instance: unknown) => {\n if (!instance || typeof instance !== 'object') return instance;\n const modelInstance = instance as {\n toJSON?: () => Record<string, unknown>;\n } & Record<string, unknown>;\n if (!modelInstance.toJSON) return instance;\n const plain = modelInstance.toJSON();\n // Copy the plain object properties back to the instance\n // This makes property access work without .toJSON() in compiled code\n Object.keys(plain).forEach((key) => {\n modelInstance[key] = plain[key];\n });\n };\n\n if (Array.isArray(result)) {\n result.forEach(convertToPlain);\n } else {\n convertToPlain(result);\n }\n });\n\n // Creating tables from the models is for the in-memory test database (or when explicitly asked).\n // Elsewhere the schema belongs to the migrations: a sync racing them would create tables without\n // their constraints and triggers.\n if (\n process.env.NODE_ENV === 'test' ||\n process.env.SQL_SYNC_MODELS === 'true'\n ) {\n sequelizeClient.sync().catch((error: unknown) => {\n reportError(error);\n });\n }\n}\n\n/** Create Sequelize client */\nexport function createSequelizeClient(): Sequelize {\n // Use in-memory SQLite for test environment to avoid real database connections\n if (process.env.NODE_ENV === 'test') {\n return new Sequelize({\n dialect: 'sqlite',\n storage: ':memory:',\n logging: false\n });\n }\n\n // File-based SQLite for local / offline dev (no SQL_HOST / live server)\n const sqlType = (process.env.SQL_TYPE ?? '').toLowerCase();\n if (sqlType === 'sqlite') {\n const storage =\n process.env.SQL_STORAGE && process.env.SQL_STORAGE.trim().length > 0\n ? process.env.SQL_STORAGE.trim()\n : path.join(process.cwd(), 'database.sqlite');\n reportInfo({\n message: 'Creating Sequelize client (SQLite file)',\n data: { storage, dialect: 'sqlite' }\n });\n return new Sequelize({\n dialect: 'sqlite',\n storage,\n logging: process.env.SQL_LOGGING === 'true' ? console.log : false\n });\n }\n\n // Throw an error if there is no host initial\n validateSequelizeEnvironment();\n\n reportInfo({\n message: 'Creating Sequelize client with config',\n data: {\n database: process.env.SQL_DB,\n username: process.env.SQL_USER,\n host: process.env.SQL_HOST,\n port: dbConfig.port,\n dialect: dbConfig.dialect,\n isUnixSocket: process.env.SQL_HOST?.startsWith('/'),\n poolMax: dbConfig.pool?.max,\n poolMin: dbConfig.pool?.min\n }\n });\n\n return new Sequelize(\n process.env.SQL_DB as string,\n process.env.SQL_USER as string,\n process.env.SQL_PASSWORD as string,\n dbConfig\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -37,6 +37,12 @@ export declare class SocketClient {
37
37
  private audience;
38
38
  /** Status tracking variable used to check the liveness of connections */
39
39
  private isAlive;
40
+ /** The `user` claim of the token the client authenticated with */
41
+ private tokenUser;
42
+ /** Timestamp (ms) at which the current authentication expires */
43
+ private authExpiresAt;
44
+ /** Closes the connection if the client does not authenticate in time */
45
+ private authTimeout;
40
46
  /** Timer used to track interval between client pinging */
41
47
  private pingInterval;
42
48
  /**
@@ -54,6 +60,12 @@ export declare class SocketClient {
54
60
  * Send a message to the client
55
61
  */
56
62
  sendMessage(type: string, payload?: unknown, options?: SocketClientSendMessageOptions): void;
63
+ /**
64
+ * Close the connection
65
+ * @param code - WebSocket close code (4000-4999 are free for applications, e.g. 4401 / 4403)
66
+ * @param reason - Human readable reason, sent to the client
67
+ */
68
+ close(code?: number, reason?: string): void;
57
69
  /** Get the client authentication status */
58
70
  isAuthenticated(): boolean;
59
71
  /** Fetch data on the user if he is authenticated */
@@ -61,6 +61,12 @@ let SocketClient = exports.SocketClient = /*#__PURE__*/function () {
61
61
  (0, _defineProperty2.default)(this, "audience", void 0);
62
62
  /** Status tracking variable used to check the liveness of connections */
63
63
  (0, _defineProperty2.default)(this, "isAlive", true);
64
+ /** The `user` claim of the token the client authenticated with */
65
+ (0, _defineProperty2.default)(this, "tokenUser", void 0);
66
+ /** Timestamp (ms) at which the current authentication expires */
67
+ (0, _defineProperty2.default)(this, "authExpiresAt", void 0);
68
+ /** Closes the connection if the client does not authenticate in time */
69
+ (0, _defineProperty2.default)(this, "authTimeout", void 0);
64
70
  /** Timer used to track interval between client pinging */
65
71
  (0, _defineProperty2.default)(this, "pingInterval", void 0);
66
72
  this.id = (0, _crypto.randomUUID)();
@@ -91,9 +97,15 @@ let SocketClient = exports.SocketClient = /*#__PURE__*/function () {
91
97
  (0, _report.reportInfo)({
92
98
  message: `Client connected [${this.id}]`
93
99
  });
100
+ // Optionally close connections that do not authenticate in time
101
+ if (options.authTimeoutMs && options.authTimeoutMs > 0) {
102
+ this.authTimeout = setTimeout(() => {
103
+ if (!this.authenticated) this.close(4401, 'Authentication required');
104
+ }, options.authTimeoutMs);
105
+ }
94
106
  // On socket close, deregister from clients list
95
- this.ws.on('close', () => {
96
- this.handleClose.bind(this)().catch(error => {
107
+ this.ws.on('close', (code, reason) => {
108
+ this.handleClose.bind(this)(code, reason?.toString()).catch(error => {
97
109
  (0, _report.reportError)(error);
98
110
  });
99
111
  });
@@ -184,6 +196,19 @@ let SocketClient = exports.SocketClient = /*#__PURE__*/function () {
184
196
  }
185
197
  }
186
198
 
199
+ /**
200
+ * Close the connection
201
+ * @param code - WebSocket close code (4000-4999 are free for applications, e.g. 4401 / 4403)
202
+ * @param reason - Human readable reason, sent to the client
203
+ */
204
+ }, {
205
+ key: "close",
206
+ value: function close(code = 1000, reason) {
207
+ this.clearTimeouts();
208
+ if (this.authTimeout) clearTimeout(this.authTimeout);
209
+ this.ws.close(code, reason);
210
+ }
211
+
187
212
  /** Get the client authentication status */
188
213
  }, {
189
214
  key: "isAuthenticated",
@@ -199,7 +224,9 @@ let SocketClient = exports.SocketClient = /*#__PURE__*/function () {
199
224
  userId: this.userId,
200
225
  deviceId: this.deviceId,
201
226
  tokenId: this.tokenId,
202
- audience: this.audience
227
+ audience: this.audience,
228
+ user: this.tokenUser,
229
+ expiresAt: this.authExpiresAt
203
230
  };
204
231
  return undefined;
205
232
  }
@@ -258,6 +285,9 @@ let SocketClient = exports.SocketClient = /*#__PURE__*/function () {
258
285
  const payload = await (0, _jwt.jwtVerify)(token);
259
286
  this.setUserData(payload);
260
287
  this.authenticated = true;
288
+ this.tokenUser = payload.user;
289
+ this.authExpiresAt = payload.exp * 1000;
290
+ if (this.authTimeout) clearTimeout(this.authTimeout);
261
291
  // Time remaining before token expiration
262
292
  const authExpiration = payload.exp * 1000 - Date.now();
263
293
  // Clear previously created timers
@@ -365,6 +395,7 @@ let SocketClient = exports.SocketClient = /*#__PURE__*/function () {
365
395
  value: async function handleClose(code, reason) {
366
396
  delete SocketClient.socketClients[this.id];
367
397
  this.clearTimeouts();
398
+ if (this.authTimeout) clearTimeout(this.authTimeout);
368
399
  // Stop ping process
369
400
  if (this.pingInterval) clearInterval(this.pingInterval);
370
401
  // Run all the shutdown callbacks
@@ -1 +1 @@
1
- {"version":3,"file":"SocketClient.js","names":["_crypto","require","_parseMessage","_const","_EngineError","_MessageHandler","_ValidationError","_WebError","_jwt","_report","namespace","SocketClient","exports","webSocket","_request","options","_classCallCheck2","default","_defineProperty2","id","randomUUID","ws","establishedAt","Date","reportDebug","message","initCallbacks","Array","authCallbacks","shutdownCallbacks","handlers","handler","MessageHandler","EngineError","socketClients","reportInfo","on","handleClose","bind","catch","error","reportError","severity","LogSeverity","WARNING","data","handleMessage","callbackResults","Promise","allSettled","map","callback","forEach","result","status","reason","pingInterval","setInterval","isAlive","terminate","ping","process","env","WS_PING_INTERVAL","parseInt","_createClass2","key","value","sendMessage","type","payload","send","noAuth","authenticated","audience","includes","DEFAULT_MESSAGE_AUDIENCE","JSON","stringify","isAuthenticated","getUser","userId","deviceId","tokenId","undefined","parseMessage","toString","authenticate","handle","handleError","token","WebError","errorCode","jwtVerify","setUserData","authExpiration","exp","now","clearTimeouts","authRenewTimer","setTimeout","authExpireTimer","clearTimeout","sub","dev","jti","aud","hint","ValidationError","code","clearInterval","getSocketClients","getSocketClient"],"sources":["../../../src/entities/SocketClient/SocketClient.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { IncomingMessage } from 'http';\nimport WS, { RawData } from 'ws';\nimport { parseMessage } from './parseMessage';\nimport {\n SocketClientOptions,\n SocketClientSendMessageOptions,\n SocketAuthenticationMessage,\n SocketClientCallback,\n SocketClientList,\n SocketUser\n} from './SocketClient.types';\nimport { LogSeverity } from 'const';\nimport { EngineError } from 'entities/EngineError';\nimport { MessageHandler } from 'entities/MessageHandler';\nimport { ValidationError } from 'entities/ValidationError';\nimport { WebError } from 'entities/WebError';\nimport { jwtVerify, UserTokenPayload } from 'utils/jwt';\nimport { reportInfo, reportError, reportDebug } from 'utils/report';\n\nconst namespace = 'SocketClient';\n\n/**\n * Class where each instance represents a client connected through web-sockets\n */\nexport class SocketClient {\n /** Mapping of all the connected socket clients */\n private static socketClients: SocketClientList = {};\n /** Unique identifier of the client */\n public readonly id: string;\n /** Date at which the client connection was established */\n public readonly establishedAt: Date;\n /** WS instance of the client connection */\n private ws: WS;\n /** List of message handlers that are registered with the client */\n private handlers: Array<MessageHandler>;\n /** Callback function that are called when the client is created */\n private initCallbacks: Array<SocketClientCallback> = [];\n /** Callback function that are called when the client successfully authenticates */\n private authCallbacks: Array<SocketClientCallback> = [];\n /** Callback function that are called when the client connection is terminated */\n private shutdownCallbacks: Array<SocketClientCallback> = [];\n /** Authentication status of the client */\n private authenticated = false;\n /** Timer used to track when to send authentication renewal reminder to the client */\n private authRenewTimer: NodeJS.Timeout | string | number | undefined;\n /** Timers used to track the time at which a user losses authentication */\n private authExpireTimer: NodeJS.Timeout | string | number | undefined;\n /** ID of the user that is authenticated */\n private userId: undefined | string;\n /** ID of the device with which the client authenticated */\n private deviceId: undefined | string;\n /** Id of the token with which the client authenticated */\n private tokenId: undefined | string;\n /** Audience claims that were in the latest authentication token used by the client */\n private audience: undefined | Array<string>;\n /** Status tracking variable used to check the liveness of connections */\n private isAlive = true;\n /** Timer used to track interval between client pinging */\n private pingInterval: NodeJS.Timeout | string | number | undefined;\n\n /**\n * Create a WS client\n * @param webSocket - The WebSocket instance\n * @param _request - The incoming HTTP request (reserved for future use)\n * @param options - Socket client configuration options\n */\n public constructor(\n webSocket: WS,\n _request: IncomingMessage,\n options: SocketClientOptions\n ) {\n this.id = randomUUID();\n this.ws = webSocket;\n this.establishedAt = new Date();\n\n reportDebug({ namespace, message: `Creating socket client [${this.id}]` });\n\n // Make sure that callbacks are made as lists of functions\n // Init\n if (typeof options.initCallbacks === 'function')\n this.initCallbacks = [options.initCallbacks];\n else if (options.initCallbacks instanceof Array)\n this.initCallbacks = options.initCallbacks;\n // Auth\n if (typeof options.authCallbacks === 'function')\n this.authCallbacks = [options.authCallbacks];\n else if (options.authCallbacks instanceof Array)\n this.authCallbacks = options.authCallbacks;\n // Shutdown\n if (typeof options.shutdownCallbacks === 'function')\n this.shutdownCallbacks = [options.shutdownCallbacks];\n else if (options.shutdownCallbacks instanceof Array)\n this.shutdownCallbacks = options.shutdownCallbacks;\n\n // Check that all handlers are valid\n this.handlers = options.handlers;\n for (const handler of this.handlers) {\n if (!(handler instanceof MessageHandler))\n throw new EngineError({\n message: 'Handler is not an instance of MessageHandler'\n });\n }\n // Register in clients list\n SocketClient.socketClients[this.id] = this;\n\n reportInfo({ message: `Client connected [${this.id}]` });\n // On socket close, deregister from clients list\n this.ws.on('close', () => {\n this.handleClose\n .bind(this)()\n .catch((error) => {\n reportError(error);\n });\n });\n // On socket error, log the error\n this.ws.on('error', (error) => {\n reportError(\n new EngineError({\n severity: LogSeverity.WARNING,\n message: `Error [${this.id}]`\n })\n );\n reportError(error);\n });\n // When receiving a message, call message handler\n this.ws.on('message', (data) => {\n this.handleMessage\n .bind(this)(data)\n .catch((error) => {\n reportError(error);\n });\n });\n\n // Run all the init callbacks\n // For proper error handling of potential asynchronous callbacks, we wrap the execution in an auto-executing async function\n (async (): Promise<void> => {\n const callbackResults = await Promise.allSettled(\n this.initCallbacks.map((callback) => callback(this))\n );\n callbackResults.forEach((result) => {\n if (result.status === 'rejected') reportError(result.reason);\n });\n })().catch((error) => {\n reportError(error);\n });\n\n // Server -> client ping mechanism using WS standard ping control frames\n // We terminate the connection if we did not get a reply in the previous interval\n this.pingInterval = setInterval(\n () => {\n if (!this.isAlive) {\n reportError(\n new EngineError({\n severity: LogSeverity.WARNING,\n message: `Terminating socket connection, ping timeout [${this.id}]`\n })\n );\n this.ws.terminate();\n return;\n }\n this.isAlive = false;\n this.ws.ping();\n reportDebug({ namespace, message: `Ping sent to client [${this.id}]` });\n },\n process.env.WS_PING_INTERVAL\n ? parseInt(process.env.WS_PING_INTERVAL) * 1000\n : 30000\n );\n\n // Track the connection as alive as we get a pong back\n this.ws.on('pong', () => {\n reportDebug({\n namespace,\n message: `Client responded to ping [${this.id}]`\n });\n this.isAlive = true;\n });\n }\n\n /** Get the list of all connected clients indexed by ID */\n public static getSocketClients(): SocketClientList {\n return this.socketClients;\n }\n\n /** Get a client by his ID */\n public static getSocketClient(id: string): SocketClient | undefined {\n return this.socketClients[id];\n }\n\n /**\n * Send a message to the client\n */\n public sendMessage(\n type: string,\n payload: unknown = {},\n options: SocketClientSendMessageOptions = {}\n ): void {\n reportDebug({\n namespace,\n message: `Received request to send message to client of type \"${type}\" [${this.id}]`,\n data: { options }\n });\n let send = false;\n // If the message is sent to unauthenticated user skip auth check\n if (options.noAuth) send = true;\n // If the message is authenticated and with the correct audience, it gets sent\n else if (\n this.authenticated &&\n (this.audience as Array<string>).includes(\n options.audience ?? (process.env.DEFAULT_MESSAGE_AUDIENCE as string)\n )\n )\n send = true;\n\n if (send) {\n reportDebug({\n namespace,\n message: `Sending message to client of type \"${type ?? 'unknown'}\" [${this.id}]`\n });\n this.ws.send(JSON.stringify({ type, payload }));\n }\n }\n\n /** Get the client authentication status */\n public isAuthenticated(): boolean {\n return this.authenticated;\n }\n\n /** Fetch data on the user if he is authenticated */\n public getUser(): SocketUser | undefined {\n if (this.authenticated)\n return {\n userId: this.userId as string,\n deviceId: this.deviceId as string,\n tokenId: this.tokenId as string,\n audience: this.audience as Array<string>\n };\n return undefined;\n }\n\n /** Process an incoming message */\n private async handleMessage(data: RawData): Promise<void> {\n try {\n const message = parseMessage(data.toString('utf-8'));\n reportInfo({\n message: `Received message of type \"${message?.type ?? 'unknown'}\" [${this.id}]`\n });\n switch (message.type) {\n case 'authenticate':\n await this.authenticate(message as SocketAuthenticationMessage);\n break;\n // As not all client libraries allow sending pings through WS control frames,\n // we add a higher level ping mechanism\n case 'ping':\n reportDebug({\n namespace,\n message: `Received ping from client [${this.id}]`\n });\n this.sendMessage('pong', {}, { noAuth: true });\n break;\n default:\n for (const handler of this.handlers) {\n await handler.handle(message, this);\n }\n }\n } catch (error) {\n this.handleError(error);\n }\n }\n\n /**\n * Process an authentication request\n * @param {String} message - Authentication message\n */\n private async authenticate(\n message: SocketAuthenticationMessage\n ): Promise<void> {\n const token = message.payload?.token;\n if (!token) {\n throw new WebError({\n errorCode: 'no-token',\n message: 'No token was given',\n data: { message }\n });\n }\n const payload = await jwtVerify(token);\n this.setUserData(payload);\n\n this.authenticated = true;\n // Time remaining before token expiration\n const authExpiration = payload.exp * 1000 - Date.now();\n // Clear previously created timers\n this.clearTimeouts();\n // Send a refresh request to the client 1 minute before expiration\n this.authRenewTimer = setTimeout(\n () => {\n this.sendMessage('renewAuthentication');\n },\n authExpiration - 60 * 1000\n );\n // Expire client authentication\n this.authExpireTimer = setTimeout(() => {\n this.authenticated = false;\n }, authExpiration);\n\n // Run all the post-authentication callbacks\n const callbackResults = await Promise.allSettled(\n this.authCallbacks.map((callback) => callback(this))\n );\n callbackResults.forEach((result) => {\n if (result.status === 'rejected') reportError(result.reason);\n });\n }\n\n // Clear all the pending timers\n /** */\n private clearTimeouts(): void {\n if (this.authRenewTimer) clearTimeout(this.authRenewTimer);\n if (this.authExpireTimer) clearTimeout(this.authExpireTimer);\n }\n\n /**\n * Fetch the user's data and attach it to the connection for new connections. For existing connections, it checks that the authentication is done on the same user.\n */\n private setUserData({ sub, dev, jti, aud }: UserTokenPayload): void {\n // Check if the user is already authenticated\n if (this.userId) {\n // Check that the token is for the same connection as before\n if (this.userId && this.userId !== sub)\n throw new WebError({\n errorCode: 'wrong-user-token',\n message:\n 'A user tried to authenticate with a token that is not for him [userId]'\n });\n } else {\n // Set the user's ID\n this.userId = sub;\n // Set the user's Device Id\n this.deviceId = dev;\n // Set the token ID\n this.tokenId = jti;\n // Set the aud\n this.audience = aud instanceof Array ? aud : [aud];\n\n reportDebug({\n namespace,\n message: `Successfully authenticated client [${this.id}]`,\n data: {\n userId: this.userId,\n deviceId: this.deviceId,\n tokenId: this.tokenId,\n audience: this.audience\n }\n });\n }\n }\n\n /**\n * Properly return errors to clients\n */\n private handleError(error: unknown): void {\n reportError(error);\n if (error instanceof WebError) {\n this.sendMessage(\n 'error',\n { errorCode: error.errorCode, hint: error.hint },\n { noAuth: true }\n );\n } else if (error instanceof ValidationError) {\n this.sendMessage(\n 'error',\n { errorCode: 'validation-error', hint: error.data },\n { noAuth: true }\n );\n } else {\n this.sendMessage(\n 'error',\n { errorCode: 'server-error' },\n { noAuth: true }\n );\n }\n }\n\n /**\n * Process a socket close event\n */\n private async handleClose(code?: string, reason?: string): Promise<void> {\n delete SocketClient.socketClients[this.id];\n this.clearTimeouts();\n // Stop ping process\n if (this.pingInterval) clearInterval(this.pingInterval);\n // Run all the shutdown callbacks\n const callbackResults = await Promise.allSettled(\n this.shutdownCallbacks.map((callback) => callback(this))\n );\n callbackResults.forEach((result) => {\n if (result.status === 'rejected') reportError(result.reason);\n });\n reportInfo({\n message: `Client disconnected [${this.id}]`,\n data: { code, reason }\n });\n }\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"SocketClient.js","names":["_crypto","require","_parseMessage","_const","_EngineError","_MessageHandler","_ValidationError","_WebError","_jwt","_report","namespace","SocketClient","exports","webSocket","_request","options","_classCallCheck2","default","_defineProperty2","id","randomUUID","ws","establishedAt","Date","reportDebug","message","initCallbacks","Array","authCallbacks","shutdownCallbacks","handlers","handler","MessageHandler","EngineError","socketClients","reportInfo","authTimeoutMs","authTimeout","setTimeout","authenticated","close","on","code","reason","handleClose","bind","toString","catch","error","reportError","severity","LogSeverity","WARNING","data","handleMessage","callbackResults","Promise","allSettled","map","callback","forEach","result","status","pingInterval","setInterval","isAlive","terminate","ping","process","env","WS_PING_INTERVAL","parseInt","_createClass2","key","value","sendMessage","type","payload","send","noAuth","audience","includes","DEFAULT_MESSAGE_AUDIENCE","JSON","stringify","clearTimeouts","clearTimeout","isAuthenticated","getUser","userId","deviceId","tokenId","user","tokenUser","expiresAt","authExpiresAt","undefined","parseMessage","authenticate","handle","handleError","token","WebError","errorCode","jwtVerify","setUserData","exp","authExpiration","now","authRenewTimer","authExpireTimer","sub","dev","jti","aud","hint","ValidationError","clearInterval","getSocketClients","getSocketClient"],"sources":["../../../src/entities/SocketClient/SocketClient.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { IncomingMessage } from 'http';\nimport WS, { RawData } from 'ws';\nimport { parseMessage } from './parseMessage';\nimport {\n SocketClientOptions,\n SocketClientSendMessageOptions,\n SocketAuthenticationMessage,\n SocketClientCallback,\n SocketClientList,\n SocketUser\n} from './SocketClient.types';\nimport { LogSeverity } from 'const';\nimport { EngineError } from 'entities/EngineError';\nimport { MessageHandler } from 'entities/MessageHandler';\nimport { ValidationError } from 'entities/ValidationError';\nimport { WebError } from 'entities/WebError';\nimport { jwtVerify, UserTokenPayload } from 'utils/jwt';\nimport { reportInfo, reportError, reportDebug } from 'utils/report';\n\nconst namespace = 'SocketClient';\n\n/**\n * Class where each instance represents a client connected through web-sockets\n */\nexport class SocketClient {\n /** Mapping of all the connected socket clients */\n private static socketClients: SocketClientList = {};\n /** Unique identifier of the client */\n public readonly id: string;\n /** Date at which the client connection was established */\n public readonly establishedAt: Date;\n /** WS instance of the client connection */\n private ws: WS;\n /** List of message handlers that are registered with the client */\n private handlers: Array<MessageHandler>;\n /** Callback function that are called when the client is created */\n private initCallbacks: Array<SocketClientCallback> = [];\n /** Callback function that are called when the client successfully authenticates */\n private authCallbacks: Array<SocketClientCallback> = [];\n /** Callback function that are called when the client connection is terminated */\n private shutdownCallbacks: Array<SocketClientCallback> = [];\n /** Authentication status of the client */\n private authenticated = false;\n /** Timer used to track when to send authentication renewal reminder to the client */\n private authRenewTimer: NodeJS.Timeout | string | number | undefined;\n /** Timers used to track the time at which a user losses authentication */\n private authExpireTimer: NodeJS.Timeout | string | number | undefined;\n /** ID of the user that is authenticated */\n private userId: undefined | string;\n /** ID of the device with which the client authenticated */\n private deviceId: undefined | string;\n /** Id of the token with which the client authenticated */\n private tokenId: undefined | string;\n /** Audience claims that were in the latest authentication token used by the client */\n private audience: undefined | Array<string>;\n /** Status tracking variable used to check the liveness of connections */\n private isAlive = true;\n /** The `user` claim of the token the client authenticated with */\n private tokenUser: undefined | Record<string, unknown>;\n /** Timestamp (ms) at which the current authentication expires */\n private authExpiresAt: undefined | number;\n /** Closes the connection if the client does not authenticate in time */\n private authTimeout: NodeJS.Timeout | undefined;\n /** Timer used to track interval between client pinging */\n private pingInterval: NodeJS.Timeout | string | number | undefined;\n\n /**\n * Create a WS client\n * @param webSocket - The WebSocket instance\n * @param _request - The incoming HTTP request (reserved for future use)\n * @param options - Socket client configuration options\n */\n public constructor(\n webSocket: WS,\n _request: IncomingMessage,\n options: SocketClientOptions\n ) {\n this.id = randomUUID();\n this.ws = webSocket;\n this.establishedAt = new Date();\n\n reportDebug({ namespace, message: `Creating socket client [${this.id}]` });\n\n // Make sure that callbacks are made as lists of functions\n // Init\n if (typeof options.initCallbacks === 'function')\n this.initCallbacks = [options.initCallbacks];\n else if (options.initCallbacks instanceof Array)\n this.initCallbacks = options.initCallbacks;\n // Auth\n if (typeof options.authCallbacks === 'function')\n this.authCallbacks = [options.authCallbacks];\n else if (options.authCallbacks instanceof Array)\n this.authCallbacks = options.authCallbacks;\n // Shutdown\n if (typeof options.shutdownCallbacks === 'function')\n this.shutdownCallbacks = [options.shutdownCallbacks];\n else if (options.shutdownCallbacks instanceof Array)\n this.shutdownCallbacks = options.shutdownCallbacks;\n\n // Check that all handlers are valid\n this.handlers = options.handlers;\n for (const handler of this.handlers) {\n if (!(handler instanceof MessageHandler))\n throw new EngineError({\n message: 'Handler is not an instance of MessageHandler'\n });\n }\n // Register in clients list\n SocketClient.socketClients[this.id] = this;\n\n reportInfo({ message: `Client connected [${this.id}]` });\n // Optionally close connections that do not authenticate in time\n if (options.authTimeoutMs && options.authTimeoutMs > 0) {\n this.authTimeout = setTimeout(() => {\n if (!this.authenticated) this.close(4401, 'Authentication required');\n }, options.authTimeoutMs);\n }\n // On socket close, deregister from clients list\n this.ws.on('close', (code: number, reason: Buffer) => {\n this.handleClose\n .bind(this)(code, reason?.toString())\n .catch((error) => {\n reportError(error);\n });\n });\n // On socket error, log the error\n this.ws.on('error', (error) => {\n reportError(\n new EngineError({\n severity: LogSeverity.WARNING,\n message: `Error [${this.id}]`\n })\n );\n reportError(error);\n });\n // When receiving a message, call message handler\n this.ws.on('message', (data) => {\n this.handleMessage\n .bind(this)(data)\n .catch((error) => {\n reportError(error);\n });\n });\n\n // Run all the init callbacks\n // For proper error handling of potential asynchronous callbacks, we wrap the execution in an auto-executing async function\n (async (): Promise<void> => {\n const callbackResults = await Promise.allSettled(\n this.initCallbacks.map((callback) => callback(this))\n );\n callbackResults.forEach((result) => {\n if (result.status === 'rejected') reportError(result.reason);\n });\n })().catch((error) => {\n reportError(error);\n });\n\n // Server -> client ping mechanism using WS standard ping control frames\n // We terminate the connection if we did not get a reply in the previous interval\n this.pingInterval = setInterval(\n () => {\n if (!this.isAlive) {\n reportError(\n new EngineError({\n severity: LogSeverity.WARNING,\n message: `Terminating socket connection, ping timeout [${this.id}]`\n })\n );\n this.ws.terminate();\n return;\n }\n this.isAlive = false;\n this.ws.ping();\n reportDebug({ namespace, message: `Ping sent to client [${this.id}]` });\n },\n process.env.WS_PING_INTERVAL\n ? parseInt(process.env.WS_PING_INTERVAL) * 1000\n : 30000\n );\n\n // Track the connection as alive as we get a pong back\n this.ws.on('pong', () => {\n reportDebug({\n namespace,\n message: `Client responded to ping [${this.id}]`\n });\n this.isAlive = true;\n });\n }\n\n /** Get the list of all connected clients indexed by ID */\n public static getSocketClients(): SocketClientList {\n return this.socketClients;\n }\n\n /** Get a client by his ID */\n public static getSocketClient(id: string): SocketClient | undefined {\n return this.socketClients[id];\n }\n\n /**\n * Send a message to the client\n */\n public sendMessage(\n type: string,\n payload: unknown = {},\n options: SocketClientSendMessageOptions = {}\n ): void {\n reportDebug({\n namespace,\n message: `Received request to send message to client of type \"${type}\" [${this.id}]`,\n data: { options }\n });\n let send = false;\n // If the message is sent to unauthenticated user skip auth check\n if (options.noAuth) send = true;\n // If the message is authenticated and with the correct audience, it gets sent\n else if (\n this.authenticated &&\n (this.audience as Array<string>).includes(\n options.audience ?? (process.env.DEFAULT_MESSAGE_AUDIENCE as string)\n )\n )\n send = true;\n\n if (send) {\n reportDebug({\n namespace,\n message: `Sending message to client of type \"${type ?? 'unknown'}\" [${this.id}]`\n });\n this.ws.send(JSON.stringify({ type, payload }));\n }\n }\n\n /**\n * Close the connection\n * @param code - WebSocket close code (4000-4999 are free for applications, e.g. 4401 / 4403)\n * @param reason - Human readable reason, sent to the client\n */\n public close(code = 1000, reason?: string): void {\n this.clearTimeouts();\n if (this.authTimeout) clearTimeout(this.authTimeout);\n this.ws.close(code, reason);\n }\n\n /** Get the client authentication status */\n public isAuthenticated(): boolean {\n return this.authenticated;\n }\n\n /** Fetch data on the user if he is authenticated */\n public getUser(): SocketUser | undefined {\n if (this.authenticated)\n return {\n userId: this.userId as string,\n deviceId: this.deviceId as string,\n tokenId: this.tokenId as string,\n audience: this.audience as Array<string>,\n user: this.tokenUser,\n expiresAt: this.authExpiresAt\n };\n return undefined;\n }\n\n /** Process an incoming message */\n private async handleMessage(data: RawData): Promise<void> {\n try {\n const message = parseMessage(data.toString('utf-8'));\n reportInfo({\n message: `Received message of type \"${message?.type ?? 'unknown'}\" [${this.id}]`\n });\n switch (message.type) {\n case 'authenticate':\n await this.authenticate(message as SocketAuthenticationMessage);\n break;\n // As not all client libraries allow sending pings through WS control frames,\n // we add a higher level ping mechanism\n case 'ping':\n reportDebug({\n namespace,\n message: `Received ping from client [${this.id}]`\n });\n this.sendMessage('pong', {}, { noAuth: true });\n break;\n default:\n for (const handler of this.handlers) {\n await handler.handle(message, this);\n }\n }\n } catch (error) {\n this.handleError(error);\n }\n }\n\n /**\n * Process an authentication request\n * @param {String} message - Authentication message\n */\n private async authenticate(\n message: SocketAuthenticationMessage\n ): Promise<void> {\n const token = message.payload?.token;\n if (!token) {\n throw new WebError({\n errorCode: 'no-token',\n message: 'No token was given',\n data: { message }\n });\n }\n const payload = await jwtVerify(token);\n this.setUserData(payload);\n\n this.authenticated = true;\n this.tokenUser = payload.user;\n this.authExpiresAt = payload.exp * 1000;\n if (this.authTimeout) clearTimeout(this.authTimeout);\n // Time remaining before token expiration\n const authExpiration = payload.exp * 1000 - Date.now();\n // Clear previously created timers\n this.clearTimeouts();\n // Send a refresh request to the client 1 minute before expiration\n this.authRenewTimer = setTimeout(\n () => {\n this.sendMessage('renewAuthentication');\n },\n authExpiration - 60 * 1000\n );\n // Expire client authentication\n this.authExpireTimer = setTimeout(() => {\n this.authenticated = false;\n }, authExpiration);\n\n // Run all the post-authentication callbacks\n const callbackResults = await Promise.allSettled(\n this.authCallbacks.map((callback) => callback(this))\n );\n callbackResults.forEach((result) => {\n if (result.status === 'rejected') reportError(result.reason);\n });\n }\n\n // Clear all the pending timers\n /** */\n private clearTimeouts(): void {\n if (this.authRenewTimer) clearTimeout(this.authRenewTimer);\n if (this.authExpireTimer) clearTimeout(this.authExpireTimer);\n }\n\n /**\n * Fetch the user's data and attach it to the connection for new connections. For existing connections, it checks that the authentication is done on the same user.\n */\n private setUserData({ sub, dev, jti, aud }: UserTokenPayload): void {\n // Check if the user is already authenticated\n if (this.userId) {\n // Check that the token is for the same connection as before\n if (this.userId && this.userId !== sub)\n throw new WebError({\n errorCode: 'wrong-user-token',\n message:\n 'A user tried to authenticate with a token that is not for him [userId]'\n });\n } else {\n // Set the user's ID\n this.userId = sub;\n // Set the user's Device Id\n this.deviceId = dev;\n // Set the token ID\n this.tokenId = jti;\n // Set the aud\n this.audience = aud instanceof Array ? aud : [aud];\n\n reportDebug({\n namespace,\n message: `Successfully authenticated client [${this.id}]`,\n data: {\n userId: this.userId,\n deviceId: this.deviceId,\n tokenId: this.tokenId,\n audience: this.audience\n }\n });\n }\n }\n\n /**\n * Properly return errors to clients\n */\n private handleError(error: unknown): void {\n reportError(error);\n if (error instanceof WebError) {\n this.sendMessage(\n 'error',\n { errorCode: error.errorCode, hint: error.hint },\n { noAuth: true }\n );\n } else if (error instanceof ValidationError) {\n this.sendMessage(\n 'error',\n { errorCode: 'validation-error', hint: error.data },\n { noAuth: true }\n );\n } else {\n this.sendMessage(\n 'error',\n { errorCode: 'server-error' },\n { noAuth: true }\n );\n }\n }\n\n /**\n * Process a socket close event\n */\n private async handleClose(code?: number, reason?: string): Promise<void> {\n delete SocketClient.socketClients[this.id];\n this.clearTimeouts();\n if (this.authTimeout) clearTimeout(this.authTimeout);\n // Stop ping process\n if (this.pingInterval) clearInterval(this.pingInterval);\n // Run all the shutdown callbacks\n const callbackResults = await Promise.allSettled(\n this.shutdownCallbacks.map((callback) => callback(this))\n );\n callbackResults.forEach((result) => {\n if (result.status === 'rejected') reportError(result.reason);\n });\n reportInfo({\n message: `Client disconnected [${this.id}]`,\n data: { code, reason }\n });\n }\n}\n"],"mappings":"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","ignoreList":[]}
@@ -11,6 +11,8 @@ export interface SocketClientOptions {
11
11
  authCallbacks?: SocketClientCallback | Array<SocketClientCallback>;
12
12
  /** Callback function that are called when the client connection is terminated */
13
13
  shutdownCallbacks?: SocketClientCallback | Array<SocketClientCallback>;
14
+ /** Close the connection with code 4401 if the client has not authenticated after this many ms (default: never) */
15
+ authTimeoutMs?: number;
14
16
  }
15
17
  /** Socket Message abstract typing */
16
18
  export interface SocketMessage<T = unknown> {
@@ -50,4 +52,8 @@ export interface SocketUser {
50
52
  tokenId: string;
51
53
  /** Audience included in the token */
52
54
  audience: Array<string>;
55
+ /** The `user` claim of the authentication token (application data such as role or organisation) */
56
+ user?: Record<string, unknown>;
57
+ /** Timestamp (ms) at which the authentication expires */
58
+ expiresAt?: number;
53
59
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SocketClient.types.js","names":[],"sources":["../../../src/entities/SocketClient/SocketClient.types.ts"],"sourcesContent":["import { SocketClient } from './SocketClient';\nimport { MessageHandler } from 'entities/MessageHandler';\n\nexport type SocketClientCallback = (\n client: SocketClient\n) => void | Promise<void>;\n\n/** SocketClient creation option */\nexport interface SocketClientOptions {\n /** List of message handlers that are registered with the client */\n handlers: Array<MessageHandler>;\n /** Callback function that are called when the client is created */\n initCallbacks?: SocketClientCallback | Array<SocketClientCallback>;\n /** Callback function that are called when the client successfully authenticates */\n authCallbacks?: SocketClientCallback | Array<SocketClientCallback>;\n /** Callback function that are called when the client connection is terminated */\n shutdownCallbacks?: SocketClientCallback | Array<SocketClientCallback>;\n}\n\n/** Socket Message abstract typing */\nexport interface SocketMessage<T = unknown> {\n /** A string that identifies the message type */\n type: string;\n /** Free-form data payload of the message */\n payload: T;\n}\n\n/** SocketMessage used by client for authentication */\nexport interface SocketAuthenticationMessage extends SocketMessage {\n /** Sting that identifies the message type */\n type: 'authenticate';\n /** Data payload of the message */\n payload: {\n /** Authentication token */\n token: string;\n };\n}\n\n/** Options for sending a message with the client */\nexport interface SocketClientSendMessageOptions {\n /** Send message event if the user is not authenticated */\n noAuth?: boolean;\n /** Audience that the authenticated user must have on his token to continue */\n audience?: string;\n}\n\n/** Map of socket clients connected to the server */\nexport interface SocketClientList {\n [id: string]: SocketClient;\n}\n\n/** User that authenticated though his socket */\nexport interface SocketUser {\n /** ID od the user */\n userId: string;\n /** ID of the device */\n deviceId: string;\n /** ID of the token used */\n tokenId: string;\n /** Audience included in the token */\n audience: Array<string>;\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"SocketClient.types.js","names":[],"sources":["../../../src/entities/SocketClient/SocketClient.types.ts"],"sourcesContent":["import { SocketClient } from './SocketClient';\nimport { MessageHandler } from 'entities/MessageHandler';\n\nexport type SocketClientCallback = (\n client: SocketClient\n) => void | Promise<void>;\n\n/** SocketClient creation option */\nexport interface SocketClientOptions {\n /** List of message handlers that are registered with the client */\n handlers: Array<MessageHandler>;\n /** Callback function that are called when the client is created */\n initCallbacks?: SocketClientCallback | Array<SocketClientCallback>;\n /** Callback function that are called when the client successfully authenticates */\n authCallbacks?: SocketClientCallback | Array<SocketClientCallback>;\n /** Callback function that are called when the client connection is terminated */\n shutdownCallbacks?: SocketClientCallback | Array<SocketClientCallback>;\n /** Close the connection with code 4401 if the client has not authenticated after this many ms (default: never) */\n authTimeoutMs?: number;\n}\n\n/** Socket Message abstract typing */\nexport interface SocketMessage<T = unknown> {\n /** A string that identifies the message type */\n type: string;\n /** Free-form data payload of the message */\n payload: T;\n}\n\n/** SocketMessage used by client for authentication */\nexport interface SocketAuthenticationMessage extends SocketMessage {\n /** Sting that identifies the message type */\n type: 'authenticate';\n /** Data payload of the message */\n payload: {\n /** Authentication token */\n token: string;\n };\n}\n\n/** Options for sending a message with the client */\nexport interface SocketClientSendMessageOptions {\n /** Send message event if the user is not authenticated */\n noAuth?: boolean;\n /** Audience that the authenticated user must have on his token to continue */\n audience?: string;\n}\n\n/** Map of socket clients connected to the server */\nexport interface SocketClientList {\n [id: string]: SocketClient;\n}\n\n/** User that authenticated though his socket */\nexport interface SocketUser {\n /** ID od the user */\n userId: string;\n /** ID of the device */\n deviceId: string;\n /** ID of the token used */\n tokenId: string;\n /** Audience included in the token */\n audience: Array<string>;\n /** The `user` claim of the authentication token (application data such as role or organisation) */\n user?: Record<string, unknown>;\n /** Timestamp (ms) at which the authentication expires */\n expiresAt?: number;\n}\n"],"mappings":"","ignoreList":[]}
@@ -8,6 +8,10 @@ export declare class WebError extends EngineError {
8
8
  errorCode: string;
9
9
  /** HTTP status code associated with this error */
10
10
  statusCode: number;
11
+ /** Public structured data sent to the client as `details` */
12
+ details?: unknown;
13
+ /** Seconds before the client may retry (Retry-After header) */
14
+ retryAfterSec?: number;
11
15
  /**
12
16
  * Create a WebError, these errors are best suited for runtime errors
13
17
  */
@@ -33,8 +33,14 @@ let WebError = exports.WebError = /*#__PURE__*/function (_EngineError) {
33
33
  (0, _defineProperty2.default)(_this, "errorCode", void 0);
34
34
  /** HTTP status code associated with this error */
35
35
  (0, _defineProperty2.default)(_this, "statusCode", void 0);
36
+ /** Public structured data sent to the client as `details` */
37
+ (0, _defineProperty2.default)(_this, "details", void 0);
38
+ /** Seconds before the client may retry (Retry-After header) */
39
+ (0, _defineProperty2.default)(_this, "retryAfterSec", void 0);
36
40
  _this.statusCode = config.statusCode ?? 400;
37
41
  _this.errorCode = config.errorCode;
42
+ if (config.details !== undefined) _this.details = config.details;
43
+ if (config.retryAfterSec !== undefined) _this.retryAfterSec = config.retryAfterSec;
38
44
  return _this;
39
45
  }
40
46
  (0, _inherits2.default)(WebError, _EngineError);
@@ -1 +1 @@
1
- {"version":3,"file":"WebError.js","names":["_const","require","_EngineError2","_callSuper","t","o","e","_getPrototypeOf2","default","_possibleConstructorReturn2","_isNativeReflectConstruct","Reflect","construct","constructor","apply","Boolean","prototype","valueOf","call","WebError","exports","_EngineError","config","_this","_classCallCheck2","severity","LogSeverity","WARNING","_defineProperty2","statusCode","errorCode","_inherits2","_createClass2","EngineError"],"sources":["../../../src/entities/WebError/WebError.ts"],"sourcesContent":["import { WebErrorOptions } from './WebError.types';\nimport { LogSeverity } from 'const';\nimport { EngineError } from 'entities/EngineError';\n\n/**\n * Error class used when errors should be reported to the client\n */\nexport class WebError extends EngineError {\n /** Machine readable error code that will be sent to the client */\n public errorCode: string;\n /** HTTP status code associated with this error */\n public statusCode: number;\n\n /**\n * Create a WebError, these errors are best suited for runtime errors\n */\n public constructor(config: WebErrorOptions) {\n super({ ...config, severity: config.severity ?? LogSeverity.WARNING });\n\n this.statusCode = config.statusCode ?? 400;\n this.errorCode = config.errorCode;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AACA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,aAAA,GAAAD,OAAA;AAAmD,SAAAE,WAAAC,CAAA,EAAAC,CAAA,EAAAC,CAAA,WAAAD,CAAA,OAAAE,gBAAA,CAAAC,OAAA,EAAAH,CAAA,OAAAI,2BAAA,CAAAD,OAAA,EAAAJ,CAAA,EAAAM,yBAAA,KAAAC,OAAA,CAAAC,SAAA,CAAAP,CAAA,EAAAC,CAAA,YAAAC,gBAAA,CAAAC,OAAA,EAAAJ,CAAA,EAAAS,WAAA,IAAAR,CAAA,CAAAS,KAAA,CAAAV,CAAA,EAAAE,CAAA;AAAA,SAAAI,0BAAA,cAAAN,CAAA,IAAAW,OAAA,CAAAC,SAAA,CAAAC,OAAA,CAAAC,IAAA,CAAAP,OAAA,CAAAC,SAAA,CAAAG,OAAA,iCAAAX,CAAA,aAAAM,yBAAA,YAAAA,CAAA,aAAAN,CAAA;AAEnD;AACA;AACA;AAFA,IAGae,QAAQ,GAAAC,OAAA,CAAAD,QAAA,0BAAAE,YAAA;EAMnB;AACF;AACA;EACE,SAAAF,SAAmBG,MAAuB,EAAE;IAAA,IAAAC,KAAA;IAAA,IAAAC,gBAAA,CAAAhB,OAAA,QAAAW,QAAA;IAC1CI,KAAA,GAAApB,UAAA,OAAAgB,QAAA,GAAM;MAAE,GAAGG,MAAM;MAAEG,QAAQ,EAAEH,MAAM,CAACG,QAAQ,IAAIC,kBAAW,CAACC;IAAQ,CAAC;IATvE;IAAA,IAAAC,gBAAA,CAAApB,OAAA,EAAAe,KAAA;IAEA;IAAA,IAAAK,gBAAA,CAAApB,OAAA,EAAAe,KAAA;IASEA,KAAA,CAAKM,UAAU,GAAGP,MAAM,CAACO,UAAU,IAAI,GAAG;IAC1CN,KAAA,CAAKO,SAAS,GAAGR,MAAM,CAACQ,SAAS;IAAC,OAAAP,KAAA;EACpC;EAAC,IAAAQ,UAAA,CAAAvB,OAAA,EAAAW,QAAA,EAAAE,YAAA;EAAA,WAAAW,aAAA,CAAAxB,OAAA,EAAAW,QAAA;AAAA,EAd2Bc,yBAAW","ignoreList":[]}
1
+ {"version":3,"file":"WebError.js","names":["_const","require","_EngineError2","_callSuper","t","o","e","_getPrototypeOf2","default","_possibleConstructorReturn2","_isNativeReflectConstruct","Reflect","construct","constructor","apply","Boolean","prototype","valueOf","call","WebError","exports","_EngineError","config","_this","_classCallCheck2","severity","LogSeverity","WARNING","_defineProperty2","statusCode","errorCode","details","undefined","retryAfterSec","_inherits2","_createClass2","EngineError"],"sources":["../../../src/entities/WebError/WebError.ts"],"sourcesContent":["import { WebErrorOptions } from './WebError.types';\nimport { LogSeverity } from 'const';\nimport { EngineError } from 'entities/EngineError';\n\n/**\n * Error class used when errors should be reported to the client\n */\nexport class WebError extends EngineError {\n /** Machine readable error code that will be sent to the client */\n public errorCode: string;\n /** HTTP status code associated with this error */\n public statusCode: number;\n /** Public structured data sent to the client as `details` */\n public details?: unknown;\n /** Seconds before the client may retry (Retry-After header) */\n public retryAfterSec?: number;\n\n /**\n * Create a WebError, these errors are best suited for runtime errors\n */\n public constructor(config: WebErrorOptions) {\n super({ ...config, severity: config.severity ?? LogSeverity.WARNING });\n\n this.statusCode = config.statusCode ?? 400;\n this.errorCode = config.errorCode;\n if (config.details !== undefined) this.details = config.details;\n if (config.retryAfterSec !== undefined)\n this.retryAfterSec = config.retryAfterSec;\n }\n}\n"],"mappings":";;;;;;;;;;;;;AACA,IAAAA,MAAA,GAAAC,OAAA;AACA,IAAAC,aAAA,GAAAD,OAAA;AAAmD,SAAAE,WAAAC,CAAA,EAAAC,CAAA,EAAAC,CAAA,WAAAD,CAAA,OAAAE,gBAAA,CAAAC,OAAA,EAAAH,CAAA,OAAAI,2BAAA,CAAAD,OAAA,EAAAJ,CAAA,EAAAM,yBAAA,KAAAC,OAAA,CAAAC,SAAA,CAAAP,CAAA,EAAAC,CAAA,YAAAC,gBAAA,CAAAC,OAAA,EAAAJ,CAAA,EAAAS,WAAA,IAAAR,CAAA,CAAAS,KAAA,CAAAV,CAAA,EAAAE,CAAA;AAAA,SAAAI,0BAAA,cAAAN,CAAA,IAAAW,OAAA,CAAAC,SAAA,CAAAC,OAAA,CAAAC,IAAA,CAAAP,OAAA,CAAAC,SAAA,CAAAG,OAAA,iCAAAX,CAAA,aAAAM,yBAAA,YAAAA,CAAA,aAAAN,CAAA;AAEnD;AACA;AACA;AAFA,IAGae,QAAQ,GAAAC,OAAA,CAAAD,QAAA,0BAAAE,YAAA;EAUnB;AACF;AACA;EACE,SAAAF,SAAmBG,MAAuB,EAAE;IAAA,IAAAC,KAAA;IAAA,IAAAC,gBAAA,CAAAhB,OAAA,QAAAW,QAAA;IAC1CI,KAAA,GAAApB,UAAA,OAAAgB,QAAA,GAAM;MAAE,GAAGG,MAAM;MAAEG,QAAQ,EAAEH,MAAM,CAACG,QAAQ,IAAIC,kBAAW,CAACC;IAAQ,CAAC;IAbvE;IAAA,IAAAC,gBAAA,CAAApB,OAAA,EAAAe,KAAA;IAEA;IAAA,IAAAK,gBAAA,CAAApB,OAAA,EAAAe,KAAA;IAEA;IAAA,IAAAK,gBAAA,CAAApB,OAAA,EAAAe,KAAA;IAEA;IAAA,IAAAK,gBAAA,CAAApB,OAAA,EAAAe,KAAA;IASEA,KAAA,CAAKM,UAAU,GAAGP,MAAM,CAACO,UAAU,IAAI,GAAG;IAC1CN,KAAA,CAAKO,SAAS,GAAGR,MAAM,CAACQ,SAAS;IACjC,IAAIR,MAAM,CAACS,OAAO,KAAKC,SAAS,EAAET,KAAA,CAAKQ,OAAO,GAAGT,MAAM,CAACS,OAAO;IAC/D,IAAIT,MAAM,CAACW,aAAa,KAAKD,SAAS,EACpCT,KAAA,CAAKU,aAAa,GAAGX,MAAM,CAACW,aAAa;IAAC,OAAAV,KAAA;EAC9C;EAAC,IAAAW,UAAA,CAAA1B,OAAA,EAAAW,QAAA,EAAAE,YAAA;EAAA,WAAAc,aAAA,CAAA3B,OAAA,EAAAW,QAAA;AAAA,EArB2BiB,yBAAW","ignoreList":[]}
@@ -5,4 +5,11 @@ export interface WebErrorOptions extends EngineErrorOptions {
5
5
  errorCode: string;
6
6
  /** HTTP status code associated with this error (default: 400) */
7
7
  statusCode?: number;
8
+ /**
9
+ * Structured data the client acts on (e.g. attempts left, per-field errors), sent in the response body as
10
+ * `details`. Unlike `data`, which is only logged, this is public: never put secrets or internals here.
11
+ */
12
+ details?: unknown;
13
+ /** Seconds the client should wait before retrying, sent as the Retry-After header (429, 503) */
14
+ retryAfterSec?: number;
8
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"WebError.types.js","names":[],"sources":["../../../src/entities/WebError/WebError.types.ts"],"sourcesContent":["import { EngineErrorOptions } from 'entities/EngineError';\n\n/** Options to create a Web Error */\nexport interface WebErrorOptions extends EngineErrorOptions {\n /** Machine readable error code that will be sent to the client */\n errorCode: string;\n /** HTTP status code associated with this error (default: 400) */\n statusCode?: number;\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"file":"WebError.types.js","names":[],"sources":["../../../src/entities/WebError/WebError.types.ts"],"sourcesContent":["import { EngineErrorOptions } from 'entities/EngineError';\n\n/** Options to create a Web Error */\nexport interface WebErrorOptions extends EngineErrorOptions {\n /** Machine readable error code that will be sent to the client */\n errorCode: string;\n /** HTTP status code associated with this error (default: 400) */\n statusCode?: number;\n /**\n * Structured data the client acts on (e.g. attempts left, per-field errors), sent in the response body as\n * `details`. Unlike `data`, which is only logged, this is public: never put secrets or internals here.\n */\n details?: unknown;\n /** Seconds the client should wait before retrying, sent as the Retry-After header (429, 503) */\n retryAfterSec?: number;\n}\n"],"mappings":"","ignoreList":[]}
@@ -10,9 +10,13 @@ var _report = require("../../utils/report");
10
10
  function error(error, request, response, _next) {
11
11
  (0, _report.reportError)(error, request);
12
12
  if (error instanceof _WebError.WebError) {
13
+ if (error.retryAfterSec !== undefined) response.set('Retry-After', String(Math.max(1, Math.round(error.retryAfterSec))));
13
14
  response.status(error.statusCode).json({
14
15
  errorCode: error.errorCode,
15
- hint: error.hint
16
+ hint: error.hint,
17
+ ...(error.details !== undefined ? {
18
+ details: error.details
19
+ } : {})
16
20
  });
17
21
  return;
18
22
  }
@@ -1 +1 @@
1
- {"version":3,"file":"error.js","names":["_WebError","require","_report","error","request","response","_next","reportError","WebError","status","statusCode","json","errorCode","hint"],"sources":["../../../src/middleware/error/error.ts"],"sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport { WebError } from 'entities/WebError';\nimport { reportError } from 'utils/report';\n\n/** Handle errors globally */\nexport function error(\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction\n): void {\n reportError(error, request);\n if (error instanceof WebError) {\n response.status(error.statusCode).json({\n errorCode: error.errorCode,\n hint: error.hint\n });\n return;\n }\n response.status(500).json({ errorCode: 'server-error' });\n}\n"],"mappings":";;;;;;AACA,IAAAA,SAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AAEA;AACO,SAASE,KAAKA,CACnBA,KAAY,EACZC,OAAgB,EAChBC,QAAkB,EAClBC,KAAmB,EACb;EACN,IAAAC,mBAAW,EAACJ,KAAK,EAAEC,OAAO,CAAC;EAC3B,IAAID,KAAK,YAAYK,kBAAQ,EAAE;IAC7BH,QAAQ,CAACI,MAAM,CAACN,KAAK,CAACO,UAAU,CAAC,CAACC,IAAI,CAAC;MACrCC,SAAS,EAAET,KAAK,CAACS,SAAS;MAC1BC,IAAI,EAAEV,KAAK,CAACU;IACd,CAAC,CAAC;IACF;EACF;EACAR,QAAQ,CAACI,MAAM,CAAC,GAAG,CAAC,CAACE,IAAI,CAAC;IAAEC,SAAS,EAAE;EAAe,CAAC,CAAC;AAC1D","ignoreList":[]}
1
+ {"version":3,"file":"error.js","names":["_WebError","require","_report","error","request","response","_next","reportError","WebError","retryAfterSec","undefined","set","String","Math","max","round","status","statusCode","json","errorCode","hint","details"],"sources":["../../../src/middleware/error/error.ts"],"sourcesContent":["import { Request, Response, NextFunction } from 'express';\nimport { WebError } from 'entities/WebError';\nimport { reportError } from 'utils/report';\n\n/** Handle errors globally */\nexport function error(\n error: Error,\n request: Request,\n response: Response,\n _next: NextFunction\n): void {\n reportError(error, request);\n if (error instanceof WebError) {\n if (error.retryAfterSec !== undefined)\n response.set(\n 'Retry-After',\n String(Math.max(1, Math.round(error.retryAfterSec)))\n );\n response.status(error.statusCode).json({\n errorCode: error.errorCode,\n hint: error.hint,\n ...(error.details !== undefined ? { details: error.details } : {})\n });\n return;\n }\n response.status(500).json({ errorCode: 'server-error' });\n}\n"],"mappings":";;;;;;AACA,IAAAA,SAAA,GAAAC,OAAA;AACA,IAAAC,OAAA,GAAAD,OAAA;AAEA;AACO,SAASE,KAAKA,CACnBA,KAAY,EACZC,OAAgB,EAChBC,QAAkB,EAClBC,KAAmB,EACb;EACN,IAAAC,mBAAW,EAACJ,KAAK,EAAEC,OAAO,CAAC;EAC3B,IAAID,KAAK,YAAYK,kBAAQ,EAAE;IAC7B,IAAIL,KAAK,CAACM,aAAa,KAAKC,SAAS,EACnCL,QAAQ,CAACM,GAAG,CACV,aAAa,EACbC,MAAM,CAACC,IAAI,CAACC,GAAG,CAAC,CAAC,EAAED,IAAI,CAACE,KAAK,CAACZ,KAAK,CAACM,aAAa,CAAC,CAAC,CACrD,CAAC;IACHJ,QAAQ,CAACW,MAAM,CAACb,KAAK,CAACc,UAAU,CAAC,CAACC,IAAI,CAAC;MACrCC,SAAS,EAAEhB,KAAK,CAACgB,SAAS;MAC1BC,IAAI,EAAEjB,KAAK,CAACiB,IAAI;MAChB,IAAIjB,KAAK,CAACkB,OAAO,KAAKX,SAAS,GAAG;QAAEW,OAAO,EAAElB,KAAK,CAACkB;MAAQ,CAAC,GAAG,CAAC,CAAC;IACnE,CAAC,CAAC;IACF;EACF;EACAhB,QAAQ,CAACW,MAAM,CAAC,GAAG,CAAC,CAACE,IAAI,CAAC;IAAEC,SAAS,EAAE;EAAe,CAAC,CAAC;AAC1D","ignoreList":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-server-engine",
3
- "version": "2.0.2",
3
+ "version": "2.2.0",
4
4
  "description": "Framework used to develop backend services. This package ships with a lot of features to standardize the creation of services, letting you focus on the business logic.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {