@c7-digital/ledger 0.0.3 → 0.0.4
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/BUILD.md +3 -3
- package/README.md +4 -6
- package/lib/src/PackageIdEmitterMap.d.ts +42 -0
- package/lib/src/PackageIdEmitterMap.js +60 -0
- package/lib/src/generated/asyncapi-schema.js +1 -1
- package/lib/src/generated/openapi-schema.js +1 -1
- package/lib/src/ledger.d.ts +15 -5
- package/lib/src/ledger.js +132 -24
- package/lib/src/multistream.d.ts +34 -7
- package/lib/src/multistream.js +69 -25
- package/lib/src/token.d.ts +13 -0
- package/lib/src/token.js +64 -0
- package/lib/src/types.d.ts +62 -2
- package/lib/src/websocket.d.ts +9 -0
- package/lib/src/websocket.js +16 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib-lite/src/PackageIdEmitterMap.d.ts +42 -0
- package/lib-lite/src/PackageIdEmitterMap.js +60 -0
- package/lib-lite/src/ledger.d.ts +15 -5
- package/lib-lite/src/ledger.js +132 -24
- package/lib-lite/src/multistream.d.ts +34 -7
- package/lib-lite/src/multistream.js +69 -25
- package/lib-lite/src/token.d.ts +13 -0
- package/lib-lite/src/token.js +64 -0
- package/lib-lite/src/types.d.ts +62 -2
- package/lib-lite/src/validation.js +2 -2
- package/lib-lite/src/websocket.d.ts +9 -0
- package/lib-lite/src/websocket.js +16 -0
- package/lib-lite/tsconfig.temp.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/lib/src/TemplateEmitterMap.d.ts +0 -40
- package/lib/src/TemplateEmitterMap.js +0 -57
- package/lib-lite/src/TemplateEmitterMap.d.ts +0 -40
- package/lib-lite/src/TemplateEmitterMap.js +0 -57
package/lib/src/multistream.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EventEmitter } from "eventemitter3";
|
|
2
2
|
import { logger } from "./logger.js";
|
|
3
|
-
import {
|
|
3
|
+
import { PackageIdEmitterMap, } from "./PackageIdEmitterMap.js";
|
|
4
4
|
/**
|
|
5
5
|
* Adapts a Stream instance to the MultiStream interface
|
|
6
6
|
* Provides template-specific event handlers with proper typing
|
|
@@ -11,7 +11,7 @@ export class MultiStreamAdapter {
|
|
|
11
11
|
* @param stream The underlying Stream instance
|
|
12
12
|
*/
|
|
13
13
|
constructor(stream) {
|
|
14
|
-
this.
|
|
14
|
+
this.packageIdEmitters = new PackageIdEmitterMap();
|
|
15
15
|
this.errorEmitter = new EventEmitter(); // Dedicated error emitter
|
|
16
16
|
this.stateEmitter = new EventEmitter(); // Dedicated state emitter
|
|
17
17
|
this.stream = stream;
|
|
@@ -22,38 +22,38 @@ export class MultiStreamAdapter {
|
|
|
22
22
|
this.stream.on("state", this.handleStateEvent.bind(this));
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Get or create an EventEmitter for a specific
|
|
25
|
+
* Get or create an EventEmitter for a specific package ID
|
|
26
26
|
*/
|
|
27
|
-
|
|
28
|
-
if (!this.
|
|
29
|
-
this.
|
|
27
|
+
getEmitterForPackageId(packageId) {
|
|
28
|
+
if (!this.packageIdEmitters.has(packageId)) {
|
|
29
|
+
this.packageIdEmitters.set(packageId, new EventEmitter());
|
|
30
30
|
}
|
|
31
|
-
return this.
|
|
31
|
+
return this.packageIdEmitters.get(packageId);
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Handle create events from the underlying stream
|
|
35
|
-
* and route them to the appropriate
|
|
35
|
+
* and route them to the appropriate package ID emitter
|
|
36
36
|
*/
|
|
37
37
|
handleCreateEvent(event) {
|
|
38
|
-
const
|
|
39
|
-
if (this.
|
|
40
|
-
this.
|
|
38
|
+
const packageId = event.templateId;
|
|
39
|
+
if (this.packageIdEmitters.has(packageId)) {
|
|
40
|
+
this.packageIdEmitters.get(packageId).emit("create", event);
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
43
|
-
logger.warn(`Received create event for unknown template ${
|
|
43
|
+
logger.warn(`Received create event for unknown template ${packageId}`);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Handle archive events from the underlying stream
|
|
48
|
-
* and route them to the appropriate
|
|
48
|
+
* and route them to the appropriate package ID emitter
|
|
49
49
|
*/
|
|
50
50
|
handleArchiveEvent(event) {
|
|
51
|
-
const
|
|
52
|
-
if (this.
|
|
53
|
-
this.
|
|
51
|
+
const packageId = event.templateId;
|
|
52
|
+
if (this.packageIdEmitters.has(packageId)) {
|
|
53
|
+
this.packageIdEmitters.get(packageId).emit("archive", event);
|
|
54
54
|
}
|
|
55
55
|
else {
|
|
56
|
-
logger.warn(`Received archive event for unknown template ${
|
|
56
|
+
logger.warn(`Received archive event for unknown template ${packageId}`);
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
@@ -69,14 +69,14 @@ export class MultiStreamAdapter {
|
|
|
69
69
|
}
|
|
70
70
|
// MultiStream interface implementation
|
|
71
71
|
onCreate(templateId, listener) {
|
|
72
|
-
const emitter = this.
|
|
72
|
+
const emitter = this.getEmitterForPackageId(templateId);
|
|
73
73
|
emitter.on("create", (event) => {
|
|
74
74
|
// Type assertion: we know the event has the correct type based on templateId
|
|
75
75
|
listener(event);
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
onArchive(templateId, listener) {
|
|
79
|
-
const emitter = this.
|
|
79
|
+
const emitter = this.getEmitterForPackageId(templateId);
|
|
80
80
|
emitter.on("archive", (event) => {
|
|
81
81
|
// Type assertion: we know the event has the correct type based on templateId
|
|
82
82
|
listener(event);
|
|
@@ -89,13 +89,13 @@ export class MultiStreamAdapter {
|
|
|
89
89
|
this.stream.on("state", listener);
|
|
90
90
|
}
|
|
91
91
|
offCreate(templateId, listener) {
|
|
92
|
-
if (this.
|
|
93
|
-
this.
|
|
92
|
+
if (this.packageIdEmitters.has(templateId)) {
|
|
93
|
+
this.packageIdEmitters.get(templateId).off("create", listener);
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
offArchive(templateId, listener) {
|
|
97
|
-
if (this.
|
|
98
|
-
this.
|
|
97
|
+
if (this.packageIdEmitters.has(templateId)) {
|
|
98
|
+
this.packageIdEmitters.get(templateId).off("archive", listener);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
offError(listener) {
|
|
@@ -112,12 +112,56 @@ export class MultiStreamAdapter {
|
|
|
112
112
|
}
|
|
113
113
|
close() {
|
|
114
114
|
this.stream.close();
|
|
115
|
-
for (const emitter of this.
|
|
115
|
+
for (const emitter of this.packageIdEmitters.values()) {
|
|
116
116
|
emitter.removeAllListeners();
|
|
117
117
|
}
|
|
118
|
-
this.
|
|
118
|
+
this.packageIdEmitters.clear();
|
|
119
119
|
// Clean up the error emitter too
|
|
120
120
|
this.errorEmitter.removeAllListeners();
|
|
121
121
|
this.stateEmitter.removeAllListeners();
|
|
122
122
|
}
|
|
123
|
+
updateToken(newToken) {
|
|
124
|
+
this.stream.updateToken(newToken);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extends MultiStreamAdapter to add interface-specific event handling
|
|
129
|
+
* Provides both template-specific and interface-specific event handlers with proper typing
|
|
130
|
+
*/
|
|
131
|
+
export class InterfaceMultiStreamImpl extends MultiStreamAdapter {
|
|
132
|
+
/**
|
|
133
|
+
* Create a new InterfaceMultiStream adapter
|
|
134
|
+
* @param stream The underlying InterfaceStream instance
|
|
135
|
+
*/
|
|
136
|
+
constructor(stream) {
|
|
137
|
+
// Pass the stream to the parent MultiStreamAdapter
|
|
138
|
+
super(stream);
|
|
139
|
+
this.interfaceStream = stream;
|
|
140
|
+
// Set up interface-specific event listener
|
|
141
|
+
this.interfaceStream.on("interfaceView", this.handleInterfaceViewEvent.bind(this));
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Handle interfaceView events from the underlying stream
|
|
145
|
+
*/
|
|
146
|
+
handleInterfaceViewEvent(event) {
|
|
147
|
+
const interfaceId = event.interfaceId;
|
|
148
|
+
if (this.packageIdEmitters.has(interfaceId)) {
|
|
149
|
+
this.packageIdEmitters.get(interfaceId).emit("interfaceView", event);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
logger.warn(`Received interfaceView event for unknown interface ${interfaceId}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// InterfaceMultiStreamMethods implementation
|
|
156
|
+
onInterfaceView(interfaceId, listener) {
|
|
157
|
+
const emitter = this.getEmitterForPackageId(interfaceId);
|
|
158
|
+
emitter.on("interfaceView", (event) => {
|
|
159
|
+
listener(event);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
offInterfaceView(interfaceId, listener) {
|
|
163
|
+
if (this.packageIdEmitters.has(interfaceId)) {
|
|
164
|
+
this.packageIdEmitters.get(interfaceId).off("interfaceView", listener);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
123
167
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface TokenInfo {
|
|
2
|
+
userId: string;
|
|
3
|
+
expiresAt: number;
|
|
4
|
+
issuedAt: number;
|
|
5
|
+
isExpired: boolean;
|
|
6
|
+
timeUntilExpiry: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Log token expiration information
|
|
10
|
+
* @param token The JWT token string
|
|
11
|
+
* @param context Additional context for logging (e.g., "connection", "reconnection")
|
|
12
|
+
*/
|
|
13
|
+
export declare function logTokenExpiration(token: string, context?: string): TokenInfo;
|
package/lib/src/token.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT token utilities for Canton ledger authentication
|
|
3
|
+
*/
|
|
4
|
+
import { decodeJwt } from "jose";
|
|
5
|
+
import { logger } from "./logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* Decode and analyze a JWT token
|
|
8
|
+
* @param token The JWT token string
|
|
9
|
+
* @returns Token information including expiration details
|
|
10
|
+
*/
|
|
11
|
+
function analyzeToken(token) {
|
|
12
|
+
const payload = decodeJwt(token);
|
|
13
|
+
if (!payload.sub) {
|
|
14
|
+
throw new Error("Token payload missing 'sub' field");
|
|
15
|
+
}
|
|
16
|
+
const now = Math.floor(Date.now() / 1000);
|
|
17
|
+
const expiresAt = payload.exp || 0;
|
|
18
|
+
const issuedAt = payload.iat || 0;
|
|
19
|
+
const isExpired = expiresAt > 0 && expiresAt <= now;
|
|
20
|
+
const timeUntilExpiry = expiresAt > 0 ? expiresAt - now : 0;
|
|
21
|
+
return {
|
|
22
|
+
userId: payload.sub,
|
|
23
|
+
expiresAt,
|
|
24
|
+
issuedAt,
|
|
25
|
+
isExpired,
|
|
26
|
+
timeUntilExpiry,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Log token expiration information
|
|
31
|
+
* @param token The JWT token string
|
|
32
|
+
* @param context Additional context for logging (e.g., "connection", "reconnection")
|
|
33
|
+
*/
|
|
34
|
+
export function logTokenExpiration(token, context = "token") {
|
|
35
|
+
try {
|
|
36
|
+
const tokenInfo = analyzeToken(token);
|
|
37
|
+
if (tokenInfo.isExpired) {
|
|
38
|
+
logger.warn(`${context}: Token is EXPIRED`, {
|
|
39
|
+
expiresAt: new Date(tokenInfo.expiresAt * 1000).toISOString(),
|
|
40
|
+
expiredSecondsAgo: Math.abs(tokenInfo.timeUntilExpiry),
|
|
41
|
+
userId: tokenInfo.userId,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
else if (tokenInfo.timeUntilExpiry < 300) { // Less than 5 minutes
|
|
45
|
+
logger.warn(`${context}: Token expires soon`, {
|
|
46
|
+
expiresAt: new Date(tokenInfo.expiresAt * 1000).toISOString(),
|
|
47
|
+
secondsUntilExpiry: tokenInfo.timeUntilExpiry,
|
|
48
|
+
userId: tokenInfo.userId,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
logger.debug(`${context}: Token is valid`, {
|
|
53
|
+
expiresAt: new Date(tokenInfo.expiresAt * 1000).toISOString(),
|
|
54
|
+
secondsUntilExpiry: tokenInfo.timeUntilExpiry,
|
|
55
|
+
userId: tokenInfo.userId,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return tokenInfo;
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
logger.error(`${context}: Failed to analyze token`, { error: error instanceof Error ? error.message : String(error) });
|
|
62
|
+
return { userId: "", expiresAt: 0, issuedAt: 0, isExpired: true, timeUntilExpiry: 0 };
|
|
63
|
+
}
|
|
64
|
+
}
|
package/lib/src/types.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export type Interface<I extends object> = {
|
|
|
43
43
|
key?: any;
|
|
44
44
|
createdEventBlob: string;
|
|
45
45
|
interfaceView: I;
|
|
46
|
+
interfaceId: PackageIdString;
|
|
46
47
|
/**
|
|
47
48
|
* Package version string (e.g., "0.0.6")
|
|
48
49
|
* Only present if using VersionedRegistry
|
|
@@ -67,7 +68,7 @@ export type VersionedLookupResult = {
|
|
|
67
68
|
*
|
|
68
69
|
* This type can be passed into our Ledger so that we can use it instead. We
|
|
69
70
|
* overload the name of the lookup parameter 'templateId' even though in the
|
|
70
|
-
* interface we really mean the interfaceId as in the
|
|
71
|
+
* interface we really mean the interfaceId as in the OpenAPI spec.
|
|
71
72
|
*/
|
|
72
73
|
export type VersionedRegistry = (templateId: string) => VersionedLookupResult | undefined;
|
|
73
74
|
export type LedgerOffset = "start" | "end" | number;
|
|
@@ -78,7 +79,7 @@ export interface StreamCloseEvent {
|
|
|
78
79
|
}
|
|
79
80
|
export type StreamState = "start" | "init" | "live" | "stop";
|
|
80
81
|
/**
|
|
81
|
-
* The return interface of
|
|
82
|
+
* The return interface of streamTemplate - for template-specific streaming with known types.
|
|
82
83
|
*/
|
|
83
84
|
export interface Stream<T extends object, K> {
|
|
84
85
|
on(type: "create", listener: (event: CreateEvent<T, K>) => void): void;
|
|
@@ -92,7 +93,24 @@ export interface Stream<T extends object, K> {
|
|
|
92
93
|
start(): void;
|
|
93
94
|
state(): StreamState;
|
|
94
95
|
close(): void;
|
|
96
|
+
updateToken(newToken: string): void;
|
|
95
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* Additional interface-specific methods for interface streaming.
|
|
100
|
+
*/
|
|
101
|
+
export interface InterfaceStreamMethods<I extends object> {
|
|
102
|
+
on(type: "interfaceView", listener: (event: Interface<I>) => void): void;
|
|
103
|
+
off(type: "interfaceView", listener: (event: Interface<I>) => void): void;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* The return interface of streamInterface - combines Stream with interface-specific events.
|
|
107
|
+
*
|
|
108
|
+
* Most of the time, if you are subscribing to an interface, the underlying
|
|
109
|
+
* template is operationally not interesting to you (otherwise, you would be
|
|
110
|
+
* subscribing to the template directly). Consequently we will emit the
|
|
111
|
+
* template related events but not make an effort to decode them.
|
|
112
|
+
*/
|
|
113
|
+
export type InterfaceStream<I extends object> = Stream<object, unknown> & InterfaceStreamMethods<I>;
|
|
96
114
|
/**
|
|
97
115
|
* Template mapping type for MultiStream:
|
|
98
116
|
* template IDs to their corresponding contract and key types
|
|
@@ -101,6 +119,30 @@ export type TemplateMapping = Record<string, {
|
|
|
101
119
|
contractType: object;
|
|
102
120
|
keyType: unknown;
|
|
103
121
|
}>;
|
|
122
|
+
/**
|
|
123
|
+
* Interface mapping type for InterfaceMultiStream:
|
|
124
|
+
* interface IDs to their corresponding contract types
|
|
125
|
+
*/
|
|
126
|
+
export type InterfaceMapping = Record<string, {
|
|
127
|
+
contractType: object;
|
|
128
|
+
}>;
|
|
129
|
+
/**
|
|
130
|
+
* Interface-specific methods for MultiStream
|
|
131
|
+
*/
|
|
132
|
+
export interface InterfaceMultiStreamMethods<IM extends InterfaceMapping> {
|
|
133
|
+
/**
|
|
134
|
+
* Register a listener for interfaceView events for a specific interface
|
|
135
|
+
* @param interfaceId The interface ID to listen for
|
|
136
|
+
* @param listener The callback function that will receive properly typed interface events
|
|
137
|
+
*/
|
|
138
|
+
onInterfaceView<IID extends keyof IM>(interfaceId: IID, listener: (event: Interface<IM[IID]["contractType"]>) => void): void;
|
|
139
|
+
/**
|
|
140
|
+
* Remove an interfaceView event listener for a specific interface
|
|
141
|
+
* @param interfaceId The interface ID to stop listening for
|
|
142
|
+
* @param listener The callback function to remove
|
|
143
|
+
*/
|
|
144
|
+
offInterfaceView<IID extends keyof IM>(interfaceId: IID, listener: (event: Interface<IM[IID]["contractType"]>) => void): void;
|
|
145
|
+
}
|
|
104
146
|
/**
|
|
105
147
|
* Provides template-specific event handlers.
|
|
106
148
|
*/
|
|
@@ -144,7 +186,24 @@ export interface MultiStream<TM extends TemplateMapping> {
|
|
|
144
186
|
start(): void;
|
|
145
187
|
state(): StreamState;
|
|
146
188
|
close(): void;
|
|
189
|
+
updateToken(newToken: string): void;
|
|
147
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Derives a TemplateMapping from an InterfaceMapping
|
|
193
|
+
* Templates underlying interfaces have generic object contractType and unknown keyType
|
|
194
|
+
*/
|
|
195
|
+
type DerivedTemplateMapping<IM extends InterfaceMapping> = {
|
|
196
|
+
[K in keyof IM]: {
|
|
197
|
+
contractType: object;
|
|
198
|
+
keyType: unknown;
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* Extended MultiStream interface that includes interface-specific event handlers
|
|
203
|
+
* Similar to how InterfaceStream extends Stream
|
|
204
|
+
* Derives the template mapping from the interface mapping since interfaces are implemented for templates
|
|
205
|
+
*/
|
|
206
|
+
export type InterfaceMultiStream<IM extends InterfaceMapping> = MultiStream<DerivedTemplateMapping<IM>> & InterfaceMultiStreamMethods<IM>;
|
|
148
207
|
export interface Query<T = unknown> {
|
|
149
208
|
[key: string]: unknown;
|
|
150
209
|
}
|
|
@@ -199,3 +258,4 @@ export interface User {
|
|
|
199
258
|
primaryParty?: Party;
|
|
200
259
|
rights: UserRight[];
|
|
201
260
|
}
|
|
261
|
+
export {};
|
package/lib/src/websocket.d.ts
CHANGED
|
@@ -47,6 +47,15 @@ export declare class WebSocketClient {
|
|
|
47
47
|
private wsBaseUrl;
|
|
48
48
|
private validator?;
|
|
49
49
|
constructor(config: StreamConfig);
|
|
50
|
+
/**
|
|
51
|
+
* Get the current token (for logging and validation purposes)
|
|
52
|
+
*/
|
|
53
|
+
getToken(): string;
|
|
54
|
+
/**
|
|
55
|
+
* Update the token used for authentication
|
|
56
|
+
* Note: This only updates the stored token. Active connections need to be recreated.
|
|
57
|
+
*/
|
|
58
|
+
setToken(newToken: string): void;
|
|
50
59
|
/**
|
|
51
60
|
* Generic streaming method with full type safety
|
|
52
61
|
* This method enforces that request/response types match the endpoint
|
package/lib/src/websocket.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SchemaValidator } from "./validation.js";
|
|
2
2
|
import { logger } from "./logger.js";
|
|
3
|
+
import { logTokenExpiration } from "./token.js";
|
|
3
4
|
import WebSocket from "isomorphic-ws";
|
|
4
5
|
// Constant mapping from endpoints to their JSON Schema validation paths
|
|
5
6
|
const SCHEMA_MAP = {
|
|
@@ -54,6 +55,19 @@ export class WebSocketClient {
|
|
|
54
55
|
? new SchemaValidator("asyncapi", config.validation)
|
|
55
56
|
: undefined;
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Get the current token (for logging and validation purposes)
|
|
60
|
+
*/
|
|
61
|
+
getToken() {
|
|
62
|
+
return this.token;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Update the token used for authentication
|
|
66
|
+
* Note: This only updates the stored token. Active connections need to be recreated.
|
|
67
|
+
*/
|
|
68
|
+
setToken(newToken) {
|
|
69
|
+
this.token = newToken;
|
|
70
|
+
}
|
|
57
71
|
/**
|
|
58
72
|
* Generic streaming method with full type safety
|
|
59
73
|
* This method enforces that request/response types match the endpoint
|
|
@@ -99,6 +113,8 @@ export class WebSocketClient {
|
|
|
99
113
|
const url = endpoint.startsWith("/")
|
|
100
114
|
? `${baseUrl}${endpoint.slice(1)}`
|
|
101
115
|
: `${baseUrl}/${endpoint}`;
|
|
116
|
+
// Log token expiration info before connecting
|
|
117
|
+
logTokenExpiration(this.token, `WebSocket connection to ${endpoint}`);
|
|
102
118
|
// WebSocket authentication via Sec-WebSocket-Protocol header
|
|
103
119
|
const protocols = [`jwt.token.${this.token}`, "daml.ws.auth"];
|
|
104
120
|
logger.debug(`Connecting websocket to ${url} with protocols: ${protocols}`);
|