@credo-ts/tenants 0.5.0-alpha.101

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 (57) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +31 -0
  3. package/build/TenantAgent.d.ts +8 -0
  4. package/build/TenantAgent.js +25 -0
  5. package/build/TenantAgent.js.map +1 -0
  6. package/build/TenantsApi.d.ts +17 -0
  7. package/build/TenantsApi.js +83 -0
  8. package/build/TenantsApi.js.map +1 -0
  9. package/build/TenantsApiOptions.d.ts +10 -0
  10. package/build/TenantsApiOptions.js +3 -0
  11. package/build/TenantsApiOptions.js.map +1 -0
  12. package/build/TenantsModule.d.ts +13 -0
  13. package/build/TenantsModule.js +39 -0
  14. package/build/TenantsModule.js.map +1 -0
  15. package/build/TenantsModuleConfig.d.ts +31 -0
  16. package/build/TenantsModuleConfig.js +20 -0
  17. package/build/TenantsModuleConfig.js.map +1 -0
  18. package/build/context/TenantAgentContextProvider.d.ts +20 -0
  19. package/build/context/TenantAgentContextProvider.js +116 -0
  20. package/build/context/TenantAgentContextProvider.js.map +1 -0
  21. package/build/context/TenantSessionCoordinator.d.ts +48 -0
  22. package/build/context/TenantSessionCoordinator.js +174 -0
  23. package/build/context/TenantSessionCoordinator.js.map +1 -0
  24. package/build/context/TenantSessionMutex.d.ts +29 -0
  25. package/build/context/TenantSessionMutex.js +84 -0
  26. package/build/context/TenantSessionMutex.js.map +1 -0
  27. package/build/context/types.d.ts +0 -0
  28. package/build/context/types.js +2 -0
  29. package/build/context/types.js.map +1 -0
  30. package/build/index.d.ts +5 -0
  31. package/build/index.js +24 -0
  32. package/build/index.js.map +1 -0
  33. package/build/models/TenantConfig.d.ts +4 -0
  34. package/build/models/TenantConfig.js +3 -0
  35. package/build/models/TenantConfig.js.map +1 -0
  36. package/build/repository/TenantRecord.d.ts +20 -0
  37. package/build/repository/TenantRecord.js +23 -0
  38. package/build/repository/TenantRecord.js.map +1 -0
  39. package/build/repository/TenantRepository.d.ts +5 -0
  40. package/build/repository/TenantRepository.js +29 -0
  41. package/build/repository/TenantRepository.js.map +1 -0
  42. package/build/repository/TenantRoutingRecord.d.ts +26 -0
  43. package/build/repository/TenantRoutingRecord.js +24 -0
  44. package/build/repository/TenantRoutingRecord.js.map +1 -0
  45. package/build/repository/TenantRoutingRepository.d.ts +7 -0
  46. package/build/repository/TenantRoutingRepository.js +34 -0
  47. package/build/repository/TenantRoutingRepository.js.map +1 -0
  48. package/build/repository/index.d.ts +4 -0
  49. package/build/repository/index.js +21 -0
  50. package/build/repository/index.js.map +1 -0
  51. package/build/services/TenantRecordService.d.ts +13 -0
  52. package/build/services/TenantRecordService.js +65 -0
  53. package/build/services/TenantRecordService.js.map +1 -0
  54. package/build/services/index.d.ts +1 -0
  55. package/build/services/index.js +18 -0
  56. package/build/services/index.js.map +1 -0
  57. package/package.json +37 -0
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.TenantAgentContextProvider = void 0;
16
+ const core_1 = require("@credo-ts/core");
17
+ const services_1 = require("../services");
18
+ const TenantSessionCoordinator_1 = require("./TenantSessionCoordinator");
19
+ let TenantAgentContextProvider = class TenantAgentContextProvider {
20
+ constructor(tenantRecordService, rootAgentContext, eventEmitter, tenantSessionCoordinator, logger) {
21
+ this.tenantRecordService = tenantRecordService;
22
+ this.rootAgentContext = rootAgentContext;
23
+ this.eventEmitter = eventEmitter;
24
+ this.tenantSessionCoordinator = tenantSessionCoordinator;
25
+ this.logger = logger;
26
+ // Start listener for newly created routing keys, so we can register a mapping for each new key for the tenant
27
+ this.listenForRoutingKeyCreatedEvents();
28
+ }
29
+ async getAgentContextForContextCorrelationId(tenantId) {
30
+ // TODO: maybe we can look at not having to retrieve the tenant record if there's already a context available.
31
+ const tenantRecord = await this.tenantRecordService.getTenantById(this.rootAgentContext, tenantId);
32
+ const agentContext = this.tenantSessionCoordinator.getContextForSession(tenantRecord);
33
+ this.logger.debug(`Created tenant agent context for tenant '${tenantId}'`);
34
+ return agentContext;
35
+ }
36
+ async getContextForInboundMessage(inboundMessage, options) {
37
+ this.logger.debug('Getting context for inbound message in tenant agent context provider', {
38
+ contextCorrelationId: options === null || options === void 0 ? void 0 : options.contextCorrelationId,
39
+ });
40
+ let tenantId = options === null || options === void 0 ? void 0 : options.contextCorrelationId;
41
+ let recipientKeys = [];
42
+ if (!tenantId && (0, core_1.isValidJweStructure)(inboundMessage)) {
43
+ this.logger.trace("Inbound message is a JWE, extracting tenant id from JWE's protected header");
44
+ recipientKeys = this.getRecipientKeysFromEncryptedMessage(inboundMessage);
45
+ this.logger.trace(`Found ${recipientKeys.length} recipient keys in JWE's protected header`);
46
+ // FIXME: what if there are multiple recipients in the same agent? If we receive the messages twice we will process it for
47
+ // the first found recipient multiple times. This is however a case I've never seen before and will add quite some complexity
48
+ // to resolve. I think we're fine to ignore this case for now.
49
+ for (const recipientKey of recipientKeys) {
50
+ const tenantRoutingRecord = await this.tenantRecordService.findTenantRoutingRecordByRecipientKey(this.rootAgentContext, recipientKey);
51
+ if (tenantRoutingRecord) {
52
+ this.logger.debug(`Found tenant routing record for recipient key ${recipientKeys[0].fingerprint}`, {
53
+ tenantId: tenantRoutingRecord.tenantId,
54
+ });
55
+ tenantId = tenantRoutingRecord.tenantId;
56
+ break;
57
+ }
58
+ }
59
+ }
60
+ if (!tenantId) {
61
+ this.logger.error("Couldn't determine tenant id for inbound message. Unable to create context", {
62
+ inboundMessage,
63
+ recipientKeys: recipientKeys.map((key) => key.fingerprint),
64
+ });
65
+ throw new core_1.AriesFrameworkError("Couldn't determine tenant id for inbound message. Unable to create context");
66
+ }
67
+ const agentContext = await this.getAgentContextForContextCorrelationId(tenantId);
68
+ return agentContext;
69
+ }
70
+ async endSessionForAgentContext(agentContext) {
71
+ await this.tenantSessionCoordinator.endAgentContextSession(agentContext);
72
+ }
73
+ getRecipientKeysFromEncryptedMessage(jwe) {
74
+ const jweProtected = core_1.JsonEncoder.fromBase64(jwe.protected);
75
+ if (!Array.isArray(jweProtected.recipients))
76
+ return [];
77
+ const recipientKeys = [];
78
+ for (const recipient of jweProtected.recipients) {
79
+ // Check if recipient.header.kid is a string
80
+ if ((0, core_1.isJsonObject)(recipient) && (0, core_1.isJsonObject)(recipient.header) && typeof recipient.header.kid === 'string') {
81
+ // This won't work with other key types, we should detect what the encoding is of kid, and based on that
82
+ // determine how we extract the key from the message
83
+ const key = core_1.Key.fromPublicKeyBase58(recipient.header.kid, core_1.KeyType.Ed25519);
84
+ recipientKeys.push(key);
85
+ }
86
+ }
87
+ return recipientKeys;
88
+ }
89
+ async registerRecipientKeyForTenant(tenantId, recipientKey) {
90
+ this.logger.debug(`Registering recipient key ${recipientKey.fingerprint} for tenant ${tenantId}`);
91
+ const tenantRecord = await this.tenantRecordService.getTenantById(this.rootAgentContext, tenantId);
92
+ await this.tenantRecordService.addTenantRoutingRecord(this.rootAgentContext, tenantRecord.id, recipientKey);
93
+ }
94
+ listenForRoutingKeyCreatedEvents() {
95
+ this.logger.debug('Listening for routing key created events in tenant agent context provider');
96
+ this.eventEmitter.on(core_1.RoutingEventTypes.RoutingCreatedEvent, async (event) => {
97
+ const contextCorrelationId = event.metadata.contextCorrelationId;
98
+ const recipientKey = event.payload.routing.recipientKey;
99
+ // We don't want to register the key if it's for the root agent context
100
+ if (contextCorrelationId === this.rootAgentContext.contextCorrelationId)
101
+ return;
102
+ this.logger.debug(`Received routing key created event for tenant ${contextCorrelationId}, registering recipient key ${recipientKey.fingerprint} in base wallet`);
103
+ await this.registerRecipientKeyForTenant(contextCorrelationId, recipientKey);
104
+ });
105
+ }
106
+ };
107
+ TenantAgentContextProvider = __decorate([
108
+ (0, core_1.injectable)(),
109
+ __param(4, (0, core_1.inject)(core_1.InjectionSymbols.Logger)),
110
+ __metadata("design:paramtypes", [services_1.TenantRecordService,
111
+ core_1.AgentContext,
112
+ core_1.EventEmitter,
113
+ TenantSessionCoordinator_1.TenantSessionCoordinator, Object])
114
+ ], TenantAgentContextProvider);
115
+ exports.TenantAgentContextProvider = TenantAgentContextProvider;
116
+ //# sourceMappingURL=TenantAgentContextProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantAgentContextProvider.js","sourceRoot":"","sources":["../../src/context/TenantAgentContextProvider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ import type { TenantRecord } from '../repository';
2
+ import type { MutexInterface } from 'async-mutex';
3
+ import { AgentContext, Logger } from '@credo-ts/core';
4
+ import { TenantsModuleConfig } from '../TenantsModuleConfig';
5
+ /**
6
+ * Coordinates all agent context instance for tenant sessions.
7
+ *
8
+ * This class keeps a mapping of tenant ids (context correlation ids) to agent context sessions mapping. Each mapping contains the agent context,
9
+ * the current session count and a mutex for making operations against the session mapping (opening / closing an agent context). The mutex ensures
10
+ * we're not susceptible to race conditions where multiple calls to open/close an agent context are made at the same time. Even though JavaScript is
11
+ * single threaded, promises can introduce race conditions as one process can stop and another process can be picked up.
12
+ *
13
+ * NOTE: the implementation doesn't yet cache agent context objects after they aren't being used for any sessions anymore. This means if a wallet is being used
14
+ * often in a short time it will be opened/closed very often. This is an improvement to be made in the near future.
15
+ */
16
+ export declare class TenantSessionCoordinator {
17
+ private rootAgentContext;
18
+ private logger;
19
+ private tenantAgentContextMapping;
20
+ private sessionMutex;
21
+ private tenantsModuleConfig;
22
+ constructor(rootAgentContext: AgentContext, logger: Logger, tenantsModuleConfig: TenantsModuleConfig);
23
+ /**
24
+ * Get agent context to use for a session. If an agent context for this tenant does not exist yet
25
+ * it will create it and store it for later use. If the agent context does already exist it will
26
+ * be returned.
27
+ */
28
+ getContextForSession(tenantRecord: TenantRecord): Promise<AgentContext>;
29
+ /**
30
+ * End a session for the provided agent context. It will decrease the session count for the agent context.
31
+ * If the number of sessions is zero after the context for this session has been ended, the agent context will be closed.
32
+ */
33
+ endAgentContextSession(agentContext: AgentContext): Promise<void>;
34
+ private hasTenantSessionMapping;
35
+ private getTenantSessionsMapping;
36
+ private mutexForTenant;
37
+ private createAgentContext;
38
+ private closeAgentContext;
39
+ }
40
+ interface TenantContextSessions {
41
+ sessionCount: number;
42
+ agentContext?: AgentContext;
43
+ mutex: MutexInterface;
44
+ }
45
+ export interface TenantAgentContextMapping {
46
+ [tenantId: string]: TenantContextSessions | undefined;
47
+ }
48
+ export {};
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __rest = (this && this.__rest) || function (s, e) {
15
+ var t = {};
16
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
17
+ t[p] = s[p];
18
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
19
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
20
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
21
+ t[p[i]] = s[p[i]];
22
+ }
23
+ return t;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.TenantSessionCoordinator = void 0;
27
+ const core_1 = require("@credo-ts/core");
28
+ const async_mutex_1 = require("async-mutex");
29
+ const TenantsModuleConfig_1 = require("../TenantsModuleConfig");
30
+ const TenantSessionMutex_1 = require("./TenantSessionMutex");
31
+ /**
32
+ * Coordinates all agent context instance for tenant sessions.
33
+ *
34
+ * This class keeps a mapping of tenant ids (context correlation ids) to agent context sessions mapping. Each mapping contains the agent context,
35
+ * the current session count and a mutex for making operations against the session mapping (opening / closing an agent context). The mutex ensures
36
+ * we're not susceptible to race conditions where multiple calls to open/close an agent context are made at the same time. Even though JavaScript is
37
+ * single threaded, promises can introduce race conditions as one process can stop and another process can be picked up.
38
+ *
39
+ * NOTE: the implementation doesn't yet cache agent context objects after they aren't being used for any sessions anymore. This means if a wallet is being used
40
+ * often in a short time it will be opened/closed very often. This is an improvement to be made in the near future.
41
+ */
42
+ let TenantSessionCoordinator = class TenantSessionCoordinator {
43
+ constructor(rootAgentContext, logger, tenantsModuleConfig) {
44
+ this.tenantAgentContextMapping = {};
45
+ this.rootAgentContext = rootAgentContext;
46
+ this.logger = logger;
47
+ this.tenantsModuleConfig = tenantsModuleConfig;
48
+ // TODO: we should make the timeout and the session limit configurable, but until we have the modularization in place with
49
+ // module specific config, it's not easy to do so. Keeping it hardcoded for now
50
+ this.sessionMutex = new TenantSessionMutex_1.TenantSessionMutex(this.logger, this.tenantsModuleConfig.sessionLimit, this.tenantsModuleConfig.sessionAcquireTimeout);
51
+ }
52
+ /**
53
+ * Get agent context to use for a session. If an agent context for this tenant does not exist yet
54
+ * it will create it and store it for later use. If the agent context does already exist it will
55
+ * be returned.
56
+ */
57
+ async getContextForSession(tenantRecord) {
58
+ this.logger.debug(`Getting context for session with tenant '${tenantRecord.id}'`);
59
+ // Wait for a session to be available
60
+ await this.sessionMutex.acquireSession();
61
+ try {
62
+ return await this.mutexForTenant(tenantRecord.id).runExclusive(async () => {
63
+ this.logger.debug(`Acquired lock for tenant '${tenantRecord.id}' to get context`);
64
+ const tenantSessions = this.getTenantSessionsMapping(tenantRecord.id);
65
+ // If we don't have an agent context already, create one and initialize it
66
+ if (!tenantSessions.agentContext) {
67
+ this.logger.debug(`No agent context has been initialized for tenant '${tenantRecord.id}', creating one`);
68
+ tenantSessions.agentContext = await this.createAgentContext(tenantRecord);
69
+ }
70
+ // If we already have a context with sessions in place return the context and increment
71
+ // the session count.
72
+ tenantSessions.sessionCount++;
73
+ this.logger.debug(`Increased agent context session count for tenant '${tenantRecord.id}' to ${tenantSessions.sessionCount}`);
74
+ return tenantSessions.agentContext;
75
+ });
76
+ }
77
+ catch (error) {
78
+ this.logger.debug(`Releasing session because an error occurred while getting the context for tenant ${tenantRecord.id}`, {
79
+ errorMessage: error.message,
80
+ });
81
+ // If there was an error acquiring the session, we MUST release it, otherwise this will lead to deadlocks over time.
82
+ this.sessionMutex.releaseSession();
83
+ // Re-throw error
84
+ throw error;
85
+ }
86
+ }
87
+ /**
88
+ * End a session for the provided agent context. It will decrease the session count for the agent context.
89
+ * If the number of sessions is zero after the context for this session has been ended, the agent context will be closed.
90
+ */
91
+ async endAgentContextSession(agentContext) {
92
+ this.logger.debug(`Ending session for agent context with contextCorrelationId ${agentContext.contextCorrelationId}'`);
93
+ const hasTenantSessionMapping = this.hasTenantSessionMapping(agentContext.contextCorrelationId);
94
+ // Custom handling for the root agent context. We don't keep track of the total number of sessions for the root
95
+ // agent context, and we always keep the dependency manager intact.
96
+ if (!hasTenantSessionMapping && agentContext.contextCorrelationId === this.rootAgentContext.contextCorrelationId) {
97
+ this.logger.debug('Ending session for root agent context. Not disposing dependency manager');
98
+ return;
99
+ }
100
+ // This should not happen
101
+ if (!hasTenantSessionMapping) {
102
+ this.logger.error(`Unknown agent context with contextCorrelationId '${agentContext.contextCorrelationId}'. Cannot end session`);
103
+ throw new core_1.AriesFrameworkError(`Unknown agent context with contextCorrelationId '${agentContext.contextCorrelationId}'. Cannot end session`);
104
+ }
105
+ await this.mutexForTenant(agentContext.contextCorrelationId).runExclusive(async () => {
106
+ this.logger.debug(`Acquired lock for tenant '${agentContext.contextCorrelationId}' to end session context`);
107
+ const tenantSessions = this.getTenantSessionsMapping(agentContext.contextCorrelationId);
108
+ // TODO: check if session count is already 0
109
+ tenantSessions.sessionCount--;
110
+ this.logger.debug(`Decreased agent context session count for tenant '${agentContext.contextCorrelationId}' to ${tenantSessions.sessionCount}`);
111
+ if (tenantSessions.sessionCount <= 0 && tenantSessions.agentContext) {
112
+ await this.closeAgentContext(tenantSessions.agentContext);
113
+ delete this.tenantAgentContextMapping[agentContext.contextCorrelationId];
114
+ }
115
+ });
116
+ // Release a session so new sessions can be acquired
117
+ this.sessionMutex.releaseSession();
118
+ }
119
+ hasTenantSessionMapping(tenantId) {
120
+ return this.tenantAgentContextMapping[tenantId] !== undefined;
121
+ }
122
+ getTenantSessionsMapping(tenantId) {
123
+ let tenantSessionMapping = this.tenantAgentContextMapping[tenantId];
124
+ if (tenantSessionMapping)
125
+ return tenantSessionMapping;
126
+ tenantSessionMapping = {
127
+ sessionCount: 0,
128
+ mutex: (0, async_mutex_1.withTimeout)(new async_mutex_1.Mutex(),
129
+ // NOTE: It can take a while to create an indy wallet. We're using RAW key derivation which should
130
+ // be fast enough to not cause a problem. This wil also only be problem when the wallet is being created
131
+ // for the first time or being acquired while wallet initialization is in progress.
132
+ this.tenantsModuleConfig.sessionAcquireTimeout, new core_1.AriesFrameworkError(`Error acquiring lock for tenant ${tenantId}. Wallet initialization or shutdown took too long.`)),
133
+ };
134
+ this.tenantAgentContextMapping[tenantId] = tenantSessionMapping;
135
+ return tenantSessionMapping;
136
+ }
137
+ mutexForTenant(tenantId) {
138
+ const tenantSessions = this.getTenantSessionsMapping(tenantId);
139
+ return tenantSessions.mutex;
140
+ }
141
+ async createAgentContext(tenantRecord) {
142
+ var _a, _b;
143
+ const tenantDependencyManager = this.rootAgentContext.dependencyManager.createChild();
144
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
145
+ const _c = (_b = (_a = this.rootAgentContext.config) === null || _a === void 0 ? void 0 : _a.walletConfig) !== null && _b !== void 0 ? _b : {}, { id, key, keyDerivationMethod } = _c, strippedWalletConfig = __rest(_c, ["id", "key", "keyDerivationMethod"]);
146
+ const tenantConfig = this.rootAgentContext.config.extend(Object.assign(Object.assign({}, tenantRecord.config), { walletConfig: Object.assign(Object.assign({}, strippedWalletConfig), tenantRecord.config.walletConfig) }));
147
+ const agentContext = new core_1.AgentContext({
148
+ contextCorrelationId: tenantRecord.id,
149
+ dependencyManager: tenantDependencyManager,
150
+ });
151
+ tenantDependencyManager.registerInstance(core_1.AgentContext, agentContext);
152
+ tenantDependencyManager.registerInstance(core_1.AgentConfig, tenantConfig);
153
+ // NOTE: we're using the wallet api here because that correctly handle creating if it doesn't exist yet
154
+ // and will also write the storage version to the storage, which is needed by the update assistant. We either
155
+ // need to move this out of the module, or just keep using the module here.
156
+ const walletApi = agentContext.dependencyManager.resolve(core_1.WalletApi);
157
+ if (!tenantConfig.walletConfig) {
158
+ throw new core_1.WalletError('Cannot initialize tenant without Wallet config.');
159
+ }
160
+ await walletApi.initialize(tenantConfig.walletConfig);
161
+ return agentContext;
162
+ }
163
+ async closeAgentContext(agentContext) {
164
+ this.logger.debug(`Closing agent context for tenant '${agentContext.contextCorrelationId}'`);
165
+ await agentContext.dependencyManager.dispose();
166
+ }
167
+ };
168
+ TenantSessionCoordinator = __decorate([
169
+ (0, core_1.injectable)(),
170
+ __param(1, (0, core_1.inject)(core_1.InjectionSymbols.Logger)),
171
+ __metadata("design:paramtypes", [core_1.AgentContext, Object, TenantsModuleConfig_1.TenantsModuleConfig])
172
+ ], TenantSessionCoordinator);
173
+ exports.TenantSessionCoordinator = TenantSessionCoordinator;
174
+ //# sourceMappingURL=TenantSessionCoordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantSessionCoordinator.js","sourceRoot":"","sources":["../../src/context/TenantSessionCoordinator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { Logger } from '@credo-ts/core';
2
+ /**
3
+ * Keep track of the total number of tenant sessions currently active. This doesn't actually manage the tenant sessions itself, or have anything to do with
4
+ * the agent context. It merely counts the current number of sessions, and provides a mutex to lock new sessions from being created once the maximum number
5
+ * of sessions has been created. Session that can't be required withing the specified sessionsAcquireTimeout will throw an error.
6
+ */
7
+ export declare class TenantSessionMutex {
8
+ private _currentSessions;
9
+ readonly maxSessions: number;
10
+ private sessionMutex;
11
+ private logger;
12
+ constructor(logger: Logger, maxSessions: number | undefined, sessionAcquireTimeout: number);
13
+ /**
14
+ * Getter to retrieve the total number of current sessions.
15
+ */
16
+ get currentSessions(): number;
17
+ private set currentSessions(value);
18
+ /**
19
+ * Wait to acquire a session. Will use the session semaphore to keep total number of sessions limited.
20
+ * For each session that is acquired using this method, the sessions MUST be closed by calling `releaseSession`.
21
+ * Failing to do so can lead to deadlocks over time.
22
+ */
23
+ acquireSession(): Promise<void>;
24
+ /**
25
+ * Release a session from the session mutex. If the total number of current sessions drops below
26
+ * the max number of sessions, the session mutex will be released so new sessions can be started.
27
+ */
28
+ releaseSession(): void;
29
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TenantSessionMutex = void 0;
4
+ const core_1 = require("@credo-ts/core");
5
+ const async_mutex_1 = require("async-mutex");
6
+ /**
7
+ * Keep track of the total number of tenant sessions currently active. This doesn't actually manage the tenant sessions itself, or have anything to do with
8
+ * the agent context. It merely counts the current number of sessions, and provides a mutex to lock new sessions from being created once the maximum number
9
+ * of sessions has been created. Session that can't be required withing the specified sessionsAcquireTimeout will throw an error.
10
+ */
11
+ class TenantSessionMutex {
12
+ constructor(logger, maxSessions = Infinity, sessionAcquireTimeout) {
13
+ this._currentSessions = 0;
14
+ this.maxSessions = Infinity;
15
+ this.logger = logger;
16
+ this.maxSessions = maxSessions;
17
+ // Session mutex, it can take at most sessionAcquireTimeout to acquire a session, otherwise it will fail with the error below
18
+ this.sessionMutex = (0, async_mutex_1.withTimeout)(new async_mutex_1.Mutex(), sessionAcquireTimeout, new core_1.AriesFrameworkError(`Failed to acquire an agent context session within ${sessionAcquireTimeout}ms`));
19
+ }
20
+ /**
21
+ * Getter to retrieve the total number of current sessions.
22
+ */
23
+ get currentSessions() {
24
+ return this._currentSessions;
25
+ }
26
+ set currentSessions(value) {
27
+ this._currentSessions = value;
28
+ }
29
+ /**
30
+ * Wait to acquire a session. Will use the session semaphore to keep total number of sessions limited.
31
+ * For each session that is acquired using this method, the sessions MUST be closed by calling `releaseSession`.
32
+ * Failing to do so can lead to deadlocks over time.
33
+ */
34
+ async acquireSession() {
35
+ // TODO: We should update this to be weighted
36
+ // This will allow to weight sessions for contexts that already exist lower than sessions
37
+ // for contexts that need to be created (new injection container, wallet session etc..)
38
+ // E.g. opening a context could weigh 5, adding sessions to it would be 1 for each
39
+ this.logger.debug('Acquiring tenant session');
40
+ // If we're out of sessions, wait for one to be released.
41
+ if (this.sessionMutex.isLocked()) {
42
+ this.logger.debug('Session mutex is locked, waiting for it to unlock');
43
+ // FIXME: waitForUnlock doesn't work with withTimeout but provides a better API (would rather not acquire and lock)
44
+ // await this.sessionMutex.waitForUnlock()
45
+ // Workaround https://github.com/MatrixAI/js-async-locks/pull/3/files#diff-4ee6a7d91cb8428765713bc3045e1dda5d43214030657a9c04804e96d68778bfR46-R61
46
+ await this.sessionMutex.acquire();
47
+ if (this.currentSessions < this.maxSessions) {
48
+ this.sessionMutex.release();
49
+ }
50
+ }
51
+ this.logger.debug(`Increasing current session count to ${this.currentSessions + 1} (max: ${this.maxSessions})`);
52
+ // We have waited for the session to unlock,
53
+ this.currentSessions++;
54
+ // If we reached the limit we should lock the session mutex
55
+ if (this.currentSessions >= this.maxSessions) {
56
+ this.logger.debug(`Reached max number of sessions ${this.maxSessions}, locking mutex`);
57
+ await this.sessionMutex.acquire();
58
+ }
59
+ this.logger.debug(`Acquired tenant session (${this.currentSessions} / ${this.maxSessions})`);
60
+ }
61
+ /**
62
+ * Release a session from the session mutex. If the total number of current sessions drops below
63
+ * the max number of sessions, the session mutex will be released so new sessions can be started.
64
+ */
65
+ releaseSession() {
66
+ this.logger.debug('Releasing tenant session');
67
+ if (this.currentSessions > 0) {
68
+ this.logger.debug(`Decreasing current sessions to ${this.currentSessions - 1} (max: ${this.maxSessions})`);
69
+ this.currentSessions--;
70
+ }
71
+ else {
72
+ this.logger.warn('Total sessions is already at 0, and releasing a session should not happen in this case. Not decrementing current session count.');
73
+ }
74
+ // If the number of current sessions is lower than the max number of sessions we can release the mutex
75
+ if (this.sessionMutex.isLocked() && this.currentSessions < this.maxSessions) {
76
+ this.logger.debug(`Releasing session mutex as number of current sessions ${this.currentSessions} is below max number of sessions ${this.maxSessions}`);
77
+ // Even though marked as deprecated, it is not actually deprecated and will be kept
78
+ // https://github.com/DirtyHairy/async-mutex/issues/50#issuecomment-1007785141
79
+ this.sessionMutex.release();
80
+ }
81
+ }
82
+ }
83
+ exports.TenantSessionMutex = TenantSessionMutex;
84
+ //# sourceMappingURL=TenantSessionMutex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantSessionMutex.js","sourceRoot":"","sources":["../../src/context/TenantSessionMutex.ts"],"names":[],"mappings":"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"}
File without changes
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/context/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ export { TenantRecord, TenantRecordProps } from './repository/TenantRecord';
2
+ export * from './TenantsModule';
3
+ export * from './TenantsApi';
4
+ export * from './TenantsApiOptions';
5
+ export * from './TenantsModuleConfig';
package/build/index.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.TenantRecord = void 0;
18
+ var TenantRecord_1 = require("./repository/TenantRecord");
19
+ Object.defineProperty(exports, "TenantRecord", { enumerable: true, get: function () { return TenantRecord_1.TenantRecord; } });
20
+ __exportStar(require("./TenantsModule"), exports);
21
+ __exportStar(require("./TenantsApi"), exports);
22
+ __exportStar(require("./TenantsApiOptions"), exports);
23
+ __exportStar(require("./TenantsModuleConfig"), exports);
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,0DAA2E;AAAlE,4GAAA,YAAY,OAAA;AACrB,kDAA+B;AAC/B,+CAA4B;AAC5B,sDAAmC;AACnC,wDAAqC"}
@@ -0,0 +1,4 @@
1
+ import type { InitConfig, WalletConfig } from '@credo-ts/core';
2
+ export type TenantConfig = Pick<InitConfig, 'label' | 'connectionImageUrl'> & {
3
+ walletConfig: Pick<WalletConfig, 'id' | 'key' | 'keyDerivationMethod'>;
4
+ };
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=TenantConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantConfig.js","sourceRoot":"","sources":["../../src/models/TenantConfig.ts"],"names":[],"mappings":""}
@@ -0,0 +1,20 @@
1
+ import type { TenantConfig } from '../models/TenantConfig';
2
+ import type { RecordTags, TagsBase } from '@credo-ts/core';
3
+ import { BaseRecord } from '@credo-ts/core';
4
+ export type TenantRecordTags = RecordTags<TenantRecord>;
5
+ export interface TenantRecordProps {
6
+ id?: string;
7
+ createdAt?: Date;
8
+ config: TenantConfig;
9
+ tags?: TagsBase;
10
+ }
11
+ export declare class TenantRecord extends BaseRecord {
12
+ static readonly type = "TenantRecord";
13
+ readonly type = "TenantRecord";
14
+ config: TenantConfig;
15
+ constructor(props: TenantRecordProps);
16
+ getTags(): {
17
+ [x: string]: import("@credo-ts/core").TagValue;
18
+ [x: number]: never;
19
+ };
20
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TenantRecord = void 0;
4
+ const core_1 = require("@credo-ts/core");
5
+ class TenantRecord extends core_1.BaseRecord {
6
+ constructor(props) {
7
+ var _a, _b, _c;
8
+ super();
9
+ this.type = TenantRecord.type;
10
+ if (props) {
11
+ this.id = (_a = props.id) !== null && _a !== void 0 ? _a : core_1.utils.uuid();
12
+ this.createdAt = (_b = props.createdAt) !== null && _b !== void 0 ? _b : new Date();
13
+ this._tags = (_c = props.tags) !== null && _c !== void 0 ? _c : {};
14
+ this.config = props.config;
15
+ }
16
+ }
17
+ getTags() {
18
+ return Object.assign({}, this._tags);
19
+ }
20
+ }
21
+ exports.TenantRecord = TenantRecord;
22
+ TenantRecord.type = 'TenantRecord';
23
+ //# sourceMappingURL=TenantRecord.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TenantRecord.js","sourceRoot":"","sources":["../../src/repository/TenantRecord.ts"],"names":[],"mappings":";;;AAGA,yCAAkD;AAWlD,MAAa,YAAa,SAAQ,iBAAU;IAM1C,YAAmB,KAAwB;;QACzC,KAAK,EAAE,CAAA;QALO,SAAI,GAAG,YAAY,CAAC,IAAI,CAAA;QAOtC,IAAI,KAAK,EAAE;YACT,IAAI,CAAC,EAAE,GAAG,MAAA,KAAK,CAAC,EAAE,mCAAI,YAAK,CAAC,IAAI,EAAE,CAAA;YAClC,IAAI,CAAC,SAAS,GAAG,MAAA,KAAK,CAAC,SAAS,mCAAI,IAAI,IAAI,EAAE,CAAA;YAC9C,IAAI,CAAC,KAAK,GAAG,MAAA,KAAK,CAAC,IAAI,mCAAI,EAAE,CAAA;YAC7B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;SAC3B;IACH,CAAC;IAEM,OAAO;QACZ,yBACK,IAAI,CAAC,KAAK,EACd;IACH,CAAC;;AArBH,oCAsBC;AArBwB,iBAAI,GAAG,cAAc,CAAA"}
@@ -0,0 +1,5 @@
1
+ import { Repository, StorageService, EventEmitter } from '@credo-ts/core';
2
+ import { TenantRecord } from './TenantRecord';
3
+ export declare class TenantRepository extends Repository<TenantRecord> {
4
+ constructor(storageService: StorageService<TenantRecord>, eventEmitter: EventEmitter);
5
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.TenantRepository = void 0;
16
+ const core_1 = require("@credo-ts/core");
17
+ const TenantRecord_1 = require("./TenantRecord");
18
+ let TenantRepository = class TenantRepository extends core_1.Repository {
19
+ constructor(storageService, eventEmitter) {
20
+ super(TenantRecord_1.TenantRecord, storageService, eventEmitter);
21
+ }
22
+ };
23
+ TenantRepository = __decorate([
24
+ (0, core_1.injectable)(),
25
+ __param(0, (0, core_1.inject)(core_1.InjectionSymbols.StorageService)),
26
+ __metadata("design:paramtypes", [Object, core_1.EventEmitter])
27
+ ], TenantRepository);
28
+ exports.TenantRepository = TenantRepository;
29
+ //# sourceMappingURL=TenantRepository.js.map