@cap-js-community/websocket
Advanced tools
Comparing version 0.8.1 to 0.9.0
@@ -8,2 +8,10 @@ # Changelog | ||
## Version 0.9.0 - 2024-04-02 | ||
### Fixed | ||
- Add option to activate Redis adapter in other (non-local) environments (e.g. Kyma) | ||
- Fix Redis re-connect behavior to prevent Redis overload | ||
- Pass adapter configurations to Redis client creation | ||
## Version 0.8.1 - 2024-03-04 | ||
@@ -10,0 +18,0 @@ |
{ | ||
"name": "@cap-js-community/websocket", | ||
"version": "0.8.1", | ||
"version": "0.9.0", | ||
"description": "WebSocket adapter for CDS", | ||
@@ -44,7 +44,7 @@ "homepage": "https://cap.cloud.sap/", | ||
"dependencies": { | ||
"@sap/xsenv": "^4.2.0", | ||
"@sap/xsenv": "^5.1.0", | ||
"cookie": "^0.6.0", | ||
"express": "^4.18.3", | ||
"express": "^4.19.2", | ||
"redis": "^4.6.13", | ||
"socket.io": "^4.7.4", | ||
"socket.io": "^4.7.5", | ||
"ws": "^8.15.1" | ||
@@ -54,8 +54,8 @@ }, | ||
"@cap-js-community/websocket": "./", | ||
"@cap-js/sqlite": "^1.5.1", | ||
"@sap/cds": "^7.7.0", | ||
"@sap/cds-dk": "^7.7.0", | ||
"@cap-js/sqlite": "^1.6.0", | ||
"@sap/cds": "^7.8.0", | ||
"@sap/cds-dk": "^7.8.0", | ||
"@sap/xssec": "3.6.1", | ||
"@socket.io/redis-adapter": "^8.2.1", | ||
"@socket.io/redis-streams-adapter": "^0.2.0", | ||
"@socket.io/redis-adapter": "^8.3.0", | ||
"@socket.io/redis-streams-adapter": "^0.2.1", | ||
"@types/express": "^4.17.21", | ||
@@ -68,3 +68,3 @@ "eslint": "^8.57.0", | ||
"prettier": "^3.2.4", | ||
"socket.io-client": "^4.7.4" | ||
"socket.io-client": "^4.7.5" | ||
}, | ||
@@ -117,8 +117,18 @@ "license": "Apache-2.0", | ||
"type": "object", | ||
"description": "Websocket adapter implementation configuration options", | ||
"description": "Websocket adapter implementation options", | ||
"additionalProperties": true | ||
}, | ||
"config": { | ||
"type": "object", | ||
"description": "Websocket adapter implementation configurations (i.e. Redis client options)", | ||
"additionalProperties": true | ||
}, | ||
"active": { | ||
"type": "boolean", | ||
"description": "Enable websocket adapter", | ||
"default": true | ||
}, | ||
"local": { | ||
"type": "boolean", | ||
"description": "Enabled websocket adapter in local environment", | ||
"description": "Enable websocket adapter in local environment", | ||
"default": false | ||
@@ -125,0 +135,0 @@ } |
@@ -649,8 +649,12 @@ # @cap-js-community/websocket | ||
- Application needs to be bound to a Redis instance | ||
- Cloud Foundry: Redis automatically active | ||
- Local (or other): | ||
- Option `cds.websocket.adapter.local: true` needs to be set | ||
- Cloud Foundry: Redis is automatically active | ||
- Use option `cds.websocket.adapter.active: false` to disable Redis adapter | ||
- Other Environment (e.g. Kyma): Redis is NOT automatically active | ||
- Use option `cds.websocket.adapter.active: true` to enable Redis adapter | ||
- Local: Redis is NOT automatically active | ||
- Use option `cds.websocket.adapter.local: true` to enable Redis adapter | ||
- File `default-env.json` need to exist with Redis configuration | ||
- Redis Adapter options can be specified via `cds.websocket.adapter.options` | ||
- Redis channel key can be specified via `cds.websocket.adapter.options.key`. Default value is `websocket`. | ||
- Redis client connection configuration can be passed via `cds.websocket.adapter.config` | ||
@@ -657,0 +661,0 @@ ###### Custom Adapter |
@@ -9,10 +9,11 @@ "use strict"; | ||
class RedisAdapter { | ||
constructor(server, prefix, options) { | ||
constructor(server, prefix, options, config) { | ||
this.server = server; | ||
this.prefix = prefix; | ||
this.options = options; | ||
this.config = config; | ||
} | ||
async setup() { | ||
this.client = await redis.createPrimaryClientAndConnect(); | ||
this.client = await redis.createPrimaryClientAndConnect(this.config); | ||
} | ||
@@ -19,0 +20,0 @@ |
@@ -13,37 +13,43 @@ "use strict"; | ||
const IS_ON_CF = process.env.USER === "vcap"; | ||
const TIMEOUT = 5 * 1000; | ||
const LOG_AFTER_SEC = 5; | ||
let primaryClientPromise; | ||
let secondaryClientPromise; | ||
let lastErrorLog = Date.now(); | ||
const createPrimaryClientAndConnect = () => { | ||
const createPrimaryClientAndConnect = (options) => { | ||
if (primaryClientPromise) { | ||
return primaryClientPromise; | ||
} | ||
const errorHandlerCreateClient = (err) => { | ||
LOG?.error("Error from redis client for pub/sub failed", err); | ||
primaryClientPromise = null; | ||
setTimeout(createPrimaryClientAndConnect, TIMEOUT).unref(); | ||
setTimeout(createPrimaryClientAndConnect, LOG_AFTER_SEC * 1000).unref(); | ||
}; | ||
primaryClientPromise = _createClientAndConnect(errorHandlerCreateClient); | ||
primaryClientPromise = createClientAndConnect(errorHandlerCreateClient, options); | ||
return primaryClientPromise; | ||
}; | ||
const createSecondaryClientAndConnect = () => { | ||
const createSecondaryClientAndConnect = (options) => { | ||
if (secondaryClientPromise) { | ||
return secondaryClientPromise; | ||
} | ||
const errorHandlerCreateClient = (err) => { | ||
LOG?.error("Error from redis client for pub/sub failed", err); | ||
secondaryClientPromise = null; | ||
setTimeout(createSecondaryClientAndConnect, TIMEOUT).unref(); | ||
setTimeout(createSecondaryClientAndConnect, LOG_AFTER_SEC * 1000).unref(); | ||
}; | ||
secondaryClientPromise = _createClientAndConnect(errorHandlerCreateClient); | ||
secondaryClientPromise = createClientAndConnect(errorHandlerCreateClient, options); | ||
return secondaryClientPromise; | ||
}; | ||
const _createClientBase = () => { | ||
const createClientBase = (options = {}) => { | ||
const adapterActive = cds.env.websocket?.adapter?.active !== false; | ||
if (!adapterActive) { | ||
LOG?.info("Redis adapter is disabled"); | ||
return; | ||
} | ||
const adapterActiveExplicit = !!cds.env.websocket?.adapter?.active; | ||
const adapterLocal = !!cds.env.websocket?.adapter?.local; | ||
if (!(IS_ON_CF || adapterLocal)) { | ||
if (!(IS_ON_CF || adapterActiveExplicit || adapterLocal)) { | ||
LOG?.info("Redis not available in local environment"); | ||
@@ -74,31 +80,38 @@ return; | ||
socket: { tls: credentials.tls }, | ||
...options, | ||
}, | ||
}); | ||
} | ||
return redis.createClient({ url }); | ||
return redis.createClient({ url, ...options }); | ||
} catch (err) { | ||
throw new Error("error during create client with redis-cache service:" + err); | ||
throw new Error("Error during create client with redis-cache service:" + err); | ||
} | ||
}; | ||
const _createClientAndConnect = async (errorHandlerCreateClient) => { | ||
let client; | ||
const createClientAndConnect = async (errorHandlerCreateClient, options) => { | ||
try { | ||
client = _createClientBase(); | ||
} catch (err) { | ||
errorHandlerCreateClient(new Error("Error during create client with redis-cache service:" + err)); | ||
return; | ||
} | ||
if (!client) { | ||
return; | ||
} | ||
client.on("error", errorHandlerCreateClient); | ||
try { | ||
const client = createClientBase(options); | ||
if (!client) { | ||
return; | ||
} | ||
await client.connect(); | ||
LOG?.info("Service redis-cache connected"); | ||
client.on("error", (err) => { | ||
const dateNow = Date.now(); | ||
if (dateNow - lastErrorLog > LOG_AFTER_SEC * 1000) { | ||
LOG?.error("Error from redis client for pub/sub failed", err); | ||
lastErrorLog = dateNow; | ||
} | ||
}); | ||
client.on("reconnecting", () => { | ||
const dateNow = Date.now(); | ||
if (dateNow - lastErrorLog > LOG_AFTER_SEC * 1000) { | ||
LOG?.info("Redis client trying reconnect..."); | ||
lastErrorLog = dateNow; | ||
} | ||
}); | ||
return client; | ||
} catch (err) { | ||
errorHandlerCreateClient(err); | ||
return; | ||
} | ||
return client; | ||
}; | ||
@@ -105,0 +118,0 @@ |
@@ -128,2 +128,6 @@ "use strict"; | ||
} | ||
let config = {}; | ||
if (cds.env.websocket?.adapter?.config) { | ||
config = { ...config, ...cds.env.websocket?.adapter?.config }; | ||
} | ||
let client; | ||
@@ -134,5 +138,5 @@ let subClient; | ||
case "@socket.io/redis-adapter": | ||
client = await redis.createPrimaryClientAndConnect(); | ||
client = await redis.createPrimaryClientAndConnect(config); | ||
if (client) { | ||
subClient = await redis.createSecondaryClientAndConnect(); | ||
subClient = await redis.createSecondaryClientAndConnect(config); | ||
if (subClient) { | ||
@@ -139,0 +143,0 @@ this.adapter = adapterFactory.createAdapter(client, subClient, options); |
@@ -191,5 +191,9 @@ "use strict"; | ||
} | ||
let config = {}; | ||
if (cds.env.websocket?.adapter?.config) { | ||
config = { ...config, ...cds.env.websocket?.adapter?.config }; | ||
} | ||
const prefix = options?.key ?? "websocket"; | ||
const adapterFactory = SocketServer.require(adapterImpl, "adapter"); | ||
this.adapter = new adapterFactory(this, prefix, options); | ||
this.adapter = new adapterFactory(this, prefix, options, config); | ||
await this.adapter?.setup(); | ||
@@ -196,0 +200,0 @@ this.adapterActive = !!this.adapter?.client; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
87557
1269
730
+ Added@sap/xsenv@5.4.0(transitive)
+ Addedverror@1.10.1(transitive)
- Removed@sap/xsenv@4.2.0(transitive)
- Removeddebug@4.3.3(transitive)
- Removedms@2.1.2(transitive)
- Removedverror@1.10.0(transitive)
Updated@sap/xsenv@^5.1.0
Updatedexpress@^4.19.2
Updatedsocket.io@^4.7.5