@project-chip/matter.js 0.9.2 → 0.9.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.
Files changed (33) hide show
  1. package/dist/cjs/MatterDevice.d.ts +1 -0
  2. package/dist/cjs/MatterDevice.d.ts.map +1 -1
  3. package/dist/cjs/MatterDevice.js +3 -0
  4. package/dist/cjs/MatterDevice.js.map +2 -2
  5. package/dist/cjs/protocol/interaction/InteractionServer.d.ts.map +1 -1
  6. package/dist/cjs/protocol/interaction/InteractionServer.js +4 -2
  7. package/dist/cjs/protocol/interaction/InteractionServer.js.map +2 -2
  8. package/dist/cjs/session/SecureSession.d.ts.map +1 -1
  9. package/dist/cjs/session/SecureSession.js +2 -1
  10. package/dist/cjs/session/SecureSession.js.map +2 -2
  11. package/dist/cjs/session/SessionManager.d.ts +1 -0
  12. package/dist/cjs/session/SessionManager.d.ts.map +1 -1
  13. package/dist/cjs/session/SessionManager.js +7 -0
  14. package/dist/cjs/session/SessionManager.js.map +2 -2
  15. package/dist/esm/MatterDevice.d.ts +1 -0
  16. package/dist/esm/MatterDevice.d.ts.map +1 -1
  17. package/dist/esm/MatterDevice.js +3 -0
  18. package/dist/esm/MatterDevice.js.map +2 -2
  19. package/dist/esm/protocol/interaction/InteractionServer.d.ts.map +1 -1
  20. package/dist/esm/protocol/interaction/InteractionServer.js +4 -2
  21. package/dist/esm/protocol/interaction/InteractionServer.js.map +2 -2
  22. package/dist/esm/session/SecureSession.d.ts.map +1 -1
  23. package/dist/esm/session/SecureSession.js +2 -1
  24. package/dist/esm/session/SecureSession.js.map +2 -2
  25. package/dist/esm/session/SessionManager.d.ts +1 -0
  26. package/dist/esm/session/SessionManager.d.ts.map +1 -1
  27. package/dist/esm/session/SessionManager.js +7 -0
  28. package/dist/esm/session/SessionManager.js.map +2 -2
  29. package/package.json +3 -3
  30. package/src/MatterDevice.ts +4 -0
  31. package/src/protocol/interaction/InteractionServer.ts +4 -2
  32. package/src/session/SecureSession.ts +2 -1
  33. package/src/session/SessionManager.ts +8 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/session/SessionManager.ts"],
4
- "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { MatterFlowError } from \"../common/MatterError.js\";\nimport { Crypto } from \"../crypto/Crypto.js\";\nimport { CaseAuthenticatedTag } from \"../datatype/CaseAuthenticatedTag.js\";\nimport { FabricId } from \"../datatype/FabricId.js\";\nimport { NodeId } from \"../datatype/NodeId.js\";\nimport { Fabric } from \"../fabric/Fabric.js\";\nimport { Logger } from \"../log/Logger.js\";\nimport { MessageCounter } from \"../protocol/MessageCounter.js\";\nimport { StorageContext } from \"../storage/StorageContext.js\";\nimport { ByteArray } from \"../util/ByteArray.js\";\nimport { AsyncObservable, Observable } from \"../util/Observable.js\";\nimport { BasicSet } from \"../util/Set.js\";\nimport { InsecureSession } from \"./InsecureSession.js\";\nimport { SecureSession } from \"./SecureSession.js\";\nimport { SessionParameterOptions, SessionParameters } from \"./Session.js\";\n\nconst logger = Logger.get(\"SessionManager\");\n\nexport const UNICAST_UNSECURE_SESSION_ID = 0x0000;\n\nexport interface ResumptionRecord {\n sharedSecret: ByteArray;\n resumptionId: ByteArray;\n fabric: Fabric;\n peerNodeId: NodeId;\n sessionParameters: SessionParameters;\n caseAuthenticatedTags?: CaseAuthenticatedTag[];\n}\n\ntype ResumptionStorageRecord = {\n nodeId: NodeId;\n sharedSecret: Uint8Array;\n resumptionId: Uint8Array;\n fabricId: FabricId;\n peerNodeId: NodeId;\n sessionParameters: {\n idleIntervalMs: number;\n activeIntervalMs: number;\n activeThresholdMs: number;\n };\n caseAuthenticatedTags?: CaseAuthenticatedTag[];\n};\n\nexport class SessionManager<ContextT> {\n readonly #insecureSessions = new Map<NodeId, InsecureSession<ContextT>>();\n readonly #sessions = new BasicSet<SecureSession<ContextT>>();\n #nextSessionId = Crypto.getRandomUInt16();\n #resumptionRecords = new Map<NodeId, ResumptionRecord>();\n readonly #sessionStorage: StorageContext;\n readonly #globalUnencryptedMessageCounter = new MessageCounter();\n readonly #subscriptionsChanged = new Observable<[session: SecureSession<ContextT>]>();\n readonly #sessionOpened = new Observable<[session: SecureSession<ContextT>]>();\n readonly #sessionClosed = new AsyncObservable<[session: SecureSession<ContextT>], void>();\n\n constructor(\n private readonly context: ContextT,\n sessionStorage: StorageContext,\n ) {\n this.#sessionStorage = sessionStorage;\n }\n\n get subscriptionsChanged() {\n return this.#subscriptionsChanged;\n }\n\n get sessionOpened() {\n return this.#sessionOpened;\n }\n\n get sessionClosed() {\n return this.#sessionClosed;\n }\n\n createUnsecureSession(options: {\n initiatorNodeId?: NodeId;\n sessionParameters?: SessionParameterOptions;\n isInitiator?: boolean;\n }) {\n const { initiatorNodeId, sessionParameters, isInitiator } = options;\n if (initiatorNodeId !== undefined) {\n if (this.#insecureSessions.has(initiatorNodeId)) {\n throw new MatterFlowError(`UnsecureSession with NodeId ${initiatorNodeId} already exists.`);\n }\n }\n while (true) {\n const session = new InsecureSession({\n context: this.context,\n messageCounter: this.#globalUnencryptedMessageCounter,\n closeCallback: async () => {\n logger.info(`End insecure session ${session.name}`);\n this.#insecureSessions.delete(session.nodeId);\n },\n initiatorNodeId,\n sessionParameters,\n isInitiator: isInitiator ?? false,\n });\n\n const ephermalNodeId = session.nodeId;\n if (this.#insecureSessions.has(ephermalNodeId)) continue;\n\n this.#insecureSessions.set(ephermalNodeId, session);\n return session;\n }\n }\n\n async createSecureSession(args: {\n sessionId: number;\n fabric: Fabric | undefined;\n peerNodeId: NodeId;\n peerSessionId: number;\n sharedSecret: ByteArray;\n salt: ByteArray;\n isInitiator: boolean;\n isResumption: boolean;\n sessionParameters?: SessionParameterOptions;\n caseAuthenticatedTags?: CaseAuthenticatedTag[];\n }) {\n const {\n sessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt,\n isInitiator,\n isResumption,\n sessionParameters,\n caseAuthenticatedTags,\n } = args;\n const session = await SecureSession.create({\n context: this.context,\n id: sessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt,\n isInitiator,\n isResumption,\n closeCallback: async () => {\n logger.info(`End ${session.isPase ? \"PASE\" : \"CASE\"} session ${session.name}`);\n this.#sessions.delete(session);\n await this.#sessionClosed.emit(session);\n },\n sessionParameters,\n caseAuthenticatedTags,\n subscriptionChangedCallback: () => {\n this.#subscriptionsChanged.emit(session);\n },\n });\n\n this.#sessions.add(session);\n this.#sessionOpened.emit(session);\n\n // TODO: Add a maximum of sessions and respect/close the \"least recently used\" session. See Core Specs 4.10.1.1\n return session;\n }\n\n removeSession(sessionId: number) {\n const session = this.getSession(sessionId);\n if (session !== undefined) {\n this.#sessions.delete(session);\n }\n }\n\n async removeResumptionRecord(peerNodeId: NodeId) {\n this.#resumptionRecords.delete(peerNodeId);\n await this.storeResumptionRecords();\n }\n\n findOldestInactiveSession() {\n let oldestSession: SecureSession<ContextT> | undefined = undefined;\n for (const session of this.#sessions) {\n if (!oldestSession || session.activeTimestamp < oldestSession.activeTimestamp) {\n oldestSession = session;\n }\n }\n if (oldestSession === undefined) {\n throw new MatterFlowError(\"No session found to close and all session ids are taken.\");\n }\n return oldestSession;\n }\n\n async getNextAvailableSessionId() {\n for (let i = 0; i < 0xffff; i++) {\n const id = this.#nextSessionId;\n this.#nextSessionId = (this.#nextSessionId + 1) & 0xffff;\n if (this.#nextSessionId === 0) this.#nextSessionId++;\n\n if (this.getSession(id) === undefined) {\n return id;\n }\n }\n\n // All session ids are taken, search for the oldest unused session, and close it and re-use its ID\n const oldestSession = this.findOldestInactiveSession();\n await oldestSession.end(true, false);\n this.#nextSessionId = oldestSession.id;\n return this.#nextSessionId++;\n }\n\n getSession(sessionId: number) {\n return this.#sessions.get(\"id\", sessionId);\n }\n\n getPaseSession() {\n return [...this.#sessions].find(\n session => session.isSecure && session.isPase && !session.closingAfterExchangeFinished,\n ) as SecureSession<ContextT>;\n }\n\n getSessionForNode(fabric: Fabric, nodeId: NodeId) {\n //TODO: It can have multiple sessions for one node ...\n return [...this.#sessions].find(session => {\n if (!session.isSecure) return false;\n const secureSession = session as SecureSession<any>;\n return secureSession.fabric?.fabricId === fabric.fabricId && secureSession.peerNodeId === nodeId;\n });\n }\n\n async removeAllSessionsForNode(nodeId: NodeId, sendClose = false) {\n for (const session of this.#sessions) {\n if (!session.isSecure) continue;\n const secureSession = session as SecureSession<any>;\n if (secureSession.peerNodeId === nodeId) {\n await secureSession.destroy(sendClose, false);\n }\n }\n }\n\n getUnsecureSession(sourceNodeId?: NodeId) {\n if (sourceNodeId === undefined) {\n return this.#insecureSessions.get(NodeId.UNSPECIFIED_NODE_ID);\n }\n return this.#insecureSessions.get(sourceNodeId);\n }\n\n findGroupSession(groupId: number, groupSessionId: number) {\n // Use groupsession id to find the key ??!!\n // The Group Session ID MAY help receiving nodes efficiently locate the Operational Group Key used to encrypt an incoming groupcast message. It SHALL NOT be used as the sole means to locate the asso\u00AD ciated Operational Group Key, since it MAY collide within the fabric. Instead, the Group Session ID provides receiving nodes a means to identify Operational Group Key candidates without the need to first attempt to decrypt groupcast messages using all available keys.\n // On receipt of a message of Group Session Type, all valid, installed, operational group key candidates referenced by the given Group Session ID SHALL be attempted until authentication is passed or there are no more operational group keys to try. This is done because the same Group Session ID might arise from different keys. The chance of a Group Session ID collision is 2-16 but the chance of both a Group Session ID collision and the message MIC matching two different operational group keys is 2-80.\n\n // TODO\n throw new Error(`Not implemented ${groupId} ${groupSessionId}`);\n }\n\n findResumptionRecordById(resumptionId: ByteArray) {\n return [...this.#resumptionRecords.values()].find(record => record.resumptionId.equals(resumptionId));\n }\n\n findResumptionRecordByNodeId(nodeId: NodeId) {\n return this.#resumptionRecords.get(nodeId);\n }\n\n async saveResumptionRecord(resumptionRecord: ResumptionRecord) {\n this.#resumptionRecords.set(resumptionRecord.peerNodeId, resumptionRecord);\n await this.storeResumptionRecords();\n }\n\n async updateFabricForResumptionRecords(fabric: Fabric) {\n const record = this.#resumptionRecords.get(fabric.rootNodeId);\n if (record === undefined) {\n throw new MatterFlowError(\"Resumption record not found. Should never happen.\");\n }\n this.#resumptionRecords.set(fabric.rootNodeId, { ...record, fabric });\n await this.storeResumptionRecords();\n }\n\n async storeResumptionRecords() {\n await this.#sessionStorage.set(\n \"resumptionRecords\",\n [...this.#resumptionRecords].map(\n ([\n nodeId,\n { sharedSecret, resumptionId, peerNodeId, fabric, sessionParameters, caseAuthenticatedTags },\n ]) =>\n ({\n nodeId,\n sharedSecret,\n resumptionId,\n fabricId: fabric.fabricId,\n peerNodeId: peerNodeId,\n sessionParameters,\n caseAuthenticatedTags,\n }) as ResumptionStorageRecord,\n ),\n );\n }\n\n async initFromStorage(fabrics: Fabric[]) {\n const storedResumptionRecords = await this.#sessionStorage.get<ResumptionStorageRecord[]>(\n \"resumptionRecords\",\n [],\n );\n\n storedResumptionRecords.forEach(\n ({\n nodeId,\n sharedSecret,\n resumptionId,\n fabricId,\n peerNodeId,\n sessionParameters,\n caseAuthenticatedTags,\n }) => {\n logger.info(\"restoring resumption record for node\", nodeId);\n const fabric = fabrics.find(fabric => fabric.fabricId === fabricId);\n if (!fabric) {\n logger.error(\"fabric not found for resumption record\", fabricId);\n return;\n }\n this.#resumptionRecords.set(nodeId, {\n sharedSecret,\n resumptionId,\n fabric,\n peerNodeId,\n sessionParameters,\n caseAuthenticatedTags,\n });\n },\n );\n }\n\n getActiveSessionInformation() {\n return [...this.#sessions]\n .filter(session => session.isSecure && !session.isPase)\n .map(session => ({\n name: session.name,\n nodeId: session.nodeId,\n peerNodeId: session.peerNodeId,\n fabric: session instanceof SecureSession ? session.fabric?.externalInformation : undefined,\n isPeerActive: session.isPeerActive(),\n secure: session.isSecure,\n lastInteractionTimestamp: session instanceof SecureSession ? session.timestamp : undefined,\n lastActiveTimestamp: session instanceof SecureSession ? session.activeTimestamp : undefined,\n numberOfActiveSubscriptions: session instanceof SecureSession ? session.numberOfActiveSubscriptions : 0,\n }));\n }\n\n async close() {\n await this.storeResumptionRecords();\n for (const session of this.#sessions) {\n await session?.end(false);\n this.#sessions.delete(session);\n }\n for (const session of this.#insecureSessions.values()) {\n await session?.end();\n this.#insecureSessions.delete(session.nodeId);\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/**\n * @license\n * Copyright 2022-2024 Matter.js Authors\n * SPDX-License-Identifier: Apache-2.0\n */\n\nimport { MatterFlowError } from \"../common/MatterError.js\";\nimport { Crypto } from \"../crypto/Crypto.js\";\nimport { CaseAuthenticatedTag } from \"../datatype/CaseAuthenticatedTag.js\";\nimport { FabricId } from \"../datatype/FabricId.js\";\nimport { NodeId } from \"../datatype/NodeId.js\";\nimport { Fabric } from \"../fabric/Fabric.js\";\nimport { Logger } from \"../log/Logger.js\";\nimport { MessageCounter } from \"../protocol/MessageCounter.js\";\nimport { StorageContext } from \"../storage/StorageContext.js\";\nimport { ByteArray } from \"../util/ByteArray.js\";\nimport { AsyncObservable, Observable } from \"../util/Observable.js\";\nimport { BasicSet } from \"../util/Set.js\";\nimport { InsecureSession } from \"./InsecureSession.js\";\nimport { SecureSession } from \"./SecureSession.js\";\nimport { SessionParameterOptions, SessionParameters } from \"./Session.js\";\n\nconst logger = Logger.get(\"SessionManager\");\n\nexport const UNICAST_UNSECURE_SESSION_ID = 0x0000;\n\nexport interface ResumptionRecord {\n sharedSecret: ByteArray;\n resumptionId: ByteArray;\n fabric: Fabric;\n peerNodeId: NodeId;\n sessionParameters: SessionParameters;\n caseAuthenticatedTags?: CaseAuthenticatedTag[];\n}\n\ntype ResumptionStorageRecord = {\n nodeId: NodeId;\n sharedSecret: Uint8Array;\n resumptionId: Uint8Array;\n fabricId: FabricId;\n peerNodeId: NodeId;\n sessionParameters: {\n idleIntervalMs: number;\n activeIntervalMs: number;\n activeThresholdMs: number;\n };\n caseAuthenticatedTags?: CaseAuthenticatedTag[];\n};\n\nexport class SessionManager<ContextT> {\n readonly #insecureSessions = new Map<NodeId, InsecureSession<ContextT>>();\n readonly #sessions = new BasicSet<SecureSession<ContextT>>();\n #nextSessionId = Crypto.getRandomUInt16();\n #resumptionRecords = new Map<NodeId, ResumptionRecord>();\n readonly #sessionStorage: StorageContext;\n readonly #globalUnencryptedMessageCounter = new MessageCounter();\n readonly #subscriptionsChanged = new Observable<[session: SecureSession<ContextT>]>();\n readonly #sessionOpened = new Observable<[session: SecureSession<ContextT>]>();\n readonly #sessionClosed = new AsyncObservable<[session: SecureSession<ContextT>], void>();\n\n constructor(\n private readonly context: ContextT,\n sessionStorage: StorageContext,\n ) {\n this.#sessionStorage = sessionStorage;\n }\n\n get subscriptionsChanged() {\n return this.#subscriptionsChanged;\n }\n\n get sessionOpened() {\n return this.#sessionOpened;\n }\n\n get sessionClosed() {\n return this.#sessionClosed;\n }\n\n createUnsecureSession(options: {\n initiatorNodeId?: NodeId;\n sessionParameters?: SessionParameterOptions;\n isInitiator?: boolean;\n }) {\n const { initiatorNodeId, sessionParameters, isInitiator } = options;\n if (initiatorNodeId !== undefined) {\n if (this.#insecureSessions.has(initiatorNodeId)) {\n throw new MatterFlowError(`UnsecureSession with NodeId ${initiatorNodeId} already exists.`);\n }\n }\n while (true) {\n const session = new InsecureSession({\n context: this.context,\n messageCounter: this.#globalUnencryptedMessageCounter,\n closeCallback: async () => {\n logger.info(`End insecure session ${session.name}`);\n this.#insecureSessions.delete(session.nodeId);\n },\n initiatorNodeId,\n sessionParameters,\n isInitiator: isInitiator ?? false,\n });\n\n const ephermalNodeId = session.nodeId;\n if (this.#insecureSessions.has(ephermalNodeId)) continue;\n\n this.#insecureSessions.set(ephermalNodeId, session);\n return session;\n }\n }\n\n async createSecureSession(args: {\n sessionId: number;\n fabric: Fabric | undefined;\n peerNodeId: NodeId;\n peerSessionId: number;\n sharedSecret: ByteArray;\n salt: ByteArray;\n isInitiator: boolean;\n isResumption: boolean;\n sessionParameters?: SessionParameterOptions;\n caseAuthenticatedTags?: CaseAuthenticatedTag[];\n }) {\n const {\n sessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt,\n isInitiator,\n isResumption,\n sessionParameters,\n caseAuthenticatedTags,\n } = args;\n const session = await SecureSession.create({\n context: this.context,\n id: sessionId,\n fabric,\n peerNodeId,\n peerSessionId,\n sharedSecret,\n salt,\n isInitiator,\n isResumption,\n closeCallback: async () => {\n logger.info(`End ${session.isPase ? \"PASE\" : \"CASE\"} session ${session.name}`);\n this.#sessions.delete(session);\n await this.#sessionClosed.emit(session);\n },\n sessionParameters,\n caseAuthenticatedTags,\n subscriptionChangedCallback: () => {\n this.#subscriptionsChanged.emit(session);\n },\n });\n\n this.#sessions.add(session);\n this.#sessionOpened.emit(session);\n\n // TODO: Add a maximum of sessions and respect/close the \"least recently used\" session. See Core Specs 4.10.1.1\n return session;\n }\n\n removeSession(sessionId: number) {\n const session = this.getSession(sessionId);\n if (session !== undefined) {\n this.#sessions.delete(session);\n }\n }\n\n async removeResumptionRecord(peerNodeId: NodeId) {\n this.#resumptionRecords.delete(peerNodeId);\n await this.storeResumptionRecords();\n }\n\n findOldestInactiveSession() {\n let oldestSession: SecureSession<ContextT> | undefined = undefined;\n for (const session of this.#sessions) {\n if (!oldestSession || session.activeTimestamp < oldestSession.activeTimestamp) {\n oldestSession = session;\n }\n }\n if (oldestSession === undefined) {\n throw new MatterFlowError(\"No session found to close and all session ids are taken.\");\n }\n return oldestSession;\n }\n\n async getNextAvailableSessionId() {\n for (let i = 0; i < 0xffff; i++) {\n const id = this.#nextSessionId;\n this.#nextSessionId = (this.#nextSessionId + 1) & 0xffff;\n if (this.#nextSessionId === 0) this.#nextSessionId++;\n\n if (this.getSession(id) === undefined) {\n return id;\n }\n }\n\n // All session ids are taken, search for the oldest unused session, and close it and re-use its ID\n const oldestSession = this.findOldestInactiveSession();\n await oldestSession.end(true, false);\n this.#nextSessionId = oldestSession.id;\n return this.#nextSessionId++;\n }\n\n getSession(sessionId: number) {\n return this.#sessions.get(\"id\", sessionId);\n }\n\n getPaseSession() {\n return [...this.#sessions].find(\n session => session.isSecure && session.isPase && !session.closingAfterExchangeFinished,\n ) as SecureSession<ContextT>;\n }\n\n getSessionForNode(fabric: Fabric, nodeId: NodeId) {\n //TODO: It can have multiple sessions for one node ...\n return [...this.#sessions].find(session => {\n if (!session.isSecure) return false;\n const secureSession = session as SecureSession<any>;\n return secureSession.fabric?.fabricId === fabric.fabricId && secureSession.peerNodeId === nodeId;\n });\n }\n\n async removeAllSessionsForNode(nodeId: NodeId, sendClose = false) {\n for (const session of this.#sessions) {\n if (!session.isSecure) continue;\n const secureSession = session as SecureSession<any>;\n if (secureSession.peerNodeId === nodeId) {\n await secureSession.destroy(sendClose, false);\n }\n }\n }\n\n getUnsecureSession(sourceNodeId?: NodeId) {\n if (sourceNodeId === undefined) {\n return this.#insecureSessions.get(NodeId.UNSPECIFIED_NODE_ID);\n }\n return this.#insecureSessions.get(sourceNodeId);\n }\n\n findGroupSession(groupId: number, groupSessionId: number) {\n // Use groupsession id to find the key ??!!\n // The Group Session ID MAY help receiving nodes efficiently locate the Operational Group Key used to encrypt an incoming groupcast message. It SHALL NOT be used as the sole means to locate the asso\u00AD ciated Operational Group Key, since it MAY collide within the fabric. Instead, the Group Session ID provides receiving nodes a means to identify Operational Group Key candidates without the need to first attempt to decrypt groupcast messages using all available keys.\n // On receipt of a message of Group Session Type, all valid, installed, operational group key candidates referenced by the given Group Session ID SHALL be attempted until authentication is passed or there are no more operational group keys to try. This is done because the same Group Session ID might arise from different keys. The chance of a Group Session ID collision is 2-16 but the chance of both a Group Session ID collision and the message MIC matching two different operational group keys is 2-80.\n\n // TODO\n throw new Error(`Not implemented ${groupId} ${groupSessionId}`);\n }\n\n findResumptionRecordById(resumptionId: ByteArray) {\n return [...this.#resumptionRecords.values()].find(record => record.resumptionId.equals(resumptionId));\n }\n\n findResumptionRecordByNodeId(nodeId: NodeId) {\n return this.#resumptionRecords.get(nodeId);\n }\n\n async saveResumptionRecord(resumptionRecord: ResumptionRecord) {\n this.#resumptionRecords.set(resumptionRecord.peerNodeId, resumptionRecord);\n await this.storeResumptionRecords();\n }\n\n async updateFabricForResumptionRecords(fabric: Fabric) {\n const record = this.#resumptionRecords.get(fabric.rootNodeId);\n if (record === undefined) {\n throw new MatterFlowError(\"Resumption record not found. Should never happen.\");\n }\n this.#resumptionRecords.set(fabric.rootNodeId, { ...record, fabric });\n await this.storeResumptionRecords();\n }\n\n async storeResumptionRecords() {\n await this.#sessionStorage.set(\n \"resumptionRecords\",\n [...this.#resumptionRecords].map(\n ([\n nodeId,\n { sharedSecret, resumptionId, peerNodeId, fabric, sessionParameters, caseAuthenticatedTags },\n ]) =>\n ({\n nodeId,\n sharedSecret,\n resumptionId,\n fabricId: fabric.fabricId,\n peerNodeId: peerNodeId,\n sessionParameters,\n caseAuthenticatedTags,\n }) as ResumptionStorageRecord,\n ),\n );\n }\n\n async initFromStorage(fabrics: Fabric[]) {\n const storedResumptionRecords = await this.#sessionStorage.get<ResumptionStorageRecord[]>(\n \"resumptionRecords\",\n [],\n );\n\n storedResumptionRecords.forEach(\n ({\n nodeId,\n sharedSecret,\n resumptionId,\n fabricId,\n peerNodeId,\n sessionParameters,\n caseAuthenticatedTags,\n }) => {\n logger.info(\"restoring resumption record for node\", nodeId);\n const fabric = fabrics.find(fabric => fabric.fabricId === fabricId);\n if (!fabric) {\n logger.error(\"fabric not found for resumption record\", fabricId);\n return;\n }\n this.#resumptionRecords.set(nodeId, {\n sharedSecret,\n resumptionId,\n fabric,\n peerNodeId,\n sessionParameters,\n caseAuthenticatedTags,\n });\n },\n );\n }\n\n getActiveSessionInformation() {\n return [...this.#sessions]\n .filter(session => session.isSecure && !session.isPase)\n .map(session => ({\n name: session.name,\n nodeId: session.nodeId,\n peerNodeId: session.peerNodeId,\n fabric: session instanceof SecureSession ? session.fabric?.externalInformation : undefined,\n isPeerActive: session.isPeerActive(),\n secure: session.isSecure,\n lastInteractionTimestamp: session instanceof SecureSession ? session.timestamp : undefined,\n lastActiveTimestamp: session instanceof SecureSession ? session.activeTimestamp : undefined,\n numberOfActiveSubscriptions: session instanceof SecureSession ? session.numberOfActiveSubscriptions : 0,\n }));\n }\n\n async clearSubscriptionsForNode(nodeId: NodeId, flushSubscriptions?: boolean) {\n for (const session of this.#sessions) {\n if (session.peerNodeId === nodeId) {\n await session.clearSubscriptions(flushSubscriptions);\n }\n }\n }\n\n async close() {\n await this.storeResumptionRecords();\n for (const session of this.#sessions) {\n await session?.end(false);\n this.#sessions.delete(session);\n }\n for (const session of this.#insecureSessions.values()) {\n await session?.end();\n this.#insecureSessions.delete(session.nodeId);\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["fabric"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@project-chip/matter.js",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "Matter protocol in pure js",
5
5
  "keywords": [
6
6
  "iot",
@@ -36,7 +36,7 @@
36
36
  "@noble/curves": "^1.4.0"
37
37
  },
38
38
  "devDependencies": {
39
- "@project-chip/matter.js-tools": "0.9.2",
39
+ "@project-chip/matter.js-tools": "0.9.3",
40
40
  "@types/chai": "^4.3.12",
41
41
  "@types/mocha": "^10.0.6",
42
42
  "@types/wtfnode": "^0.7.3",
@@ -169,5 +169,5 @@
169
169
  "publishConfig": {
170
170
  "access": "public"
171
171
  },
172
- "gitHead": "d638f16736899f69cc752581454960c9dc1904d2"
172
+ "gitHead": "7bbb377e3a1a5ac4bb006b0573c25c2fe0d6dd75"
173
173
  }
@@ -504,6 +504,10 @@ export class MatterDevice {
504
504
  return { session, channel: await networkInterface.openChannel(device.addresses[0]) };
505
505
  }
506
506
 
507
+ async clearSubscriptionsForNode(peerNodeId: NodeId, flushSubscriptions?: boolean) {
508
+ await this.#sessionManager.clearSubscriptionsForNode(peerNodeId, flushSubscriptions);
509
+ }
510
+
507
511
  async close() {
508
512
  this.#isClosing = true;
509
513
  await this.endCommissioning();
@@ -1061,8 +1061,10 @@ export class InteractionServer implements ProtocolHandler<MatterDevice>, Interac
1061
1061
  }
1062
1062
 
1063
1063
  if (!keepSubscriptions) {
1064
- logger.debug(`Clear subscriptions for Session ${session.name} because keepSubscriptions=false`);
1065
- await session.clearSubscriptions(true);
1064
+ logger.debug(
1065
+ `Clear subscriptions for Subscriber node ${session.peerNodeId} because keepSubscriptions=false`,
1066
+ );
1067
+ await session.context.clearSubscriptionsForNode(session.peerNodeId, true);
1066
1068
  }
1067
1069
 
1068
1070
  const maxInterval = subscriptionHandler.getMaxInterval();
@@ -290,7 +290,8 @@ export class SecureSession<T> extends Session<T> {
290
290
  }
291
291
 
292
292
  async clearSubscriptions(flushSubscriptions = false) {
293
- for (const subscription of this.#subscriptions) {
293
+ const subscriptions = [...this.#subscriptions]; // get all values because subscriptions will remove themselves when cancelled
294
+ for (const subscription of subscriptions) {
294
295
  await subscription.cancel(flushSubscriptions);
295
296
  }
296
297
  this.#subscriptions.length = 0;
@@ -343,6 +343,14 @@ export class SessionManager<ContextT> {
343
343
  }));
344
344
  }
345
345
 
346
+ async clearSubscriptionsForNode(nodeId: NodeId, flushSubscriptions?: boolean) {
347
+ for (const session of this.#sessions) {
348
+ if (session.peerNodeId === nodeId) {
349
+ await session.clearSubscriptions(flushSubscriptions);
350
+ }
351
+ }
352
+ }
353
+
346
354
  async close() {
347
355
  await this.storeResumptionRecords();
348
356
  for (const session of this.#sessions) {