@enbox/dwn-server 0.1.2 → 0.1.3
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/dist/esm/src/admin/admin-api.js +2 -2
- package/dist/esm/src/admin/admin-api.js.map +1 -1
- package/dist/esm/src/admin/admin-auth.js +1 -1
- package/dist/esm/src/admin/admin-auth.js.map +1 -1
- package/dist/esm/src/admin/admin-passkey-store.js.map +1 -1
- package/dist/esm/src/admin/admin-session.js.map +1 -1
- package/dist/esm/src/admin/admin-store.d.ts +2 -2
- package/dist/esm/src/admin/admin-store.d.ts.map +1 -1
- package/dist/esm/src/admin/admin-store.js +1 -1
- package/dist/esm/src/admin/admin-store.js.map +1 -1
- package/dist/esm/src/admin/audit-log.js.map +1 -1
- package/dist/esm/src/admin/webhook-manager.js.map +1 -1
- package/dist/esm/src/connect/connect-server.d.ts +1 -1
- package/dist/esm/src/connect/connect-server.d.ts.map +1 -1
- package/dist/esm/src/connect/connect-server.js.map +1 -1
- package/dist/esm/src/connect/sql-ttl-cache.d.ts +1 -1
- package/dist/esm/src/connect/sql-ttl-cache.d.ts.map +1 -1
- package/dist/esm/src/connect/sql-ttl-cache.js.map +1 -1
- package/dist/esm/src/connection/connection-manager.d.ts +9 -9
- package/dist/esm/src/connection/connection-manager.d.ts.map +1 -1
- package/dist/esm/src/connection/connection-manager.js.map +1 -1
- package/dist/esm/src/connection/socket-connection.d.ts +13 -13
- package/dist/esm/src/connection/socket-connection.d.ts.map +1 -1
- package/dist/esm/src/connection/socket-connection.js.map +1 -1
- package/dist/esm/src/delivery-service.d.ts.map +1 -1
- package/dist/esm/src/delivery-service.js +15 -7
- package/dist/esm/src/delivery-service.js.map +1 -1
- package/dist/esm/src/dwn-server.js.map +1 -1
- package/dist/esm/src/http-api.js +18 -18
- package/dist/esm/src/http-api.js.map +1 -1
- package/dist/esm/src/plugins/event-log-nats.js +16 -16
- package/dist/esm/src/plugins/event-log-nats.js.map +1 -1
- package/dist/esm/src/rate-limiter.js.map +1 -1
- package/dist/esm/src/registration/jwt-provider-auth-plugin.js.map +1 -1
- package/dist/esm/src/registration/open-auth-handler.js.map +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.d.ts +5 -5
- package/dist/esm/src/registration/proof-of-work-manager.d.ts.map +1 -1
- package/dist/esm/src/registration/proof-of-work-manager.js +12 -10
- package/dist/esm/src/registration/proof-of-work-manager.js.map +1 -1
- package/dist/esm/src/registration/registration-manager.js +6 -6
- package/dist/esm/src/registration/registration-manager.js.map +1 -1
- package/dist/esm/src/registration/registration-store.d.ts +1 -1
- package/dist/esm/src/registration/registration-store.d.ts.map +1 -1
- package/dist/esm/src/registration/registration-store.js.map +1 -1
- package/dist/esm/src/server-migration-runner.js.map +1 -1
- package/dist/esm/src/ws-api.js.map +1 -1
- package/package.json +5 -5
- package/src/admin/admin-api.ts +5 -5
- package/src/admin/admin-auth.ts +1 -1
- package/src/admin/admin-passkey-store.ts +1 -1
- package/src/admin/admin-session.ts +2 -2
- package/src/admin/admin-store.ts +3 -3
- package/src/admin/audit-log.ts +1 -1
- package/src/admin/webhook-manager.ts +1 -1
- package/src/connect/connect-server.ts +1 -1
- package/src/connect/sql-ttl-cache.ts +1 -1
- package/src/connection/connection-manager.ts +9 -9
- package/src/connection/socket-connection.ts +13 -13
- package/src/delivery-service.ts +16 -7
- package/src/dwn-server.ts +3 -3
- package/src/http-api.ts +15 -15
- package/src/plugins/event-log-nats.ts +14 -14
- package/src/rate-limiter.ts +1 -1
- package/src/registration/jwt-provider-auth-plugin.ts +3 -3
- package/src/registration/open-auth-handler.ts +5 -5
- package/src/registration/proof-of-work-manager.ts +17 -15
- package/src/registration/registration-manager.ts +6 -6
- package/src/registration/registration-store.ts +1 -1
- package/src/server-migration-runner.ts +1 -1
- package/src/ws-api.ts +1 -1
package/src/dwn-server.ts
CHANGED
|
@@ -101,9 +101,9 @@ export class DwnServer {
|
|
|
101
101
|
#auditLog: AuditLog | undefined;
|
|
102
102
|
#passkeyStore: AdminPasskeyStore | undefined;
|
|
103
103
|
#sessionManager: AdminSessionManager | undefined;
|
|
104
|
-
#externalHooks: MessageProcessedHook[];
|
|
105
|
-
#externalRegistrationManager: RegistrationManager | undefined;
|
|
106
|
-
#externalOpenAuthHandler: OpenAuthHandler | undefined;
|
|
104
|
+
readonly #externalHooks: MessageProcessedHook[];
|
|
105
|
+
readonly #externalRegistrationManager: RegistrationManager | undefined;
|
|
106
|
+
readonly #externalOpenAuthHandler: OpenAuthHandler | undefined;
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* @param options.dwn - Dwn instance to use as an override.
|
package/src/http-api.ts
CHANGED
|
@@ -800,11 +800,11 @@ export class HttpApi {
|
|
|
800
800
|
return Response.json({ success: true }, { status: 200 });
|
|
801
801
|
} catch (error) {
|
|
802
802
|
const dwnServerError = error as DwnServerError;
|
|
803
|
-
if (dwnServerError.code
|
|
804
|
-
return Response.json(dwnServerError, { status: 400 });
|
|
805
|
-
} else {
|
|
803
|
+
if (dwnServerError.code === undefined) {
|
|
806
804
|
log.info('Error handling registration request:', error);
|
|
807
805
|
return Response.json({ success: false }, { status: 500 });
|
|
806
|
+
} else {
|
|
807
|
+
return Response.json(dwnServerError, { status: 400 });
|
|
808
808
|
}
|
|
809
809
|
}
|
|
810
810
|
}
|
|
@@ -850,18 +850,18 @@ export class HttpApi {
|
|
|
850
850
|
log.info(`Retrieving Connect Request object of ID: ${requestId}...`);
|
|
851
851
|
|
|
852
852
|
const requestObjectJwt = await this.connectServer.getConnectRequest(requestId);
|
|
853
|
-
if (
|
|
854
|
-
return Response.json({
|
|
855
|
-
ok : false,
|
|
856
|
-
status : { code: 404, message: 'Not Found' },
|
|
857
|
-
}, { status: 404 });
|
|
858
|
-
} else {
|
|
853
|
+
if (requestObjectJwt) {
|
|
859
854
|
const body = typeof requestObjectJwt === 'string'
|
|
860
855
|
? requestObjectJwt
|
|
861
856
|
: JSON.stringify(requestObjectJwt);
|
|
862
857
|
return new Response(body, {
|
|
863
858
|
headers: { 'content-type': 'application/jwt' },
|
|
864
859
|
});
|
|
860
|
+
} else {
|
|
861
|
+
return Response.json({
|
|
862
|
+
ok : false,
|
|
863
|
+
status : { code: 404, message: 'Not Found' },
|
|
864
|
+
}, { status: 404 });
|
|
865
865
|
}
|
|
866
866
|
}
|
|
867
867
|
}
|
|
@@ -910,16 +910,16 @@ export class HttpApi {
|
|
|
910
910
|
log.info(`Retrieving ID token for state: ${state}...`);
|
|
911
911
|
|
|
912
912
|
const idToken = await this.connectServer.getConnectResponse(state);
|
|
913
|
-
if (
|
|
914
|
-
return Response.json({
|
|
915
|
-
ok : false,
|
|
916
|
-
status : { code: 404, message: 'Not Found' },
|
|
917
|
-
}, { status: 404 });
|
|
918
|
-
} else {
|
|
913
|
+
if (idToken) {
|
|
919
914
|
const body = typeof idToken === 'string' ? idToken : JSON.stringify(idToken);
|
|
920
915
|
return new Response(body, {
|
|
921
916
|
headers: { 'content-type': 'application/jwt' },
|
|
922
917
|
});
|
|
918
|
+
} else {
|
|
919
|
+
return Response.json({
|
|
920
|
+
ok : false,
|
|
921
|
+
status : { code: 404, message: 'Not Found' },
|
|
922
|
+
}, { status: 404 });
|
|
923
923
|
}
|
|
924
924
|
}
|
|
925
925
|
}
|
|
@@ -161,13 +161,13 @@ function tenantToSubjectToken(tenant: string): string {
|
|
|
161
161
|
* Must be a default export with a no-arg constructor.
|
|
162
162
|
*/
|
|
163
163
|
export default class NatsEventLog implements EventLog {
|
|
164
|
-
#config: NatsEventLogConfig;
|
|
164
|
+
readonly #config: NatsEventLogConfig;
|
|
165
165
|
#nc: NatsConnection | undefined;
|
|
166
166
|
#js: JetStreamClient | undefined;
|
|
167
167
|
#jsm: JetStreamManager | undefined;
|
|
168
168
|
|
|
169
169
|
/** Active subscription consumers, keyed by consumer name. */
|
|
170
|
-
#activeConsumers: Map<string, { messages?: ConsumerMessages; stopped: boolean }> = new Map();
|
|
170
|
+
readonly #activeConsumers: Map<string, { messages?: ConsumerMessages; stopped: boolean }> = new Map();
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
173
|
* Epoch for this EventLog instance. Stable as long as the JetStream stream exists.
|
|
@@ -252,13 +252,13 @@ export default class NatsEventLog implements EventLog {
|
|
|
252
252
|
let reason: ProgressGapReason;
|
|
253
253
|
if (cursor.streamId !== expectedStreamId) {
|
|
254
254
|
reason = 'stream_mismatch';
|
|
255
|
-
} else if (cursor.epoch
|
|
256
|
-
reason = 'epoch_mismatch';
|
|
257
|
-
} else {
|
|
255
|
+
} else if (cursor.epoch === this.#epoch) {
|
|
258
256
|
// Check if position is within replay bounds using BigInt
|
|
259
257
|
// for safe handling of NATS sequences beyond Number.MAX_SAFE_INTEGER.
|
|
260
258
|
const bounds = await this.getReplayBounds(tenant);
|
|
261
|
-
if (bounds
|
|
259
|
+
if (bounds === undefined) {
|
|
260
|
+
return; // No events — vacuously valid.
|
|
261
|
+
} else {
|
|
262
262
|
const cursorSeq = BigInt(cursor.position);
|
|
263
263
|
const oldestSeq = BigInt(bounds.oldest.position);
|
|
264
264
|
if (cursorSeq < oldestSeq - 1n) {
|
|
@@ -266,9 +266,9 @@ export default class NatsEventLog implements EventLog {
|
|
|
266
266
|
} else {
|
|
267
267
|
return; // Valid.
|
|
268
268
|
}
|
|
269
|
-
} else {
|
|
270
|
-
return; // No events — vacuously valid.
|
|
271
269
|
}
|
|
270
|
+
} else {
|
|
271
|
+
reason = 'epoch_mismatch';
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
const bounds = await this.getReplayBounds(tenant);
|
|
@@ -315,11 +315,11 @@ export default class NatsEventLog implements EventLog {
|
|
|
315
315
|
ack_policy : AckPolicy.None, // ordered consumers use AckNone
|
|
316
316
|
};
|
|
317
317
|
|
|
318
|
-
if (cursor
|
|
318
|
+
if (cursor === undefined) {
|
|
319
|
+
consumerOpts.deliver_policy = DeliverPolicy.All;
|
|
320
|
+
} else {
|
|
319
321
|
consumerOpts.deliver_policy = DeliverPolicy.StartSequence;
|
|
320
322
|
consumerOpts.opt_start_seq = Number(cursor.position) + 1;
|
|
321
|
-
} else {
|
|
322
|
-
consumerOpts.deliver_policy = DeliverPolicy.All;
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
const consumer = await this.#jsm!.consumers.add(this.#config.streamName, consumerOpts);
|
|
@@ -405,11 +405,11 @@ export default class NatsEventLog implements EventLog {
|
|
|
405
405
|
inactive_threshold : 60_000_000_000, // 60 seconds in nanos
|
|
406
406
|
};
|
|
407
407
|
|
|
408
|
-
if (cursor
|
|
408
|
+
if (cursor === undefined) {
|
|
409
|
+
consumerOpts.deliver_policy = DeliverPolicy.New;
|
|
410
|
+
} else {
|
|
409
411
|
consumerOpts.deliver_policy = DeliverPolicy.StartSequence;
|
|
410
412
|
consumerOpts.opt_start_seq = Number(cursor.position) + 1;
|
|
411
|
-
} else {
|
|
412
|
-
consumerOpts.deliver_policy = DeliverPolicy.New;
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
await this.#jsm!.consumers.add(this.#config.streamName, consumerOpts);
|
package/src/rate-limiter.ts
CHANGED
|
@@ -25,7 +25,7 @@ type Bucket = {
|
|
|
25
25
|
export class RateLimiter {
|
|
26
26
|
#refillRate: number;
|
|
27
27
|
#maxTokens: number;
|
|
28
|
-
#buckets: Map<string, Bucket> = new Map();
|
|
28
|
+
readonly #buckets: Map<string, Bucket> = new Map();
|
|
29
29
|
#cleanupInterval: ReturnType<typeof setInterval> | undefined;
|
|
30
30
|
|
|
31
31
|
/** Stale buckets older than 5 minutes are purged. */
|
|
@@ -36,9 +36,9 @@ export type JwtProviderAuthPluginOptions = {
|
|
|
36
36
|
* {@link ProviderAuthPlugin} instead.
|
|
37
37
|
*/
|
|
38
38
|
export class JwtProviderAuthPlugin implements ProviderAuthPlugin {
|
|
39
|
-
#getKey: Uint8Array | jose.JWTVerifyGetKey;
|
|
40
|
-
#issuer: string | undefined;
|
|
41
|
-
#audience: string | undefined;
|
|
39
|
+
readonly #getKey: Uint8Array | jose.JWTVerifyGetKey;
|
|
40
|
+
readonly #issuer: string | undefined;
|
|
41
|
+
readonly #audience: string | undefined;
|
|
42
42
|
|
|
43
43
|
private constructor(
|
|
44
44
|
getKey: Uint8Array | jose.JWTVerifyGetKey,
|
|
@@ -28,14 +28,14 @@ const MAX_PENDING_CODES = 10_000;
|
|
|
28
28
|
const CLEANUP_INTERVAL_MS = 60_000;
|
|
29
29
|
|
|
30
30
|
export class OpenAuthHandler {
|
|
31
|
-
#secret: Uint8Array;
|
|
32
|
-
#issuer: string;
|
|
31
|
+
readonly #secret: Uint8Array;
|
|
32
|
+
readonly #issuer: string;
|
|
33
33
|
/** Pending authorization codes. Maps code → { redirectUri, expiresAt }. */
|
|
34
|
-
#pendingCodes: Map<string, { redirectUri: string; expiresAt: number }>;
|
|
34
|
+
readonly #pendingCodes: Map<string, { redirectUri: string; expiresAt: number }>;
|
|
35
35
|
/** Registration token TTL in seconds. Default: 1 year. */
|
|
36
|
-
#tokenTtlSeconds: number;
|
|
36
|
+
readonly #tokenTtlSeconds: number;
|
|
37
37
|
/** Periodic cleanup timer for expired codes. */
|
|
38
|
-
#cleanupTimer: ReturnType<typeof setInterval>;
|
|
38
|
+
readonly #cleanupTimer: ReturnType<typeof setInterval>;
|
|
39
39
|
|
|
40
40
|
private constructor(secret: Uint8Array, issuer: string, tokenTtlSeconds: number) {
|
|
41
41
|
this.#secret = secret;
|
|
@@ -20,14 +20,14 @@ export class ProofOfWorkManager {
|
|
|
20
20
|
|
|
21
21
|
// There is opportunity to improve implementation here.
|
|
22
22
|
// TODO: https://github.com/enboxorg/enbox/issues/101
|
|
23
|
-
private proofOfWorkOfLastMinute: Map<string, number> = new Map(); // proofOfWorkId -> timestamp of proof-of-work
|
|
23
|
+
private readonly proofOfWorkOfLastMinute: Map<string, number> = new Map(); // proofOfWorkId -> timestamp of proof-of-work
|
|
24
24
|
|
|
25
25
|
// Seed to generate the challenge nonce from, this allows all DWN instances in a cluster to generate the same challenge.
|
|
26
|
-
private challengeSeed?: string;
|
|
27
|
-
private difficultyIncreaseMultiplier: number;
|
|
26
|
+
private readonly challengeSeed?: string;
|
|
27
|
+
private readonly difficultyIncreaseMultiplier: number;
|
|
28
28
|
private currentMaximumAllowedHashValueAsBigInt: bigint;
|
|
29
|
-
private initialMaximumAllowedHashValueAsBigInt: bigint;
|
|
30
|
-
private desiredSolveCountPerMinute: number;
|
|
29
|
+
private readonly initialMaximumAllowedHashValueAsBigInt: bigint;
|
|
30
|
+
private readonly desiredSolveCountPerMinute: number;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* How often the challenge nonce is refreshed.
|
|
@@ -183,17 +183,19 @@ export class ProofOfWorkManager {
|
|
|
183
183
|
} catch (error) {
|
|
184
184
|
console.error(`Encountered error while refreshing challenge nonce: ${error}`);
|
|
185
185
|
} finally {
|
|
186
|
-
setTimeout(
|
|
186
|
+
setTimeout(() => this.periodicallyRefreshChallengeNonce(), this.challengeRefreshFrequencyInSeconds * 1000);
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
-
private periodicallyRefreshProofOfWorkDifficulty (): void {
|
|
190
|
+
private async periodicallyRefreshProofOfWorkDifficulty (): Promise<void> {
|
|
191
191
|
try {
|
|
192
|
-
this.refreshMaximumAllowedHashValue();
|
|
192
|
+
await this.refreshMaximumAllowedHashValue();
|
|
193
193
|
} catch (error) {
|
|
194
194
|
console.error(`Encountered error while updating proof of work difficulty: ${error}`);
|
|
195
195
|
} finally {
|
|
196
|
-
setTimeout(
|
|
196
|
+
setTimeout(() => {
|
|
197
|
+
void this.periodicallyRefreshProofOfWorkDifficulty();
|
|
198
|
+
}, this.difficultyReevaluationFrequencyInSeconds * 1000);
|
|
197
199
|
}
|
|
198
200
|
}
|
|
199
201
|
|
|
@@ -208,7 +210,12 @@ export class ProofOfWorkManager {
|
|
|
208
210
|
|
|
209
211
|
private refreshChallengeNonce(): void {
|
|
210
212
|
// If challenge seed is supplied, use it to deterministically generate the challenge nonces.
|
|
211
|
-
if (this.challengeSeed
|
|
213
|
+
if (this.challengeSeed === undefined) {
|
|
214
|
+
const newChallengeNonce = ProofOfWork.generateNonce();
|
|
215
|
+
|
|
216
|
+
this.challengeNonces.previousChallengeNonce = this.challengeNonces.currentChallengeNonce;
|
|
217
|
+
this.challengeNonces.currentChallengeNonce = newChallengeNonce;
|
|
218
|
+
} else {
|
|
212
219
|
const currentRefreshIntervalId = Math.floor(Date.now() / (this.challengeRefreshFrequencyInSeconds * 1000));
|
|
213
220
|
const previousRefreshIntervalId = currentRefreshIntervalId - 1;
|
|
214
221
|
const nextRefreshIntervalId = currentRefreshIntervalId + 1;
|
|
@@ -218,11 +225,6 @@ export class ProofOfWorkManager {
|
|
|
218
225
|
const nextChallengeNonce = ProofOfWork.hashAsHexString([this.challengeSeed, nextRefreshIntervalId.toString(), this.challengeSeed]);
|
|
219
226
|
|
|
220
227
|
this.challengeNonces = { previousChallengeNonce, currentChallengeNonce, nextChallengeNonce };
|
|
221
|
-
} else {
|
|
222
|
-
const newChallengeNonce = ProofOfWork.generateNonce();
|
|
223
|
-
|
|
224
|
-
this.challengeNonces.previousChallengeNonce = this.challengeNonces.currentChallengeNonce;
|
|
225
|
-
this.challengeNonces.currentChallengeNonce = newChallengeNonce;
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
|
|
@@ -108,13 +108,13 @@ export class RegistrationManager implements TenantGate {
|
|
|
108
108
|
public async handleRegistrationRequest(registrationRequest: RegistrationRequest): Promise<void> {
|
|
109
109
|
if (registrationRequest.providerAuth?.registrationToken !== undefined) {
|
|
110
110
|
await this.handleProviderAuthRegistration(registrationRequest);
|
|
111
|
-
} else if (registrationRequest.proofOfWork
|
|
112
|
-
await this.handleProofOfWorkRegistration(registrationRequest);
|
|
113
|
-
} else {
|
|
111
|
+
} else if (registrationRequest.proofOfWork === undefined) {
|
|
114
112
|
throw new DwnServerError(
|
|
115
113
|
DwnServerErrorCode.RegistrationRequestMissingCredentials,
|
|
116
114
|
'Registration request must include either providerAuth or proofOfWork credentials.',
|
|
117
115
|
);
|
|
116
|
+
} else {
|
|
117
|
+
await this.handleProofOfWorkRegistration(registrationRequest);
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -154,9 +154,9 @@ export class RegistrationManager implements TenantGate {
|
|
|
154
154
|
termsOfServiceHash : registrationRequest.registrationData.termsOfServiceHash,
|
|
155
155
|
accountId : validationResult.accountId,
|
|
156
156
|
registrationType : 'provider-auth',
|
|
157
|
-
metadata : validationResult.metadata
|
|
158
|
-
?
|
|
159
|
-
:
|
|
157
|
+
metadata : validationResult.metadata === undefined
|
|
158
|
+
? undefined
|
|
159
|
+
: JSON.stringify(validationResult.metadata),
|
|
160
160
|
});
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -12,7 +12,7 @@ export class RegistrationStore {
|
|
|
12
12
|
private static readonly registeredTenantTableName = 'registeredTenants';
|
|
13
13
|
private static readonly tenantQuotasTableName = 'tenantQuotas';
|
|
14
14
|
|
|
15
|
-
private db: Kysely<RegistrationDatabase>;
|
|
15
|
+
private readonly db: Kysely<RegistrationDatabase>;
|
|
16
16
|
|
|
17
17
|
private constructor (sqlDialect: Dialect) {
|
|
18
18
|
this.db = new Kysely<RegistrationDatabase>({ dialect: sqlDialect });
|
|
@@ -11,7 +11,7 @@ import { Migrator } from 'kysely';
|
|
|
11
11
|
* dialect, producing the concrete Kysely {@link Migration} objects.
|
|
12
12
|
*/
|
|
13
13
|
class ServerMigrationProvider implements MigrationProvider {
|
|
14
|
-
#dialect: Dialect;
|
|
14
|
+
readonly #dialect: Dialect;
|
|
15
15
|
#factories: ReadonlyArray<readonly [name: string, factory: ServerMigrationFactory]>;
|
|
16
16
|
|
|
17
17
|
constructor(
|
package/src/ws-api.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { InMemoryConnectionManager } from './connection/connection-manager.js';
|
|
|
14
14
|
|
|
15
15
|
export class WsApi {
|
|
16
16
|
dwn: Dwn;
|
|
17
|
-
#connectionManager: ConnectionManager;
|
|
17
|
+
readonly #connectionManager: ConnectionManager;
|
|
18
18
|
|
|
19
19
|
constructor(
|
|
20
20
|
httpApi: HttpApi, dwn: Dwn, connectionManager?: ConnectionManager,
|