@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.
- package/dist/cjs/MatterDevice.d.ts +1 -0
- package/dist/cjs/MatterDevice.d.ts.map +1 -1
- package/dist/cjs/MatterDevice.js +3 -0
- package/dist/cjs/MatterDevice.js.map +2 -2
- package/dist/cjs/protocol/interaction/InteractionServer.d.ts.map +1 -1
- package/dist/cjs/protocol/interaction/InteractionServer.js +4 -2
- package/dist/cjs/protocol/interaction/InteractionServer.js.map +2 -2
- package/dist/cjs/session/SecureSession.d.ts.map +1 -1
- package/dist/cjs/session/SecureSession.js +2 -1
- package/dist/cjs/session/SecureSession.js.map +2 -2
- package/dist/cjs/session/SessionManager.d.ts +1 -0
- package/dist/cjs/session/SessionManager.d.ts.map +1 -1
- package/dist/cjs/session/SessionManager.js +7 -0
- package/dist/cjs/session/SessionManager.js.map +2 -2
- package/dist/esm/MatterDevice.d.ts +1 -0
- package/dist/esm/MatterDevice.d.ts.map +1 -1
- package/dist/esm/MatterDevice.js +3 -0
- package/dist/esm/MatterDevice.js.map +2 -2
- package/dist/esm/protocol/interaction/InteractionServer.d.ts.map +1 -1
- package/dist/esm/protocol/interaction/InteractionServer.js +4 -2
- package/dist/esm/protocol/interaction/InteractionServer.js.map +2 -2
- package/dist/esm/session/SecureSession.d.ts.map +1 -1
- package/dist/esm/session/SecureSession.js +2 -1
- package/dist/esm/session/SecureSession.js.map +2 -2
- package/dist/esm/session/SessionManager.d.ts +1 -0
- package/dist/esm/session/SessionManager.d.ts.map +1 -1
- package/dist/esm/session/SessionManager.js +7 -0
- package/dist/esm/session/SessionManager.js.map +2 -2
- package/package.json +3 -3
- package/src/MatterDevice.ts +4 -0
- package/src/protocol/interaction/InteractionServer.ts +4 -2
- package/src/session/SecureSession.ts +2 -1
- 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.
|
|
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.
|
|
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": "
|
|
172
|
+
"gitHead": "7bbb377e3a1a5ac4bb006b0573c25c2fe0d6dd75"
|
|
173
173
|
}
|
package/src/MatterDevice.ts
CHANGED
|
@@ -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(
|
|
1065
|
-
|
|
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
|
-
|
|
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) {
|