Compare commits

..

4 Commits

28 changed files with 238 additions and 125 deletions

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.33.4](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.3...@standardnotes/analytics@2.33.4) (2023-11-23)
**Note:** Version bump only for package @standardnotes/analytics
## [2.33.3](https://github.com/standardnotes/server/compare/@standardnotes/analytics@2.33.2...@standardnotes/analytics@2.33.3) (2023-11-22)
**Note:** Version bump only for package @standardnotes/analytics

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/analytics",
"version": "2.33.3",
"version": "2.33.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.86.6](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.5...@standardnotes/api-gateway@1.86.6) (2023-11-23)
**Note:** Version bump only for package @standardnotes/api-gateway
## [1.86.5](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.4...@standardnotes/api-gateway@1.86.5) (2023-11-22)
### Bug Fixes
* error handling on gRPC ([#937](https://github.com/standardnotes/api-gateway/issues/937)) ([8f23c8a](https://github.com/standardnotes/api-gateway/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
## [1.86.4](https://github.com/standardnotes/api-gateway/compare/@standardnotes/api-gateway@1.86.3...@standardnotes/api-gateway@1.86.4) (2023-11-22)
**Note:** Version bump only for package @standardnotes/api-gateway

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/api-gateway",
"version": "1.86.4",
"version": "1.86.6",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -88,7 +88,10 @@ export class GRPCServiceProxy implements ServiceProxyInterface {
endpoint: string,
payload?: Record<string, unknown> | string,
): Promise<void> {
if (endpoint === 'items/sync') {
const requestIsUsingLatestApiVersions =
payload !== undefined && typeof payload !== 'string' && 'api' in payload && payload.api === '20200115'
if (requestIsUsingLatestApiVersions && endpoint === 'items/sync') {
const result = await this.gRPCSyncingServerServiceProxy.sync(request, response, payload)
response.status(result.status).send({

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.173.2](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.1...@standardnotes/auth-server@1.173.2) (2023-11-23)
**Note:** Version bump only for package @standardnotes/auth-server
## [1.173.1](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.173.0...@standardnotes/auth-server@1.173.1) (2023-11-22)
### Bug Fixes
* error handling on gRPC ([#937](https://github.com/standardnotes/server/issues/937)) ([8f23c8a](https://github.com/standardnotes/server/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
# [1.173.0](https://github.com/standardnotes/server/compare/@standardnotes/auth-server@1.172.2...@standardnotes/auth-server@1.173.0) (2023-11-22)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/auth-server",
"version": "1.173.0",
"version": "1.173.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -19,62 +19,75 @@ export class SessionsServer implements ISessionsServer {
call: grpc.ServerUnaryCall<AuthorizationHeader, SessionValidationResponse>,
callback: grpc.sendUnaryData<SessionValidationResponse>,
): Promise<void> {
this.logger.debug('[SessionsServer] Validating session via gRPC')
try {
this.logger.debug('[SessionsServer] Validating session via gRPC')
const authenticateRequestResponse = await this.authenticateRequest.execute({
authorizationHeader: call.request.getBearerToken(),
})
const authenticateRequestResponse = await this.authenticateRequest.execute({
authorizationHeader: call.request.getBearerToken(),
})
if (!authenticateRequestResponse.success) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', authenticateRequestResponse.errorMessage as string)
metadata.set('x-auth-error-tag', authenticateRequestResponse.errorTag as string)
metadata.set('x-auth-error-response-code', authenticateRequestResponse.responseCode.toString())
return callback(
if (!authenticateRequestResponse.success) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', authenticateRequestResponse.errorMessage as string)
metadata.set('x-auth-error-tag', authenticateRequestResponse.errorTag as string)
metadata.set('x-auth-error-response-code', authenticateRequestResponse.responseCode.toString())
return callback(
{
code: Status.PERMISSION_DENIED,
message: authenticateRequestResponse.errorMessage,
name: authenticateRequestResponse.errorTag,
metadata,
},
null,
)
}
const user = authenticateRequestResponse.user as User
const sharedVaultOwnerMetadata = call.metadata.get('x-shared-vault-owner-context')
let sharedVaultOwnerContext = undefined
if (sharedVaultOwnerMetadata.length > 0 && sharedVaultOwnerMetadata[0].length > 0) {
sharedVaultOwnerContext = sharedVaultOwnerMetadata[0].toString()
}
const resultOrError = await this.createCrossServiceToken.execute({
user,
session: authenticateRequestResponse.session,
sharedVaultOwnerContext,
})
if (resultOrError.isFailed()) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', resultOrError.getError())
metadata.set('x-auth-error-response-code', '400')
return callback(
{
code: Status.INVALID_ARGUMENT,
message: resultOrError.getError(),
name: 'INVALID_ARGUMENT',
metadata,
},
null,
)
}
const response = new SessionValidationResponse()
response.setCrossServiceToken(resultOrError.getValue())
this.logger.debug('[SessionsServer] Session validated via gRPC')
callback(null, response)
} catch (error) {
this.logger.error(`[SessionsServer] Error validating session via gRPC: ${(error as Error).message}`)
callback(
{
code: Status.PERMISSION_DENIED,
message: authenticateRequestResponse.errorMessage,
name: authenticateRequestResponse.errorTag,
metadata,
code: Status.UNKNOWN,
message: 'An error occurred while validating session',
name: 'UNKNOWN',
},
null,
)
}
const user = authenticateRequestResponse.user as User
const sharedVaultOwnerMetadata = call.metadata.get('x-shared-vault-owner-context')
let sharedVaultOwnerContext = undefined
if (sharedVaultOwnerMetadata.length > 0 && sharedVaultOwnerMetadata[0].length > 0) {
sharedVaultOwnerContext = sharedVaultOwnerMetadata[0].toString()
}
const resultOrError = await this.createCrossServiceToken.execute({
user,
session: authenticateRequestResponse.session,
sharedVaultOwnerContext,
})
if (resultOrError.isFailed()) {
const metadata = new grpc.Metadata()
metadata.set('x-auth-error-message', resultOrError.getError())
metadata.set('x-auth-error-response-code', '400')
return callback(
{
code: Status.INVALID_ARGUMENT,
message: resultOrError.getError(),
name: 'INVALID_ARGUMENT',
metadata,
},
null,
)
}
const response = new SessionValidationResponse()
response.setCrossServiceToken(resultOrError.getValue())
this.logger.debug('[SessionsServer] Session validated via gRPC')
callback(null, response)
}
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.4](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.3...@standardnotes/domain-events-infra@1.21.4) (2023-11-23)
**Note:** Version bump only for package @standardnotes/domain-events-infra
## [1.21.3](https://github.com/standardnotes/server/compare/@standardnotes/domain-events-infra@1.21.2...@standardnotes/domain-events-infra@1.21.3) (2023-11-22)
**Note:** Version bump only for package @standardnotes/domain-events-infra

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events-infra",
"version": "1.21.3",
"version": "1.21.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,12 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.136.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.135.0...@standardnotes/domain-events@2.136.0) (2023-11-23)
### Features
* **domain-events:** add email campaign send out requested event ([45bd009](https://github.com/standardnotes/server/commit/45bd00919c0062ac4bddd3f858f3ab89166f4e69))
# [2.135.0](https://github.com/standardnotes/server/compare/@standardnotes/domain-events@2.134.2...@standardnotes/domain-events@2.135.0) (2023-11-22)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/domain-events",
"version": "2.135.0",
"version": "2.136.0",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -0,0 +1,7 @@
import { DomainEventInterface } from './DomainEventInterface'
import { EmailCampaignSendOutRequestedEventPayload } from './EmailCampaignSendOutRequestedEventPayload'
export interface EmailCampaignSendOutRequestedEvent extends DomainEventInterface {
type: 'EMAIL_CAMPAIGN_SEND_OUT_REQUESTED'
payload: EmailCampaignSendOutRequestedEventPayload
}

View File

@@ -0,0 +1,5 @@
export interface EmailCampaignSendOutRequestedEventPayload {
limit: number
page: number
campaignFileName: string
}

View File

@@ -16,6 +16,8 @@ export * from './Event/EmailBackupRequestedEvent'
export * from './Event/EmailBackupRequestedEventPayload'
export * from './Event/EmailBouncedEvent'
export * from './Event/EmailBouncedEventPayload'
export * from './Event/EmailCampaignSendOutRequestedEvent'
export * from './Event/EmailCampaignSendOutRequestedEventPayload'
export * from './Event/EmailRequestedEvent'
export * from './Event/EmailRequestedEventPayload'
export * from './Event/EmailSubscriptionUnsubscribedEvent'

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.35.2](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.35.1...@standardnotes/files-server@1.35.2) (2023-11-23)
**Note:** Version bump only for package @standardnotes/files-server
## [1.35.1](https://github.com/standardnotes/files/compare/@standardnotes/files-server@1.35.0...@standardnotes/files-server@1.35.1) (2023-11-22)
**Note:** Version bump only for package @standardnotes/files-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/files-server",
"version": "1.35.1",
"version": "1.35.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.21.11](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.21.10...@standardnotes/home-server@1.21.11) (2023-11-23)
**Note:** Version bump only for package @standardnotes/home-server
## [1.21.10](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.21.9...@standardnotes/home-server@1.21.10) (2023-11-22)
**Note:** Version bump only for package @standardnotes/home-server
## [1.21.9](https://github.com/standardnotes/server/compare/@standardnotes/home-server@1.21.8...@standardnotes/home-server@1.21.9) (2023-11-22)
**Note:** Version bump only for package @standardnotes/home-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/home-server",
"version": "1.21.9",
"version": "1.21.11",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.50.2](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.50.1...@standardnotes/revisions-server@1.50.2) (2023-11-23)
**Note:** Version bump only for package @standardnotes/revisions-server
## [1.50.1](https://github.com/standardnotes/server/compare/@standardnotes/revisions-server@1.50.0...@standardnotes/revisions-server@1.50.1) (2023-11-22)
**Note:** Version bump only for package @standardnotes/revisions-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/revisions-server",
"version": "1.50.1",
"version": "1.50.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.27.4](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.3...@standardnotes/scheduler-server@1.27.4) (2023-11-23)
**Note:** Version bump only for package @standardnotes/scheduler-server
## [1.27.3](https://github.com/standardnotes/server/compare/@standardnotes/scheduler-server@1.27.2...@standardnotes/scheduler-server@1.27.3) (2023-11-22)
**Note:** Version bump only for package @standardnotes/scheduler-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/scheduler-server",
"version": "1.27.3",
"version": "1.27.4",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -3,6 +3,16 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.125.2](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.125.1...@standardnotes/syncing-server@1.125.2) (2023-11-23)
**Note:** Version bump only for package @standardnotes/syncing-server
## [1.125.1](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.125.0...@standardnotes/syncing-server@1.125.1) (2023-11-22)
### Bug Fixes
* error handling on gRPC ([#937](https://github.com/standardnotes/syncing-server-js/issues/937)) ([8f23c8a](https://github.com/standardnotes/syncing-server-js/commit/8f23c8ab3f03e9c23adfb31a33c5805492bc2f5b))
# [1.125.0](https://github.com/standardnotes/syncing-server-js/compare/@standardnotes/syncing-server@1.124.3...@standardnotes/syncing-server@1.125.0) (2023-11-22)
### Features

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/syncing-server",
"version": "1.125.0",
"version": "1.125.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},

View File

@@ -22,38 +22,82 @@ export class SyncingServer implements ISyncingServer {
call: grpc.ServerUnaryCall<SyncRequest, SyncResponse>,
callback: grpc.sendUnaryData<SyncResponse>,
): Promise<void> {
this.logger.debug('[SyncingServer] Syncing items via gRPC')
try {
this.logger.debug('[SyncingServer] Syncing items via gRPC')
const itemHashesRPC = call.request.getItemsList()
const itemHashes: ItemHash[] = []
for (const itemHash of itemHashesRPC) {
const itemHashOrError = ItemHash.create({
uuid: itemHash.getUuid(),
content: itemHash.hasContent() ? itemHash.getContent() : undefined,
content_type: itemHash.hasContentType() ? (itemHash.getContentType() as string) : null,
deleted: itemHash.hasDeleted() ? itemHash.getDeleted() : undefined,
duplicate_of: itemHash.hasDuplicateOf() ? itemHash.getDuplicateOf() : undefined,
auth_hash: itemHash.hasAuthHash() ? itemHash.getAuthHash() : undefined,
enc_item_key: itemHash.hasEncItemKey() ? itemHash.getEncItemKey() : undefined,
items_key_id: itemHash.hasItemsKeyId() ? itemHash.getItemsKeyId() : undefined,
created_at: itemHash.hasCreatedAt() ? itemHash.getCreatedAt() : undefined,
created_at_timestamp: itemHash.hasCreatedAtTimestamp() ? itemHash.getCreatedAtTimestamp() : undefined,
updated_at: itemHash.hasUpdatedAt() ? itemHash.getUpdatedAt() : undefined,
updated_at_timestamp: itemHash.hasUpdatedAtTimestamp() ? itemHash.getUpdatedAtTimestamp() : undefined,
user_uuid: call.metadata.get('userUuid').pop() as string,
key_system_identifier: itemHash.hasKeySystemIdentifier() ? (itemHash.getKeySystemIdentifier() as string) : null,
shared_vault_uuid: itemHash.hasSharedVaultUuid() ? (itemHash.getSharedVaultUuid() as string) : null,
const itemHashesRPC = call.request.getItemsList()
const itemHashes: ItemHash[] = []
for (const itemHash of itemHashesRPC) {
const itemHashOrError = ItemHash.create({
uuid: itemHash.getUuid(),
content: itemHash.hasContent() ? itemHash.getContent() : undefined,
content_type: itemHash.hasContentType() ? (itemHash.getContentType() as string) : null,
deleted: itemHash.hasDeleted() ? itemHash.getDeleted() : undefined,
duplicate_of: itemHash.hasDuplicateOf() ? itemHash.getDuplicateOf() : undefined,
auth_hash: itemHash.hasAuthHash() ? itemHash.getAuthHash() : undefined,
enc_item_key: itemHash.hasEncItemKey() ? itemHash.getEncItemKey() : undefined,
items_key_id: itemHash.hasItemsKeyId() ? itemHash.getItemsKeyId() : undefined,
created_at: itemHash.hasCreatedAt() ? itemHash.getCreatedAt() : undefined,
created_at_timestamp: itemHash.hasCreatedAtTimestamp() ? itemHash.getCreatedAtTimestamp() : undefined,
updated_at: itemHash.hasUpdatedAt() ? itemHash.getUpdatedAt() : undefined,
updated_at_timestamp: itemHash.hasUpdatedAtTimestamp() ? itemHash.getUpdatedAtTimestamp() : undefined,
user_uuid: call.metadata.get('userUuid').pop() as string,
key_system_identifier: itemHash.hasKeySystemIdentifier()
? (itemHash.getKeySystemIdentifier() as string)
: null,
shared_vault_uuid: itemHash.hasSharedVaultUuid() ? (itemHash.getSharedVaultUuid() as string) : null,
})
if (itemHashOrError.isFailed()) {
const metadata = new grpc.Metadata()
metadata.set('x-sync-error-message', itemHashOrError.getError())
metadata.set('x-sync-error-response-code', '400')
return callback(
{
code: Status.INVALID_ARGUMENT,
message: itemHashOrError.getError(),
name: 'INVALID_ARGUMENT',
metadata,
},
null,
)
}
itemHashes.push(itemHashOrError.getValue())
}
let sharedVaultUuids: string[] | undefined = undefined
const sharedVaultUuidsList = call.request.getSharedVaultUuidsList()
if (sharedVaultUuidsList.length > 0) {
sharedVaultUuids = sharedVaultUuidsList
}
const apiVersion = call.request.hasApiVersion() ? (call.request.getApiVersion() as string) : ApiVersion.v20161215
const syncResult = await this.syncItemsUseCase.execute({
userUuid: call.metadata.get('x-user-uuid').pop() as string,
itemHashes,
computeIntegrityHash: call.request.hasComputeIntegrity() ? call.request.getComputeIntegrity() === true : false,
syncToken: call.request.hasSyncToken() ? call.request.getSyncToken() : undefined,
cursorToken: call.request.getCursorToken() ? call.request.getCursorToken() : undefined,
limit: call.request.hasLimit() ? call.request.getLimit() : undefined,
contentType: call.request.hasContentType() ? call.request.getContentType() : undefined,
apiVersion,
snjsVersion: call.metadata.get('x-snjs-version').pop() as string,
readOnlyAccess: call.metadata.get('x-read-only-access').pop() === 'true',
sessionUuid: call.metadata.get('x-session-uuid').pop() as string,
sharedVaultUuids,
})
if (itemHashOrError.isFailed()) {
if (syncResult.isFailed()) {
const metadata = new grpc.Metadata()
metadata.set('x-sync-error-message', itemHashOrError.getError())
metadata.set('x-sync-error-message', syncResult.getError())
metadata.set('x-sync-error-response-code', '400')
return callback(
{
code: Status.INVALID_ARGUMENT,
message: itemHashOrError.getError(),
message: syncResult.getError(),
name: 'INVALID_ARGUMENT',
metadata,
},
@@ -61,53 +105,24 @@ export class SyncingServer implements ISyncingServer {
)
}
itemHashes.push(itemHashOrError.getValue())
}
const syncResponse = await this.syncResponseFactoryResolver
.resolveSyncResponseFactoryVersion(apiVersion)
.createResponse(syncResult.getValue())
let sharedVaultUuids: string[] | undefined = undefined
const sharedVaultUuidsList = call.request.getSharedVaultUuidsList()
if (sharedVaultUuidsList.length > 0) {
sharedVaultUuids = sharedVaultUuidsList
}
const projection = this.mapper.toProjection(syncResponse as SyncResponse20200115)
const apiVersion = call.request.hasApiVersion() ? (call.request.getApiVersion() as string) : ApiVersion.v20161215
const syncResult = await this.syncItemsUseCase.execute({
userUuid: call.metadata.get('x-user-uuid').pop() as string,
itemHashes,
computeIntegrityHash: call.request.hasComputeIntegrity() ? call.request.getComputeIntegrity() === true : false,
syncToken: call.request.hasSyncToken() ? call.request.getSyncToken() : undefined,
cursorToken: call.request.getCursorToken() ? call.request.getCursorToken() : undefined,
limit: call.request.hasLimit() ? call.request.getLimit() : undefined,
contentType: call.request.hasContentType() ? call.request.getContentType() : undefined,
apiVersion,
snjsVersion: call.metadata.get('x-snjs-version').pop() as string,
readOnlyAccess: call.metadata.get('x-read-only-access').pop() === 'true',
sessionUuid: call.metadata.get('x-session-uuid').pop() as string,
sharedVaultUuids,
})
if (syncResult.isFailed()) {
const metadata = new grpc.Metadata()
metadata.set('x-sync-error-message', syncResult.getError())
metadata.set('x-sync-error-response-code', '400')
callback(null, projection)
} catch (error) {
this.logger.error(`[SyncingServer] Error syncing items via gRPC: ${(error as Error).message}`)
return callback(
{
code: Status.INVALID_ARGUMENT,
message: syncResult.getError(),
name: 'INVALID_ARGUMENT',
metadata,
code: Status.UNKNOWN,
message: 'An error occurred while syncing items',
name: 'UNKNOWN',
},
null,
)
}
const syncResponse = await this.syncResponseFactoryResolver
.resolveSyncResponseFactoryVersion(apiVersion)
.createResponse(syncResult.getValue())
const projection = this.mapper.toProjection(syncResponse as SyncResponse20200115)
callback(null, projection)
}
}

View File

@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.20.2](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.20.1...@standardnotes/websockets-server@1.20.2) (2023-11-23)
**Note:** Version bump only for package @standardnotes/websockets-server
## [1.20.1](https://github.com/standardnotes/server/compare/@standardnotes/websockets-server@1.20.0...@standardnotes/websockets-server@1.20.1) (2023-11-22)
**Note:** Version bump only for package @standardnotes/websockets-server

View File

@@ -1,6 +1,6 @@
{
"name": "@standardnotes/websockets-server",
"version": "1.20.1",
"version": "1.20.2",
"engines": {
"node": ">=18.0.0 <21.0.0"
},