@matterbridge/core 3.7.2-dev-20260330-bb55c39 → 3.7.2-dev-20260331-ac050d8

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.
@@ -1,6 +1,7 @@
1
1
  import { DoorLockServer } from '@matter/node/behaviors/door-lock';
2
2
  import { DoorLock } from '@matter/types/clusters/door-lock';
3
- declare const MatterbridgeDoorLockServer_base: import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterTypeModifier.WithAlterations<DoorLock.Cluster, import("@matter/types").ClusterTypeModifier.ElementFlagAlterations<{
3
+ import { FabricIndex } from '@matter/types/datatype';
4
+ declare const MatterbridgeDoorLockServer_base: import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterTypeModifier.WithAlterations<import("@matter/types").ClusterComposer.WithFeatures<DoorLock.Cluster, readonly [DoorLock.Feature.User, DoorLock.Feature.PinCredential]>, import("@matter/types").ClusterTypeModifier.ElementFlagAlterations<{
4
5
  readonly events: {
5
6
  readonly doorLockAlarm: true;
6
7
  readonly lockOperation: true;
@@ -11,18 +12,51 @@ declare const MatterbridgeDoorLockServer_base: import("@matter/node").ClusterBeh
11
12
  readonly unlockDoor: true;
12
13
  readonly unlockWithTimeout: true;
13
14
  };
14
- }>>, typeof DoorLockServer, import("@matter/node/behaviors/door-lock").DoorLockInterface>;
15
+ }>>, import("@matter/node").ClusterBehavior.Type<import("@matter/types").ClusterComposer.WithFeatures<DoorLock.Cluster, readonly [DoorLock.Feature.User, DoorLock.Feature.PinCredential]>, typeof DoorLockServer, import("@matter/node/behaviors/door-lock").DoorLockInterface>, import("@matter/node/behaviors/door-lock").DoorLockInterface>;
15
16
  export declare class MatterbridgeDoorLockServer extends MatterbridgeDoorLockServer_base {
16
17
  protected internal: MatterbridgeDoorLockServer.Internal;
17
18
  initialize(): Promise<void>;
18
19
  lockDoor(request: DoorLock.LockDoorRequest): Promise<void>;
19
20
  unlockDoor(request: DoorLock.UnlockDoorRequest): Promise<void>;
20
21
  unlockWithTimeout(request: DoorLock.UnlockWithTimeoutRequest): Promise<void>;
22
+ setUser(request: DoorLock.SetUserRequest): Promise<void>;
23
+ getUser(request: DoorLock.GetUserRequest): Promise<DoorLock.GetUserResponse>;
24
+ clearUser(request: DoorLock.ClearUserRequest): Promise<void>;
25
+ setCredential(request: DoorLock.SetCredentialRequest): Promise<DoorLock.SetCredentialResponse>;
26
+ getCredentialStatus(request: DoorLock.GetCredentialStatusRequest): Promise<DoorLock.GetCredentialStatusResponse>;
27
+ clearCredential(request: DoorLock.ClearCredentialRequest): Promise<void>;
28
+ private validateUserIndex;
29
+ private getNextUserIndex;
30
+ private getAccessingFabricIndex;
31
+ private getAccessingNodeId;
32
+ private getOperationSource;
33
+ private getLockDataTypeForCredentialType;
34
+ private getCredentialDataIndex;
35
+ private getStoredCredentialTypes;
36
+ private findStoredCredential;
37
+ private getNextOccupiedCredentialIndex;
21
38
  }
22
39
  export declare namespace MatterbridgeDoorLockServer {
40
+ type StoredCredential = DoorLock.Credential & {
41
+ creatorFabricIndex: FabricIndex | null;
42
+ lastModifiedFabricIndex: FabricIndex | null;
43
+ credentialData: Uint8Array;
44
+ };
45
+ type StoredUser = {
46
+ userIndex: number;
47
+ userName: string | null;
48
+ userUniqueId: number | null;
49
+ userStatus: DoorLock.UserStatus | null;
50
+ userType: DoorLock.UserType | null;
51
+ credentialRule: DoorLock.CredentialRule | null;
52
+ credentials: StoredCredential[] | null;
53
+ creatorFabricIndex: FabricIndex | null;
54
+ lastModifiedFabricIndex: FabricIndex | null;
55
+ };
23
56
  class Internal {
24
57
  enableTimeout: boolean;
25
58
  unlockTimeout: NodeJS.Timeout | undefined;
59
+ users: StoredUser[];
26
60
  }
27
61
  }
28
62
  export {};
@@ -1,7 +1,13 @@
1
1
  import { DoorLockServer } from '@matter/node/behaviors/door-lock';
2
+ import { hasRemoteActor } from '@matter/protocol';
2
3
  import { DoorLock } from '@matter/types/clusters/door-lock';
4
+ import { StatusResponse } from '@matter/types/common';
5
+ import { FabricIndex } from '@matter/types/datatype';
6
+ import { Status } from '@matter/types/globals';
7
+ import { getEnumDescription } from '@matterbridge/utils/enum';
8
+ import { debugStringify } from 'node-ansi-logger';
3
9
  import { MatterbridgeServer } from './matterbridgeServer.js';
4
- export class MatterbridgeDoorLockServer extends DoorLockServer.enable({
10
+ export class MatterbridgeDoorLockServer extends DoorLockServer.with(DoorLock.Feature.User, DoorLock.Feature.PinCredential).enable({
5
11
  events: { doorLockAlarm: true, lockOperation: true, lockOperationError: true },
6
12
  commands: { lockDoor: true, unlockDoor: true, unlockWithTimeout: true },
7
13
  }) {
@@ -43,7 +49,7 @@ export class MatterbridgeDoorLockServer extends DoorLockServer.enable({
43
49
  endpoint: this.endpoint,
44
50
  context: this.context,
45
51
  });
46
- device.log.debug(`MatterbridgeDoorLockServer: unlockDoor called`);
52
+ device.log.debug(`MatterbridgeDoorLockServer: unlockDoor called ${this.state.autoRelockTime ? 'with ' + this.state.autoRelockTime + ' seconds' : 'without'} autoRelockTime ${this.internal.enableTimeout ? 'with' : 'without'} enableTimeout`);
47
53
  await super.unlockDoor(request);
48
54
  if (!this.internal.enableTimeout)
49
55
  return;
@@ -52,9 +58,9 @@ export class MatterbridgeDoorLockServer extends DoorLockServer.enable({
52
58
  this.internal.unlockTimeout = setTimeout(async () => {
53
59
  this.internal.unlockTimeout = undefined;
54
60
  const device = this.endpoint.stateOf(MatterbridgeServer);
55
- const state = this.endpoint.stateOf(MatterbridgeDoorLockServer);
61
+ const state = this.endpoint.stateOf(MatterbridgeDoorLockServer.with());
56
62
  device.log.info(`Auto-relocking door after ${state.autoRelockTime} seconds (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
57
- await this.endpoint.act((agent) => agent.get(MatterbridgeDoorLockServer).lockDoor({ pinCode: request.pinCode }));
63
+ await this.endpoint.act((agent) => agent.get(MatterbridgeDoorLockServer.with()).lockDoor({ pinCode: request.pinCode }));
58
64
  }, this.state.autoRelockTime * 1000).unref();
59
65
  }
60
66
  }
@@ -73,7 +79,7 @@ export class MatterbridgeDoorLockServer extends DoorLockServer.enable({
73
79
  endpoint: this.endpoint,
74
80
  context: this.context,
75
81
  });
76
- device.log.debug(`MatterbridgeDoorLockServer: unlockWithTimeout called`);
82
+ device.log.debug(`MatterbridgeDoorLockServer: unlockWithTimeout called ${this.internal.enableTimeout ? 'with' : 'without'} enableTimeout`);
77
83
  this.state.lockState = DoorLock.LockState.Unlocked;
78
84
  if (!this.internal.enableTimeout)
79
85
  return;
@@ -83,15 +89,355 @@ export class MatterbridgeDoorLockServer extends DoorLockServer.enable({
83
89
  this.internal.unlockTimeout = undefined;
84
90
  const device = this.endpoint.stateOf(MatterbridgeServer);
85
91
  device.log.info(`Locking door after ${request.timeout} seconds (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
86
- await this.endpoint.act((agent) => agent.get(MatterbridgeDoorLockServer).lockDoor({ pinCode: request.pinCode }));
92
+ await this.endpoint.act((agent) => agent.get(MatterbridgeDoorLockServer.with()).lockDoor({ pinCode: request.pinCode }));
87
93
  }, request.timeout * 1000).unref();
88
94
  }
89
95
  }
96
+ async setUser(request) {
97
+ const device = this.endpoint.stateOf(MatterbridgeServer);
98
+ const accessingFabricIndex = this.getAccessingFabricIndex();
99
+ device.log.info(`Setting user operationType ${getEnumDescription(DoorLock.DataOperationType, request.operationType)} userIndex ${request.userIndex} userName ${request.userName ?? 'null'} userUniqueId ${request.userUniqueId ?? 'null'} userStatus ${getEnumDescription(DoorLock.UserStatus, request.userStatus, { fallback: 'null' })} userType ${getEnumDescription(DoorLock.UserType, request.userType, { fallback: 'null' })} credentialRule ${getEnumDescription(DoorLock.CredentialRule, request.credentialRule, { fallback: 'null' })} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
100
+ device.log.debug(`MatterbridgeDoorLockServer: setUser accessingFabricIndex ${accessingFabricIndex ?? 'null'}`);
101
+ await device.commandHandler.executeHandler('DoorLock.setUser', {
102
+ command: 'setUser',
103
+ request,
104
+ cluster: DoorLockServer.id,
105
+ attributes: this.state,
106
+ endpoint: this.endpoint,
107
+ context: this.context,
108
+ });
109
+ const user = this.internal.users.find((storedUser) => storedUser.userIndex === request.userIndex);
110
+ device.log.debug(`MatterbridgeDoorLockServer: setUser called for userIndex ${request.userIndex} (${user ? 'existing user ' + debugStringify(user) : 'new user'})`);
111
+ if (!user && request.operationType === DoorLock.DataOperationType.Add) {
112
+ this.internal.users.push({
113
+ userIndex: request.userIndex,
114
+ userName: request.userName ?? '',
115
+ userUniqueId: request.userUniqueId ?? 0xffffffff,
116
+ userStatus: request.userStatus ?? DoorLock.UserStatus.OccupiedEnabled,
117
+ userType: request.userType ?? DoorLock.UserType.UnrestrictedUser,
118
+ credentialRule: request.credentialRule ?? DoorLock.CredentialRule.Single,
119
+ credentials: [],
120
+ creatorFabricIndex: accessingFabricIndex,
121
+ lastModifiedFabricIndex: accessingFabricIndex,
122
+ });
123
+ this.events.lockUserChange.emit({
124
+ lockDataType: DoorLock.LockDataType.UserIndex,
125
+ dataOperationType: DoorLock.DataOperationType.Add,
126
+ operationSource: this.getOperationSource(),
127
+ userIndex: request.userIndex,
128
+ fabricIndex: accessingFabricIndex,
129
+ sourceNode: this.getAccessingNodeId(),
130
+ dataIndex: request.userIndex,
131
+ }, this.context);
132
+ device.log.debug(`MatterbridgeDoorLockServer: added userIndex ${request.userIndex} (total users: ${this.internal.users.length}) to internal state: ${debugStringify(this.internal.users.find((storedUser) => storedUser.userIndex === request.userIndex))}`);
133
+ }
134
+ else if (user && request.operationType === DoorLock.DataOperationType.Modify) {
135
+ user.userName = request.userName ?? user.userName;
136
+ user.userUniqueId = request.userUniqueId ?? user.userUniqueId;
137
+ user.userStatus = request.userStatus ?? user.userStatus;
138
+ user.userType = request.userType ?? user.userType;
139
+ user.credentialRule = request.credentialRule ?? user.credentialRule;
140
+ user.lastModifiedFabricIndex = accessingFabricIndex;
141
+ this.events.lockUserChange.emit({
142
+ lockDataType: DoorLock.LockDataType.UserIndex,
143
+ dataOperationType: DoorLock.DataOperationType.Modify,
144
+ operationSource: this.getOperationSource(),
145
+ userIndex: request.userIndex,
146
+ fabricIndex: accessingFabricIndex,
147
+ sourceNode: this.getAccessingNodeId(),
148
+ dataIndex: request.userIndex,
149
+ }, this.context);
150
+ device.log.debug(`MatterbridgeDoorLockServer: modified userIndex ${request.userIndex} (total users: ${this.internal.users.length}) in internal state: ${debugStringify(user)}`);
151
+ }
152
+ }
153
+ async getUser(request) {
154
+ const device = this.endpoint.stateOf(MatterbridgeServer);
155
+ device.log.info(`Getting userIndex ${request.userIndex} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
156
+ const response = await device.commandHandler.executeHandler('DoorLock.getUser', {
157
+ command: 'getUser',
158
+ request,
159
+ cluster: DoorLockServer.id,
160
+ attributes: this.state,
161
+ endpoint: this.endpoint,
162
+ context: this.context,
163
+ });
164
+ if (response !== undefined) {
165
+ return response;
166
+ }
167
+ device.log.debug(`MatterbridgeDoorLockServer: getUser called for userIndex ${request.userIndex}`);
168
+ if (!this.validateUserIndex(request.userIndex))
169
+ throw new StatusResponse.InvalidCommandError('Invalid userIndex in GetUser request');
170
+ const user = this.internal.users.find((storedUser) => storedUser.userIndex === request.userIndex);
171
+ if (!user) {
172
+ return {
173
+ userIndex: request.userIndex,
174
+ userName: null,
175
+ userUniqueId: null,
176
+ userStatus: null,
177
+ userType: null,
178
+ credentialRule: null,
179
+ credentials: null,
180
+ creatorFabricIndex: null,
181
+ lastModifiedFabricIndex: null,
182
+ nextUserIndex: null,
183
+ };
184
+ }
185
+ return {
186
+ ...user,
187
+ credentials: user.credentials?.map(({ credentialType, credentialIndex }) => ({ credentialType, credentialIndex })) ?? null,
188
+ nextUserIndex: this.getNextUserIndex(request.userIndex),
189
+ };
190
+ }
191
+ async clearUser(request) {
192
+ const device = this.endpoint.stateOf(MatterbridgeServer);
193
+ const accessingFabricIndex = this.getAccessingFabricIndex();
194
+ device.log.info(`Clearing userIndex ${request.userIndex} ${request.userIndex === 0xfffe ? '(all users)' : ''} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
195
+ await device.commandHandler.executeHandler('DoorLock.clearUser', {
196
+ command: 'clearUser',
197
+ request,
198
+ cluster: DoorLockServer.id,
199
+ attributes: this.state,
200
+ endpoint: this.endpoint,
201
+ context: this.context,
202
+ });
203
+ device.log.debug(`MatterbridgeDoorLockServer: clearUser called for userIndex ${request.userIndex}`);
204
+ if (request.userIndex != 0xfffe && !this.validateUserIndex(request.userIndex))
205
+ throw new StatusResponse.InvalidCommandError('Invalid userIndex in ClearUser request');
206
+ if (request.userIndex === 0xfffe) {
207
+ this.internal.users = [];
208
+ }
209
+ else {
210
+ this.internal.users = this.internal.users.filter((storedUser) => storedUser.userIndex !== request.userIndex);
211
+ }
212
+ this.events.lockUserChange.emit({
213
+ lockDataType: DoorLock.LockDataType.UserIndex,
214
+ dataOperationType: DoorLock.DataOperationType.Clear,
215
+ operationSource: this.getOperationSource(),
216
+ userIndex: request.userIndex,
217
+ fabricIndex: accessingFabricIndex,
218
+ sourceNode: this.getAccessingNodeId(),
219
+ dataIndex: request.userIndex,
220
+ }, this.context);
221
+ }
222
+ async setCredential(request) {
223
+ const device = this.endpoint.stateOf(MatterbridgeServer);
224
+ const accessingFabricIndex = this.getAccessingFabricIndex();
225
+ device.log.info(`Setting credential operationType ${getEnumDescription(DoorLock.DataOperationType, request.operationType)} credentialType ${getEnumDescription(DoorLock.CredentialType, request.credential.credentialType)} credentialIndex ${request.credential.credentialIndex} credentialData ${Buffer.from(request.credentialData).toString('hex') ? '0x' + Buffer.from(request.credentialData).toString('hex') : '0x'} userIndex ${request.userIndex ?? 'null'} userStatus ${getEnumDescription(DoorLock.UserStatus, request.userStatus, { fallback: 'null' })} userType ${getEnumDescription(DoorLock.UserType, request.userType, { fallback: 'null' })} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
226
+ await device.commandHandler.executeHandler('DoorLock.setCredential', {
227
+ command: 'setCredential',
228
+ request,
229
+ cluster: DoorLockServer.id,
230
+ attributes: this.state,
231
+ endpoint: this.endpoint,
232
+ context: this.context,
233
+ });
234
+ const user = this.internal.users.find((storedUser) => storedUser.userIndex === request.userIndex);
235
+ const existingCredential = this.findStoredCredential(request.credential);
236
+ device.log.debug(`MatterbridgeDoorLockServer: setCredential pre-update lookup for credentialIndex ${request.credential.credentialIndex} (${existingCredential ? 'existing credential found' : 'no existing credential'})`);
237
+ if (user && (request.operationType === DoorLock.DataOperationType.Add || request.operationType === DoorLock.DataOperationType.Modify)) {
238
+ const credential = user.credentials?.find((storedCredential) => storedCredential.credentialType === request.credential.credentialType && storedCredential.credentialIndex === request.credential.credentialIndex);
239
+ if (credential) {
240
+ credential.credentialData = request.credentialData;
241
+ credential.creatorFabricIndex = credential.creatorFabricIndex ?? accessingFabricIndex;
242
+ credential.lastModifiedFabricIndex = accessingFabricIndex;
243
+ device.log.debug(`MatterbridgeDoorLockServer: modified credentialIndex ${request.credential.credentialIndex} for userIndex ${request.userIndex} in internal state: ${debugStringify(user)}`);
244
+ }
245
+ else {
246
+ user.credentials = user.credentials ?? [];
247
+ user.credentials.push({
248
+ credentialType: request.credential.credentialType,
249
+ credentialIndex: request.credential.credentialIndex,
250
+ credentialData: request.credentialData,
251
+ creatorFabricIndex: accessingFabricIndex,
252
+ lastModifiedFabricIndex: accessingFabricIndex,
253
+ });
254
+ device.log.debug(`MatterbridgeDoorLockServer: added credentialIndex ${request.credential.credentialIndex} for userIndex ${request.userIndex} to internal state: ${debugStringify(user)}`);
255
+ }
256
+ this.events.lockUserChange.emit({
257
+ lockDataType: this.getLockDataTypeForCredentialType(request.credential.credentialType),
258
+ dataOperationType: request.operationType,
259
+ operationSource: this.getOperationSource(),
260
+ userIndex: request.userIndex,
261
+ fabricIndex: accessingFabricIndex,
262
+ sourceNode: this.getAccessingNodeId(),
263
+ dataIndex: this.getCredentialDataIndex(request.credential),
264
+ }, this.context);
265
+ }
266
+ else {
267
+ device.log.debug(`MatterbridgeDoorLockServer: setCredential did not update internal state for credentialIndex ${request.credential.credentialIndex} (user ${request.userIndex ?? 'null'} not found or operation not handled)`);
268
+ }
269
+ return {
270
+ status: Status.Success,
271
+ userIndex: request.userIndex,
272
+ };
273
+ }
274
+ async getCredentialStatus(request) {
275
+ const device = this.endpoint.stateOf(MatterbridgeServer);
276
+ device.log.info(`Getting credential status for credentialType ${getEnumDescription(DoorLock.CredentialType, request.credential.credentialType)} credentialIndex ${request.credential.credentialIndex} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
277
+ await device.commandHandler.executeHandler('DoorLock.getCredentialStatus', {
278
+ command: 'getCredentialStatus',
279
+ request,
280
+ cluster: DoorLockServer.id,
281
+ attributes: this.state,
282
+ endpoint: this.endpoint,
283
+ context: this.context,
284
+ });
285
+ device.log.debug(`MatterbridgeDoorLockServer: getCredentialStatus called`);
286
+ const credentialRecord = this.findStoredCredential(request.credential);
287
+ const nextCredentialIndex = this.getNextOccupiedCredentialIndex(request.credential);
288
+ const response = {
289
+ credentialExists: credentialRecord !== null,
290
+ userIndex: credentialRecord?.user.userIndex ?? null,
291
+ creatorFabricIndex: credentialRecord?.storedCredential.creatorFabricIndex ?? null,
292
+ lastModifiedFabricIndex: credentialRecord?.storedCredential.lastModifiedFabricIndex ?? null,
293
+ nextCredentialIndex,
294
+ };
295
+ device.log.debug(`MatterbridgeDoorLockServer: getCredentialStatus result ${debugStringify(response)}`);
296
+ return response;
297
+ }
298
+ async clearCredential(request) {
299
+ const device = this.endpoint.stateOf(MatterbridgeServer);
300
+ const accessingFabricIndex = this.getAccessingFabricIndex();
301
+ const clearedCredentialUserIndex = request.credential !== null && request.credential.credentialIndex !== 0xfffe ? (this.findStoredCredential(request.credential)?.user.userIndex ?? null) : null;
302
+ const credentialTypesToClear = request.credential !== null ? [request.credential.credentialType] : this.getStoredCredentialTypes();
303
+ device.log.info(`Clearing credentialType ${request.credential ? getEnumDescription(DoorLock.CredentialType, request.credential.credentialType) : 'null'} credentialIndex ${request.credential ? request.credential.credentialIndex : 'null'} ${request.credential === null ? '(all credentials)' : ''} (endpoint ${this.endpoint.maybeId}.${this.endpoint.maybeNumber})`);
304
+ await device.commandHandler.executeHandler('DoorLock.clearCredential', {
305
+ command: 'clearCredential',
306
+ request,
307
+ cluster: DoorLockServer.id,
308
+ attributes: this.state,
309
+ endpoint: this.endpoint,
310
+ context: this.context,
311
+ });
312
+ device.log.debug('MatterbridgeDoorLockServer: clearCredential called');
313
+ for (const user of this.internal.users) {
314
+ user.credentials =
315
+ user.credentials?.filter((storedCredential) => {
316
+ if (request.credential === null) {
317
+ return false;
318
+ }
319
+ if (storedCredential.credentialType !== request.credential.credentialType) {
320
+ return true;
321
+ }
322
+ if (request.credential.credentialIndex === 0xfffe) {
323
+ return false;
324
+ }
325
+ return storedCredential.credentialIndex !== request.credential.credentialIndex;
326
+ }) ?? user.credentials;
327
+ }
328
+ for (const credentialType of credentialTypesToClear) {
329
+ this.events.lockUserChange.emit({
330
+ lockDataType: this.getLockDataTypeForCredentialType(credentialType),
331
+ dataOperationType: DoorLock.DataOperationType.Clear,
332
+ operationSource: this.getOperationSource(),
333
+ userIndex: request.credential === null || request.credential.credentialIndex === 0xfffe ? null : clearedCredentialUserIndex,
334
+ fabricIndex: accessingFabricIndex,
335
+ sourceNode: this.getAccessingNodeId(),
336
+ dataIndex: request.credential === null ? 0xfffe : this.getCredentialDataIndex(request.credential),
337
+ }, this.context);
338
+ }
339
+ }
340
+ validateUserIndex(userIndex) {
341
+ if (userIndex < 1 || userIndex > this.state.numberOfTotalUsersSupported) {
342
+ return false;
343
+ }
344
+ return true;
345
+ }
346
+ getNextUserIndex(userIndex) {
347
+ const nextUser = this.internal.users.find((storedUser) => storedUser.userIndex === userIndex + 1);
348
+ if (nextUser && nextUser.userStatus !== DoorLock.UserStatus.Available) {
349
+ return nextUser.userIndex;
350
+ }
351
+ return null;
352
+ }
353
+ getAccessingFabricIndex() {
354
+ let fabricIndex;
355
+ try {
356
+ fabricIndex = this.context.fabric;
357
+ }
358
+ catch {
359
+ return null;
360
+ }
361
+ if (fabricIndex === undefined || fabricIndex === FabricIndex.NO_FABRIC) {
362
+ return null;
363
+ }
364
+ return fabricIndex;
365
+ }
366
+ getAccessingNodeId() {
367
+ if (!hasRemoteActor(this.context)) {
368
+ return null;
369
+ }
370
+ return this.context.session.peerNodeId ?? null;
371
+ }
372
+ getOperationSource() {
373
+ return this.getAccessingNodeId() !== null ? DoorLock.OperationSource.Remote : DoorLock.OperationSource.Unspecified;
374
+ }
375
+ getLockDataTypeForCredentialType(credentialType) {
376
+ switch (credentialType) {
377
+ case DoorLock.CredentialType.ProgrammingPin:
378
+ return DoorLock.LockDataType.ProgrammingCode;
379
+ case DoorLock.CredentialType.Pin:
380
+ return DoorLock.LockDataType.Pin;
381
+ case DoorLock.CredentialType.Rfid:
382
+ return DoorLock.LockDataType.Rfid;
383
+ case DoorLock.CredentialType.Fingerprint:
384
+ return DoorLock.LockDataType.Fingerprint;
385
+ case DoorLock.CredentialType.FingerVein:
386
+ return DoorLock.LockDataType.FingerVein;
387
+ case DoorLock.CredentialType.Face:
388
+ return DoorLock.LockDataType.Face;
389
+ case DoorLock.CredentialType.AliroCredentialIssuerKey:
390
+ return DoorLock.LockDataType.AliroCredentialIssuerKey;
391
+ case DoorLock.CredentialType.AliroEvictableEndpointKey:
392
+ return DoorLock.LockDataType.AliroEvictableEndpointKey;
393
+ case DoorLock.CredentialType.AliroNonEvictableEndpointKey:
394
+ return DoorLock.LockDataType.AliroNonEvictableEndpointKey;
395
+ default:
396
+ return DoorLock.LockDataType.Unspecified;
397
+ }
398
+ }
399
+ getCredentialDataIndex(credential) {
400
+ if (credential.credentialType === DoorLock.CredentialType.ProgrammingPin) {
401
+ return null;
402
+ }
403
+ return credential.credentialIndex;
404
+ }
405
+ getStoredCredentialTypes() {
406
+ const credentialTypes = new Set();
407
+ for (const user of this.internal.users) {
408
+ for (const credential of user.credentials ?? []) {
409
+ credentialTypes.add(credential.credentialType);
410
+ }
411
+ }
412
+ return [...credentialTypes];
413
+ }
414
+ findStoredCredential(credential) {
415
+ for (const user of this.internal.users) {
416
+ for (const storedCredential of user.credentials ?? []) {
417
+ if (storedCredential.credentialType === credential.credentialType && storedCredential.credentialIndex === credential.credentialIndex) {
418
+ return { user, storedCredential };
419
+ }
420
+ }
421
+ }
422
+ return null;
423
+ }
424
+ getNextOccupiedCredentialIndex(credential) {
425
+ for (const user of this.internal.users) {
426
+ for (const storedCredential of user.credentials ?? []) {
427
+ if (storedCredential.credentialType !== credential.credentialType || storedCredential.credentialIndex <= credential.credentialIndex) {
428
+ continue;
429
+ }
430
+ return storedCredential.credentialIndex;
431
+ }
432
+ }
433
+ return null;
434
+ }
90
435
  }
91
436
  (function (MatterbridgeDoorLockServer) {
92
437
  class Internal {
93
438
  enableTimeout = true;
94
439
  unlockTimeout;
440
+ users = [];
95
441
  }
96
442
  MatterbridgeDoorLockServer.Internal = Internal;
97
443
  })(MatterbridgeDoorLockServer || (MatterbridgeDoorLockServer = {}));
@@ -13,12 +13,10 @@ export * from './matterbridgeServer.js';
13
13
  export * from './modeSelectServer.js';
14
14
  export * from './onOffServer.js';
15
15
  export * from './operationalStateServer.js';
16
- export * from './pinDoorLockServer.js';
17
16
  export * from './powerSourceServer.js';
18
17
  export * from './serviceAreaServer.js';
19
18
  export * from './smokeCoAlarmServer.js';
20
19
  export * from './switchServer.js';
21
20
  export * from './thermostatServer.js';
22
- export * from './userPinDoorLockServer.js';
23
21
  export * from './valveConfigurationAndControlServer.js';
24
22
  export * from './windowCoveringServer.js';
@@ -13,12 +13,10 @@ export * from './matterbridgeServer.js';
13
13
  export * from './modeSelectServer.js';
14
14
  export * from './onOffServer.js';
15
15
  export * from './operationalStateServer.js';
16
- export * from './pinDoorLockServer.js';
17
16
  export * from './powerSourceServer.js';
18
17
  export * from './serviceAreaServer.js';
19
18
  export * from './smokeCoAlarmServer.js';
20
19
  export * from './switchServer.js';
21
20
  export * from './thermostatServer.js';
22
- export * from './userPinDoorLockServer.js';
23
21
  export * from './valveConfigurationAndControlServer.js';
24
22
  export * from './windowCoveringServer.js';
package/dist/export.d.ts CHANGED
@@ -13,13 +13,11 @@ export * from './behaviors/matterbridgeServer.js';
13
13
  export * from './behaviors/modeSelectServer.js';
14
14
  export * from './behaviors/onOffServer.js';
15
15
  export * from './behaviors/operationalStateServer.js';
16
- export * from './behaviors/pinDoorLockServer.js';
17
16
  export * from './behaviors/powerSourceServer.js';
18
17
  export * from './behaviors/serviceAreaServer.js';
19
18
  export * from './behaviors/smokeCoAlarmServer.js';
20
19
  export * from './behaviors/switchServer.js';
21
20
  export * from './behaviors/thermostatServer.js';
22
- export * from './behaviors/userPinDoorLockServer.js';
23
21
  export * from './behaviors/valveConfigurationAndControlServer.js';
24
22
  export * from './behaviors/windowCoveringServer.js';
25
23
  export { addVirtualDevice } from './helpers.js';
package/dist/export.js CHANGED
@@ -15,13 +15,11 @@ export * from './behaviors/matterbridgeServer.js';
15
15
  export * from './behaviors/modeSelectServer.js';
16
16
  export * from './behaviors/onOffServer.js';
17
17
  export * from './behaviors/operationalStateServer.js';
18
- export * from './behaviors/pinDoorLockServer.js';
19
18
  export * from './behaviors/powerSourceServer.js';
20
19
  export * from './behaviors/serviceAreaServer.js';
21
20
  export * from './behaviors/smokeCoAlarmServer.js';
22
21
  export * from './behaviors/switchServer.js';
23
22
  export * from './behaviors/thermostatServer.js';
24
- export * from './behaviors/userPinDoorLockServer.js';
25
23
  export * from './behaviors/valveConfigurationAndControlServer.js';
26
24
  export * from './behaviors/windowCoveringServer.js';
27
25
  export { addVirtualDevice } from './helpers.js';
package/dist/frontend.js CHANGED
@@ -486,7 +486,7 @@ export class Frontend extends EventEmitter {
486
486
  };
487
487
  res.status(200).json(memoryReport);
488
488
  });
489
- this.expressApp.get('/api/settings', express.json(), async (req, res) => {
489
+ this.expressApp.get('/api/settings', async (req, res) => {
490
490
  this.log.debug('The frontend sent /api/settings');
491
491
  if (!this.validateReq(req, res))
492
492
  return;
@@ -863,10 +863,10 @@ export class Frontend extends EventEmitter {
863
863
  matterbridgeLatestVersion: this.matterbridge.matterbridgeLatestVersion,
864
864
  matterbridgeDevVersion: this.matterbridge.matterbridgeDevVersion,
865
865
  frontendVersion: this.matterbridge.frontendVersion,
866
- dockerDev: undefined,
867
- dockerVersion: undefined,
868
- dockerLatestVersion: undefined,
869
- dockerDevVersion: undefined,
866
+ dockerDev: this.matterbridge.dockerDev,
867
+ dockerVersion: this.matterbridge.dockerVersion,
868
+ dockerLatestVersion: this.matterbridge.dockerLatestVersion,
869
+ dockerDevVersion: this.matterbridge.dockerDevVersion,
870
870
  bridgeMode: this.matterbridge.bridgeMode,
871
871
  restartMode: this.matterbridge.restartMode,
872
872
  virtualMode: this.matterbridge.virtualMode,
@@ -2119,7 +2119,7 @@ export class Frontend extends EventEmitter {
2119
2119
  if (!this.listening || this.webSocketServer?.clients.size === 0)
2120
2120
  return;
2121
2121
  const stringifiedMsg = JSON.stringify(msg);
2122
- if (msg.method !== 'log')
2122
+ if (!['log', 'cpu_update', 'memory_update', 'uptime_update'].includes(msg.method))
2123
2123
  this.log.debug(`Sending a broadcast message: ${debugStringify(msg)}`);
2124
2124
  this.webSocketServer?.clients.forEach((client) => {
2125
2125
  if (client.readyState === client.OPEN) {
package/dist/helpers.js CHANGED
@@ -74,11 +74,6 @@ export async function addVirtualDevice(aggregatorEndpoint, name, type, callback)
74
74
  }
75
75
  export async function addVirtualDevices(matterbridge, aggregatorEndpoint) {
76
76
  if (hasParameter('experimental') && matterbridge.bridgeMode === 'bridge' && aggregatorEndpoint) {
77
- const lockPin = new MatterbridgeEndpoint(doorLockDevice, { id: 'door_lock_pin' });
78
- lockPin.createDefaultBridgedDeviceBasicInformationClusterServer('Matterbridge Pin Lock', 'sn_system_lock', 0xfff1, 'Matterbridge', 'Matterbridge Virtual Device', 20000, '2.0.0');
79
- lockPin.createPinDoorLockClusterServer();
80
- lockPin.addRequiredClusterServers();
81
- await aggregatorEndpoint.add(lockPin);
82
77
  const lockUserPin = new MatterbridgeEndpoint(doorLockDevice, { id: 'door_lock_user_pin' });
83
78
  lockUserPin.createDefaultBridgedDeviceBasicInformationClusterServer('Matterbridge User Pin Lock', 'sn_system_lock', 0xfff1, 'Matterbridge', 'Matterbridge Virtual Device', 20000, '2.0.0');
84
79
  lockUserPin.createUserPinDoorLockClusterServer();
@@ -280,8 +280,16 @@ export class MatterNode extends EventEmitter {
280
280
  this.matterLog.logFilePath = path.join(this.matterbridge.matterbridgeDirectory, MATTER_LOGGER_FILE);
281
281
  }
282
282
  return (text, message) => {
283
- const logger = text.slice(44, 44 + 20).trim();
284
- const msg = text.slice(65);
283
+ let logger;
284
+ let msg;
285
+ if (!hasParameter('no-ansi') && process.env.NO_ANSI !== 'true') {
286
+ logger = text.slice(44, 44 + 20).trim();
287
+ msg = text.slice(65);
288
+ }
289
+ else {
290
+ logger = text.slice(30).trim().split(/\s+/, 1)[0];
291
+ msg = text.slice(30).trim().slice(logger.length).trimStart();
292
+ }
285
293
  this.matterLog.logName = logger;
286
294
  this.matterLog.log(MatterLogLevel.names[message.level], msg);
287
295
  };
@@ -1053,8 +1053,16 @@ export class Matterbridge extends EventEmitter {
1053
1053
  this.matterLog.logFilePath = path.join(this.matterbridgeDirectory, MATTER_LOGGER_FILE);
1054
1054
  }
1055
1055
  return (text, message) => {
1056
- const logger = text.slice(44, 44 + 20).trim();
1057
- const msg = text.slice(65);
1056
+ let logger;
1057
+ let msg;
1058
+ if (!hasParameter('no-ansi') && process.env.NO_ANSI !== 'true') {
1059
+ logger = text.slice(44, 44 + 20).trim();
1060
+ msg = text.slice(65);
1061
+ }
1062
+ else {
1063
+ logger = text.slice(30).trim().split(/\s+/, 1)[0];
1064
+ msg = text.slice(30).trim().slice(logger.length).trimStart();
1065
+ }
1058
1066
  this.matterLog.logName = logger;
1059
1067
  switch (message.level) {
1060
1068
  case MatterLogLevel.DEBUG:
@@ -24,7 +24,7 @@ import { ClusterId, EndpointNumber } from '@matter/types/datatype';
24
24
  import { Semtag } from '@matter/types/globals';
25
25
  import { AnsiLogger, LogLevel } from 'node-ansi-logger';
26
26
  import { DeviceTypeDefinition } from './matterbridgeDeviceTypes.js';
27
- import { CommandHandler, CommandHandlerData, CommandHandlerFunction, CommandHandlers } from './matterbridgeEndpointCommandHandler.js';
27
+ import { CommandHandler, CommandHandlerData, CommandHandlerExecutionResult, CommandHandlerFunction, CommandHandlers } from './matterbridgeEndpointCommandHandler.js';
28
28
  import { MatterbridgeEndpointOptions, SerializedMatterbridgeEndpoint } from './matterbridgeEndpointTypes.js';
29
29
  type BehaviorCommandName<T extends Behavior.Type> = {
30
30
  [K in keyof CommandsOfBehavior<T>]: K;
@@ -101,7 +101,7 @@ export declare class MatterbridgeEndpoint extends Endpoint {
101
101
  addUserLabel(label: string, value: string): Promise<this>;
102
102
  addCommandHandler<C extends CommandHandlers>(command: C, handler: CommandHandlerFunction<C>): this;
103
103
  removeCommandHandler<C extends CommandHandlers>(command: C, handler: CommandHandlerFunction<C>): this;
104
- executeCommandHandler<C extends CommandHandlers>(command: C, request: CommandHandlerData<C>['request'], cluster: CommandHandlerData<C>['cluster'], attributes: CommandHandlerData<C>['attributes'], endpoint: CommandHandlerData<C>['endpoint']): Promise<void>;
104
+ executeCommandHandler<C extends CommandHandlers>(command: C, request: CommandHandlerData<C>['request'], cluster: CommandHandlerData<C>['cluster'], attributes: CommandHandlerData<C>['attributes'], endpoint: CommandHandlerData<C>['endpoint']): Promise<CommandHandlerExecutionResult<C>>;
105
105
  invokeBehaviorCommand<T extends Behavior.Type, C extends BehaviorCommandName<T>>(cluster: T, command: C, params?: BehaviorCommandParams<T, C>): Promise<void>;
106
106
  invokeBehaviorCommand<T extends ClusterType, C extends keyof ClusterType.CommandsOf<T>>(cluster: T, command: C, params?: ClusterType.CommandsOf<T>[C] extends {
107
107
  requestSchema: infer S extends import('@matter/types/tlv').TlvSchema<unknown>;
@@ -182,9 +182,8 @@ export declare class MatterbridgeEndpoint extends Endpoint {
182
182
  }, airflowDirection?: FanControl.AirflowDirection): this;
183
183
  createDefaultHepaFilterMonitoringClusterServer(condition?: number, changeIndication?: ResourceMonitoring.ChangeIndication, inPlaceIndicator?: boolean | undefined, lastChangedTime?: number | null | undefined, replacementProductList?: ResourceMonitoring.ReplacementProduct[]): this;
184
184
  createDefaultActivatedCarbonFilterMonitoringClusterServer(condition?: number, changeIndication?: ResourceMonitoring.ChangeIndication, inPlaceIndicator?: boolean | undefined, lastChangedTime?: number | null | undefined, replacementProductList?: ResourceMonitoring.ReplacementProduct[]): this;
185
- createDefaultDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
186
- createPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
187
- createUserPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType): this;
185
+ createDefaultDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType, autoRelockTime?: number): this;
186
+ createUserPinDoorLockClusterServer(lockState?: DoorLock.LockState, lockType?: DoorLock.LockType, autoRelockTime?: number, minPinCodeLength?: number, maxPinCodeLength?: number): this;
188
187
  createDefaultModeSelectClusterServer(description: string, supportedModes: ModeSelect.ModeOption[], currentMode?: number, startUpMode?: number): this;
189
188
  createDefaultValveConfigurationAndControlClusterServer(valveState?: ValveConfigurationAndControl.ValveState, valveLevel?: number): this;
190
189
  createDefaultPumpConfigurationAndControlClusterServer(pumpMode?: PumpConfigurationAndControl.OperationMode): this;