@gitkraken/core-gitlens 0.10.1 → 0.10.2

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/CHANGELOG.md CHANGED
@@ -6,6 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.10.2] - 2026-10-05
10
+
11
+ ### Fixed
12
+
13
+ - Fixes reads that resolve the integration's own session rather than a pinned connection (a self-managed host read by `domain` alone, for one) reporting `no-connection` when the session had expired and the GitKraken API could not hand back its replacement (a `429` or `5xx` from `v1/provider-tokens`): the read now fails with the refresh's error, a `rate-limit` warning for a `429` and an `other` warning otherwise (a refresh cancelled by the host's timeout included). A forced re-sync no longer clears the cached session while it fetches the replacement, so a transient failure leaves it as it was and the integration stays connected; the next read refreshes it once the cloud answers again. The session-based lookups (`getAccountForEmail`, `getDefaultBranch`, `getPullRequestForBranch` and the like) answer nothing rather than reading with the expired token, and a pull request lookup asked to `throwOnError` throws the failure. Jira and Linear autolinks are not rebuilt with an expired session. `syncCloudConnection` now resolves its token fetch's failure instead of `undefined` (plus/integrations)
14
+ - Fixes the integration's session racing with itself: a resolution, a forced re-sync, a disconnect, a reauthentication, a connection switch and a refused token now change the session one at a time instead of each publishing what it read over what another settled, so a disconnect during a re-sync stands (it waits for the re-sync), a switch is not undone by the former primary, and a refused token stays refused. Reads keep using the cached session meanwhile. Storing and deleting a provider's tokens are serialized too, and a token read or fetched before a sign-out (a disconnect, a reauthentication, a removed connection) is no longer stored after it, so it does not reconnect on the next resolution or restart (#5948). `switchConnection` now returns a promise (plus/integrations)
15
+ - Fixes `getSession()` handing a direct consumer (code suggestions, cloud patches) an expired session's token while the GitKraken API could not refresh it: an expired session is now refreshed before it is returned, and nothing is returned when the refresh fails. `isConnected()` still reports the integration as connected without a request ([#5943](https://github.com/gitkraken/vscode-gitlens/issues/5943)) (plus/integrations)
16
+
9
17
  ## [0.10.1] - 2026-10-05
10
18
 
11
19
  ### Fixed
@@ -567,7 +575,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
567
575
 
568
576
  - Initial release. Bundles `@gitlens/utils`, `@gitlens/git`, `@gitlens/git-cli`, `@gitlens/ai`, and `@gitlens/git-github` into a single core npm package with subpath exports.
569
577
 
570
- [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.10.1...HEAD
578
+ [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.10.2...HEAD
579
+ [0.10.2]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.10.1...gitkraken:releases/core/v0.10.2
571
580
  [0.10.1]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.10.0...gitkraken:releases/core/v0.10.1
572
581
  [0.10.0]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.9.9...gitkraken:releases/core/v0.10.0
573
582
  [0.9.9]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.9.8...gitkraken:releases/core/v0.9.9
@@ -1,29 +1,10 @@
1
1
  import { Disposable } from "../../../utils/disposable.js";
2
2
  import { Event } from "../../../utils/event.js";
3
- import { CloudIntegrationAuthType, ConfiguredIntegrationDescriptor, ProviderAuthenticationSession } from "./models.js";
3
+ import { ConfiguredIntegrationDescriptor, ProviderAuthenticationSession } from "./models.js";
4
4
  import { IntegrationIds } from "../constants.js";
5
5
  import { IntegrationAuthenticationSessionDescriptor } from "./integrationAuthenticationProvider.js";
6
6
  import { IntegrationServiceContext } from "../context.js";
7
7
  //#region ../plus/integrations/src/authentication/configuredIntegrationService.d.ts
8
- interface StoredSession {
9
- id: string;
10
- accessToken: string;
11
- account?: {
12
- label?: string;
13
- displayName?: string;
14
- id: string;
15
- };
16
- scopes: string[];
17
- cloud?: boolean;
18
- type: CloudIntegrationAuthType | undefined;
19
- expiresAt?: string;
20
- domain?: string;
21
- /** See {@link ProviderAuthenticationSession.baseUrl}: the configured address, path included. */
22
- baseUrl?: string;
23
- protocol?: string;
24
- /** The provider app key paired with the token for providers whose client needs one (e.g. Trello). */
25
- appKey?: string;
26
- }
27
8
  export interface ConfiguredIntegrationsChangeEvent {
28
9
  readonly added: readonly IntegrationIds[];
29
10
  readonly removed: readonly IntegrationIds[];
@@ -32,6 +13,16 @@ export declare class ConfiguredIntegrationService implements Disposable {
32
13
  private readonly ctx;
33
14
  private readonly _onDidChange;
34
15
  private storeConfiguredQueue;
16
+ /**
17
+ * One provider's secret and descriptor mutations run one at a time, so a sign-out never interleaves with a
18
+ * token being stored: storing writes the secret and then registers its descriptor, and a delete in between would
19
+ * miss a connection it cannot see yet, or be undone by the pending write (#5948).
20
+ */
21
+ private readonly _locks;
22
+ /** Monotonic across providers; see {@link getSignOutMark}. */
23
+ private _signOutEpoch;
24
+ /** The epoch of the latest sign-out per scope: a provider, a self-managed host, or a single connection. */
25
+ private readonly _signOuts;
35
26
  get onDidChange(): Event<ConfiguredIntegrationsChangeEvent>;
36
27
  constructor(ctx: IntegrationServiceContext);
37
28
  dispose(): void;
@@ -44,11 +35,34 @@ export declare class ConfiguredIntegrationService implements Disposable {
44
35
  private storeConfigured;
45
36
  private addOrUpdateConfigured;
46
37
  private removeConfigured;
47
- storeSession(id: IntegrationIds, session: ProviderAuthenticationSession): Promise<void>;
38
+ private withLock;
39
+ /**
40
+ * Taken before a token is read or fetched, and handed to {@link storeSession}: a sign-out that lands after it
41
+ * (a disconnect, a reauthentication, the connection being removed) refuses the store, so a token fetched across
42
+ * it is not written back and reconnected on the next resolution (gitkraken/kepler#3546).
43
+ */
44
+ getSignOutMark(): number;
45
+ private markSignOut;
46
+ private getHostScope;
47
+ private isSignedOutSince;
48
+ /**
49
+ * Stores the session's secret and descriptor together. With a `signOutMark`, nothing is stored, and `false` is
50
+ * returned, when a sign-out covering the session landed after the mark was taken.
51
+ */
52
+ storeSession(id: IntegrationIds, session: ProviderAuthenticationSession, options?: {
53
+ signOutMark?: number;
54
+ }): Promise<boolean>;
48
55
  getStoredSession(id: IntegrationIds, descriptor: IntegrationAuthenticationSessionDescriptor): Promise<ProviderAuthenticationSession | undefined>;
56
+ /**
57
+ * Deletes one connection's stored session. `signOut` is for a delete that signs the integration out (a forced new
58
+ * session): it also refuses the store of any token of the provider, or of the descriptor's self-managed host,
59
+ * fetched before it (see {@link getSignOutMark}). A cleanup of a replaced token leaves it unset.
60
+ */
49
61
  deleteStoredSessions(id: IntegrationIds, descriptor: IntegrationAuthenticationSessionDescriptor, cloud?: boolean, options?: {
50
62
  preserveConfigured?: boolean;
63
+ signOut?: boolean;
51
64
  }): Promise<void>;
65
+ /** Signs out of every stored session of the provider, or of one self-managed host when `domain` is given. */
52
66
  deleteAllStoredSessions(id: IntegrationIds, cloud?: boolean, domain?: string): Promise<void>;
53
67
  /**
54
68
  * Reclaims all stored configuration + secrets for the given (typically retired) integration ids.
@@ -58,12 +72,10 @@ export declare class ConfiguredIntegrationService implements Disposable {
58
72
  * are a no-op.
59
73
  */
60
74
  purgeStoredConfiguration(ids: readonly string[]): Promise<void>;
61
- deleteSecrets(id: IntegrationIds, connectionId: string, cloud?: boolean, options?: {
62
- preserveConfigured?: boolean;
63
- }): Promise<void>;
64
- deleteAllSecrets(id: IntegrationIds, cloud?: boolean, domain?: string): Promise<void>;
65
- writeSecret(id: IntegrationIds, session: ProviderAuthenticationSession | StoredSession): Promise<void>;
66
- readSecret(id: IntegrationIds, sessionId: string, cloud?: boolean): Promise<StoredSession | undefined>;
75
+ private deleteSecrets;
76
+ private deleteAllSecrets;
77
+ private writeSecret;
78
+ private readSecret;
67
79
  private getSecretKey;
68
80
  private getLocalSecretKey;
69
81
  private getCloudSecretKey;
@@ -116,6 +128,7 @@ export declare class ConfiguredIntegrationService implements Disposable {
116
128
  * siblings. Persists immediately instead of relying on a session re-store to carry the primary flag.
117
129
  */
118
130
  setPrimaryConnection(id: IntegrationIds, connectionId: string): Promise<void>;
131
+ private setPrimaryConnectionCore;
119
132
  /**
120
133
  * Removes a single connection (its secret + configured descriptor). If it was the primary and other
121
134
  * connections remain, a secondary is promoted (see {@link removeConfigured}). Unlike
@@ -9,6 +9,16 @@ var ConfiguredIntegrationService = class {
9
9
  ctx;
10
10
  _onDidChange = new Emitter();
11
11
  storeConfiguredQueue = Promise.resolve();
12
+ /**
13
+ * One provider's secret and descriptor mutations run one at a time, so a sign-out never interleaves with a
14
+ * token being stored: storing writes the secret and then registers its descriptor, and a delete in between would
15
+ * miss a connection it cannot see yet, or be undone by the pending write (#5948).
16
+ */
17
+ _locks = /* @__PURE__ */ new Map();
18
+ /** Monotonic across providers; see {@link getSignOutMark}. */
19
+ _signOutEpoch = 0;
20
+ /** The epoch of the latest sign-out per scope: a provider, a self-managed host, or a single connection. */
21
+ _signOuts = /* @__PURE__ */ new Map();
12
22
  get onDidChange() {
13
23
  return this._onDidChange.event;
14
24
  }
@@ -112,30 +122,84 @@ var ConfiguredIntegrationService = class {
112
122
  this.fireChange(void 0, id);
113
123
  await this.storeConfigured();
114
124
  }
115
- async storeSession(id, session) {
116
- await this.writeSecret(id, session);
125
+ withLock(id, fn) {
126
+ const run = (this._locks.get(id) ?? Promise.resolve()).then(fn);
127
+ const settled = run.then(() => void 0, () => void 0);
128
+ this._locks.set(id, settled);
129
+ settled.then(() => {
130
+ if (this._locks.get(id) === settled) this._locks.delete(id);
131
+ });
132
+ return run;
133
+ }
134
+ /**
135
+ * Taken before a token is read or fetched, and handed to {@link storeSession}: a sign-out that lands after it
136
+ * (a disconnect, a reauthentication, the connection being removed) refuses the store, so a token fetched across
137
+ * it is not written back and reconnected on the next resolution (gitkraken/kepler#3546).
138
+ */
139
+ getSignOutMark() {
140
+ return this._signOutEpoch;
141
+ }
142
+ markSignOut(scope) {
143
+ this._signOuts.set(scope, ++this._signOutEpoch);
144
+ }
145
+ getHostScope(id, domain) {
146
+ return `${id}|host:${hostFromDomain(domain) ?? domain}`;
147
+ }
148
+ isSignedOutSince(id, session, mark) {
149
+ const since = (scope) => (this._signOuts.get(scope) ?? 0) > mark;
150
+ if (since(id)) return true;
151
+ if (isSelfManagedHostIntegrationId(id) && session.domain && since(this.getHostScope(id, session.domain))) return true;
152
+ return since(`${id}|connection:${session.id}`);
153
+ }
154
+ /**
155
+ * Stores the session's secret and descriptor together. With a `signOutMark`, nothing is stored, and `false` is
156
+ * returned, when a sign-out covering the session landed after the mark was taken.
157
+ */
158
+ storeSession(id, session, options) {
159
+ return this.withLock(id, async () => {
160
+ if (options?.signOutMark != null && this.isSignedOutSince(id, session, options.signOutMark)) return false;
161
+ await this.writeSecret(id, session);
162
+ return true;
163
+ });
117
164
  }
118
- async getStoredSession(id, descriptor) {
119
- const sessionId = this.resolveConnectionId(id, descriptor);
120
- let session = descriptor.cloud === true ? void 0 : await this.readSecret(id, sessionId, false);
121
- let cloudIfMissing = false;
122
- if (session != null) {
123
- if (session.expiresAt != null) {
165
+ getStoredSession(id, descriptor) {
166
+ return this.withLock(id, async () => {
167
+ const sessionId = this.resolveConnectionId(id, descriptor);
168
+ let session = descriptor.cloud === true ? void 0 : await this.readSecret(id, sessionId, false);
169
+ let cloudIfMissing = false;
170
+ if (session != null) {
171
+ if (session.expiresAt != null) {
172
+ cloudIfMissing = true;
173
+ try {
174
+ await this.deleteSecrets(id, session.id);
175
+ await this.writeSecret(id, session);
176
+ } catch {}
177
+ }
178
+ }
179
+ if (session == null && descriptor.cloud !== false) {
124
180
  cloudIfMissing = true;
125
- await Promise.allSettled([this.deleteSecrets(id, session.id), this.writeSecret(id, session)]);
181
+ session = await this.readSecret(id, sessionId, true);
126
182
  }
127
- }
128
- if (session == null && descriptor.cloud !== false) {
129
- cloudIfMissing = true;
130
- session = await this.readSecret(id, sessionId, true);
131
- }
132
- return convertStoredSessionToSession(session, descriptor, cloudIfMissing);
183
+ return convertStoredSessionToSession(session, descriptor, cloudIfMissing);
184
+ });
133
185
  }
134
- async deleteStoredSessions(id, descriptor, cloud, options) {
135
- await this.deleteSecrets(id, this.resolveConnectionId(id, descriptor), cloud, options);
186
+ /**
187
+ * Deletes one connection's stored session. `signOut` is for a delete that signs the integration out (a forced new
188
+ * session): it also refuses the store of any token of the provider, or of the descriptor's self-managed host,
189
+ * fetched before it (see {@link getSignOutMark}). A cleanup of a replaced token leaves it unset.
190
+ */
191
+ deleteStoredSessions(id, descriptor, cloud, options) {
192
+ return this.withLock(id, async () => {
193
+ if (options?.signOut) this.markSignOut(isSelfManagedHostIntegrationId(id) && descriptor.domain ? this.getHostScope(id, descriptor.domain) : id);
194
+ await this.deleteSecrets(id, this.resolveConnectionId(id, descriptor), cloud, options);
195
+ });
136
196
  }
137
- async deleteAllStoredSessions(id, cloud, domain) {
138
- await this.deleteAllSecrets(id, cloud, domain);
197
+ /** Signs out of every stored session of the provider, or of one self-managed host when `domain` is given. */
198
+ deleteAllStoredSessions(id, cloud, domain) {
199
+ return this.withLock(id, async () => {
200
+ this.markSignOut(isSelfManagedHostIntegrationId(id) && domain != null ? this.getHostScope(id, domain) : id);
201
+ await this.deleteAllSecrets(id, cloud, domain);
202
+ });
139
203
  }
140
204
  /**
141
205
  * Reclaims all stored configuration + secrets for the given (typically retired) integration ids.
@@ -317,7 +381,10 @@ var ConfiguredIntegrationService = class {
317
381
  * Marks the given connection as the primary/default for the provider and clears the flag on its
318
382
  * siblings. Persists immediately instead of relying on a session re-store to carry the primary flag.
319
383
  */
320
- async setPrimaryConnection(id, connectionId) {
384
+ setPrimaryConnection(id, connectionId) {
385
+ return this.withLock(id, () => this.setPrimaryConnectionCore(id, connectionId));
386
+ }
387
+ async setPrimaryConnectionCore(id, connectionId) {
321
388
  const descriptors = this.configured.get(id);
322
389
  if (descriptors == null || descriptors.length === 0) return;
323
390
  if (!descriptors.some((d) => d.id === connectionId)) return;
@@ -347,8 +414,11 @@ var ConfiguredIntegrationService = class {
347
414
  * {@link deleteAllStoredSessions}, this only affects the targeted connection. Pass `cloud` to scope
348
415
  * the removal to the cloud/local variant when a local PAT and a cloud session share a connection id.
349
416
  */
350
- async deleteConnection(id, connectionId, cloud) {
351
- await this.deleteSecrets(id, connectionId, cloud);
417
+ deleteConnection(id, connectionId, cloud) {
418
+ return this.withLock(id, async () => {
419
+ this.markSignOut(`${id}|connection:${connectionId}`);
420
+ await this.deleteSecrets(id, connectionId, cloud);
421
+ });
352
422
  }
353
423
  _addedIds = /* @__PURE__ */ new Set();
354
424
  _removedIds = /* @__PURE__ */ new Set();
@@ -1 +1 @@
1
- {"version":3,"file":"configuredIntegrationService.js","names":[],"sources":["../../../../../plus/integrations/src/authentication/configuredIntegrationService.ts"],"sourcesContent":["import { debounce } from '@gitlens/utils/debounce.js';\nimport type { Disposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { flatten } from '@gitlens/utils/iterable.js';\nimport type {\n\tIntegrationIds,\n\tStoredConfiguredIntegrationDescriptor,\n\tStoredIntegrationConfigurations,\n} from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport { providersMetadata } from '../providers/models.js';\nimport {\n\tareDomainsOnSameHost,\n\tbaseUrlFromDomain,\n\thostFromDomain,\n\tsameConfiguredBaseUrl,\n} from '../utils/domain.utils.js';\nimport { isSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport type { IntegrationAuthenticationSessionDescriptor } from './integrationAuthenticationProvider.js';\nimport type {\n\tCloudIntegrationAuthType,\n\tConfiguredIntegrationDescriptor,\n\tProviderAuthenticationSession,\n} from './models.js';\n\ninterface StoredSession {\n\tid: string;\n\taccessToken: string;\n\taccount?: { label?: string; displayName?: string; id: string };\n\tscopes: string[];\n\tcloud?: boolean;\n\ttype: CloudIntegrationAuthType | undefined;\n\texpiresAt?: string;\n\tdomain?: string;\n\t/** See {@link ProviderAuthenticationSession.baseUrl}: the configured address, path included. */\n\tbaseUrl?: string;\n\tprotocol?: string;\n\t/** The provider app key paired with the token for providers whose client needs one (e.g. Trello). */\n\tappKey?: string;\n}\n\nexport interface ConfiguredIntegrationsChangeEvent {\n\treadonly added: readonly IntegrationIds[];\n\treadonly removed: readonly IntegrationIds[];\n}\n\nexport class ConfiguredIntegrationService implements Disposable {\n\tprivate readonly _onDidChange = new Emitter<ConfiguredIntegrationsChangeEvent>();\n\tprivate storeConfiguredQueue: Promise<void> = Promise.resolve();\n\tget onDidChange(): Event<ConfiguredIntegrationsChangeEvent> {\n\t\treturn this._onDidChange.event;\n\t}\n\n\tconstructor(private readonly ctx: IntegrationServiceContext) {}\n\n\tdispose(): void {\n\t\tthis._onDidChange.dispose();\n\t}\n\n\tprivate _configured?: Map<IntegrationIds, ConfiguredIntegrationDescriptor[]>;\n\tprivate get configured(): Map<IntegrationIds, ConfiguredIntegrationDescriptor[]> {\n\t\tif (this._configured == null) {\n\t\t\tthis._configured = new Map<IntegrationIds, ConfiguredIntegrationDescriptor[]>();\n\n\t\t\tconst storedConfigured = this.ctx.storage.get('integrations:configured');\n\t\t\tfor (const [id, configured] of Object.entries(storedConfigured ?? {}) as [\n\t\t\t\tIntegrationIds,\n\t\t\t\tStoredConfiguredIntegrationDescriptor[],\n\t\t\t][]) {\n\t\t\t\tif (configured == null) continue;\n\n\t\t\t\tconst descriptors = configured.map(d => ({\n\t\t\t\t\t...d,\n\t\t\t\t\tbaseUrl: isSelfManagedHostIntegrationId(id) ? getStoredBaseUrl(d.baseUrl, d.domain) : d.baseUrl,\n\t\t\t\t\tdomain: this.normalizeConfiguredDomain(id, d.domain),\n\t\t\t\t\t// Backfill a stable connection id for pre-multi-account stored data: the domain for\n\t\t\t\t\t// self-managed hosts, or the provider's canonical domain for cloud (which is the legacy\n\t\t\t\t\t// secret-key session id), so existing secrets keep resolving with zero migration. Fall back\n\t\t\t\t\t// to the integration id (never empty) when no domain is known — self-managed providers carry\n\t\t\t\t\t// `domain: ''` in metadata, and an empty id would collide across connections and produce\n\t\t\t\t\t// ambiguous secret keys (`...|`).\n\t\t\t\t\tid: d.id || d.domain || providersMetadata[id]?.domain || id,\n\t\t\t\t\texpiresAt: d.expiresAt ? new Date(d.expiresAt) : undefined,\n\t\t\t\t}));\n\t\t\t\tthis._configured.set(id, descriptors);\n\t\t\t}\n\t\t}\n\n\t\treturn this._configured;\n\t}\n\n\t// Synchronous: reads the in-memory configured map (lazily hydrated from storage). No async work —\n\t// the old async variant only existed to weave in the built-in VS Code GitHub session, which is gone.\n\tgetConfigured(\n\t\tid?: IntegrationIds,\n\t\toptions?: { cloud?: boolean; domain?: string },\n\t): ConfiguredIntegrationDescriptor[] {\n\t\tconst descriptors: ConfiguredIntegrationDescriptor[] = [];\n\n\t\tconst configured =\n\t\t\tid != null\n\t\t\t\t? this.configured.get(id)\n\t\t\t\t: [...flatten<ConfiguredIntegrationDescriptor>(this.configured.values())];\n\t\tif (!configured?.length) return descriptors;\n\n\t\tif (options?.domain != null || options?.cloud != null) {\n\t\t\tfor (const descriptor of configured) {\n\t\t\t\tif (\n\t\t\t\t\t(options?.domain != null &&\n\t\t\t\t\t\t!this.domainsMatch(id ?? descriptor.integrationId, descriptor.domain, options.domain)) ||\n\t\t\t\t\t(options?.cloud === true && !descriptor.cloud) ||\n\t\t\t\t\t(options?.cloud === false && descriptor.cloud)\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tdescriptors.push(descriptor);\n\t\t\t}\n\t\t} else {\n\t\t\tdescriptors.push(...configured);\n\t\t}\n\n\t\treturn descriptors;\n\t}\n\n\tprivate async storeConfigured(): Promise<void> {\n\t\t// We need to convert the map to a record to store\n\t\tconst configured: StoredIntegrationConfigurations = {} as unknown as StoredIntegrationConfigurations;\n\t\tfor (const [id, descriptors] of this.configured) {\n\t\t\tconfigured[id] = descriptors.map(d => ({\n\t\t\t\t...d,\n\t\t\t\texpiresAt: d.expiresAt\n\t\t\t\t\t? d.expiresAt instanceof Date\n\t\t\t\t\t\t? d.expiresAt.toISOString()\n\t\t\t\t\t\t: d.expiresAt\n\t\t\t\t\t: undefined,\n\t\t\t}));\n\t\t}\n\n\t\tconst pending = this.storeConfiguredQueue.then(() =>\n\t\t\tthis.ctx.storage.store('integrations:configured', configured),\n\t\t);\n\t\tthis.storeConfiguredQueue = pending.catch(() => {});\n\t\tawait pending;\n\t}\n\n\tprivate async addOrUpdateConfigured(descriptor: ConfiguredIntegrationDescriptor): Promise<void> {\n\t\tdescriptor = {\n\t\t\t...descriptor,\n\t\t\tbaseUrl: isSelfManagedHostIntegrationId(descriptor.integrationId)\n\t\t\t\t? getStoredBaseUrl(descriptor.baseUrl, descriptor.domain)\n\t\t\t\t: descriptor.baseUrl,\n\t\t\tdomain: this.normalizeConfiguredDomain(descriptor.integrationId, descriptor.domain),\n\t\t};\n\t\tconst descriptors = this.configured.get(descriptor.integrationId) ?? [];\n\t\t// Key connections by their stable id (+ cloud, to preserve legacy local/cloud coexistence) so\n\t\t// multiple accounts on the same provider+domain no longer overwrite each other.\n\t\tconst existing = descriptors.find(d => d.id === descriptor.id && d.cloud === descriptor.cloud);\n\n\t\t// The first connection for a provider/domain is primary by default; an explicit flag (or the\n\t\t// existing value) always wins. Self-managed providers can span multiple hosts under the same\n\t\t// integration id, so each host needs its own primary.\n\t\tconst primaryScope = this.getPrimaryScope(descriptor.integrationId, descriptors, descriptor.domain);\n\t\tconst primary = descriptor.primary ?? existing?.primary ?? !primaryScope.some(d => d.primary);\n\t\t// Preserve a previously-resolved type/accountName when the incoming write doesn't carry them (e.g.\n\t\t// the model re-stores the primary session with an empty account, after reconcile enriched it).\n\t\tconst type = descriptor.type ?? existing?.type;\n\t\tconst accountName = descriptor.accountName ?? existing?.accountName;\n\t\tconst normalized: ConfiguredIntegrationDescriptor = {\n\t\t\t...descriptor,\n\t\t\tprimary: primary,\n\t\t\ttype: type,\n\t\t\taccountName: accountName,\n\t\t};\n\n\t\tlet changed: boolean;\n\t\tif (existing != null) {\n\t\t\t// `baseUrl` is compared alongside `domain`, not folded into it: `domain` is normalized to a bare\n\t\t\t// host, so a connection re-pointed to another context path on the SAME host is identical by domain\n\t\t\t// and would otherwise be treated as unchanged — leaving the stored descriptor, and every warm\n\t\t\t// integration listening for the change, on the old address.\n\t\t\tif (\n\t\t\t\texisting.domain === normalized.domain &&\n\t\t\t\tsameConfiguredBaseUrl(existing.baseUrl, normalized.baseUrl) &&\n\t\t\t\texisting.expiresAt === normalized.expiresAt &&\n\t\t\t\texisting.scopes === normalized.scopes &&\n\t\t\t\t(existing.primary ?? false) === (normalized.primary ?? false) &&\n\t\t\t\texisting.type === normalized.type &&\n\t\t\t\texisting.accountName === normalized.accountName\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Only fire the change event on address/scopes/primary/type/accountName changes (ignore expiresAt churn)\n\t\t\tchanged =\n\t\t\t\texisting.domain !== normalized.domain ||\n\t\t\t\t!sameConfiguredBaseUrl(existing.baseUrl, normalized.baseUrl) ||\n\t\t\t\texisting.scopes !== normalized.scopes ||\n\t\t\t\t(existing.primary ?? false) !== (normalized.primary ?? false) ||\n\t\t\t\texisting.type !== normalized.type ||\n\t\t\t\texisting.accountName !== normalized.accountName;\n\n\t\t\t// remove the existing descriptor from the array\n\t\t\tdescriptors.splice(descriptors.indexOf(existing), 1);\n\t\t} else {\n\t\t\tchanged = true;\n\t\t}\n\n\t\tdescriptors.push(normalized);\n\t\tthis.configured.set(descriptor.integrationId, descriptors);\n\n\t\tif (changed) {\n\t\t\tthis.fireChange(descriptor.integrationId);\n\t\t}\n\t\tawait this.storeConfigured();\n\t}\n\n\tprivate async removeConfigured(\n\t\tid: IntegrationIds,\n\t\toptions: { connectionId: string; cloud: boolean | undefined },\n\t): Promise<void> {\n\t\tconst existing = this.configured.get(id);\n\t\tif (existing == null || existing.length === 0) return;\n\n\t\tconst removedPrimaryDomains = new Set<string | undefined>();\n\t\tconst descriptors: ConfiguredIntegrationDescriptor[] = [];\n\t\tfor (const d of existing) {\n\t\t\tif (\n\t\t\t\td.id === options.connectionId &&\n\t\t\t\t(options?.cloud == null ||\n\t\t\t\t\t(options?.cloud === true && d.cloud === true) ||\n\t\t\t\t\t(options?.cloud === false && d.cloud === false))\n\t\t\t) {\n\t\t\t\tif (d.primary) {\n\t\t\t\t\tremovedPrimaryDomains.add(d.domain);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tdescriptors.push(d);\n\t\t}\n\n\t\tif (descriptors.length === existing.length) return; // nothing matched\n\n\t\t// Promote a secondary to primary when the removed connection was the primary and others remain\n\t\t// in the same primary scope, so the provider/host stays in a well-defined connected state.\n\t\tfor (const domain of removedPrimaryDomains) {\n\t\t\tif (descriptors.some(d => this.isInPrimaryScope(id, d, domain) && d.primary)) continue;\n\n\t\t\tconst index = descriptors.findIndex(d => this.isInPrimaryScope(id, d, domain));\n\t\t\tif (index !== -1) {\n\t\t\t\tdescriptors[index] = { ...descriptors[index], primary: true };\n\t\t\t}\n\t\t}\n\n\t\tthis.configured.set(id, descriptors);\n\t\tthis.fireChange(undefined, id);\n\t\tawait this.storeConfigured();\n\t}\n\n\tasync storeSession(id: IntegrationIds, session: ProviderAuthenticationSession): Promise<void> {\n\t\tawait this.writeSecret(id, session);\n\t}\n\n\tasync getStoredSession(\n\t\tid: IntegrationIds,\n\t\tdescriptor: IntegrationAuthenticationSessionDescriptor,\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst sessionId = this.resolveConnectionId(id, descriptor);\n\t\tlet session = descriptor.cloud === true ? undefined : await this.readSecret(id, sessionId, false);\n\n\t\tlet cloudIfMissing = false;\n\t\tif (session != null) {\n\t\t\t// Check the `expiresAt` field\n\t\t\t// If it has an expiresAt property and the key is the old type, then it's a cloud session,\n\t\t\t// so delete it from the local key and\n\t\t\t// store with the \"cloud\" type key, and then use that one.\n\t\t\t// Otherwise it's a local session under the local key, so just return it.\n\t\t\tif (session.expiresAt != null) {\n\t\t\t\tcloudIfMissing = true;\n\t\t\t\tawait Promise.allSettled([this.deleteSecrets(id, session.id), this.writeSecret(id, session)]);\n\t\t\t}\n\t\t}\n\n\t\t// If no local session we try to restore a session with the cloud key\n\t\tif (session == null && descriptor.cloud !== false) {\n\t\t\tcloudIfMissing = true;\n\t\t\tsession = await this.readSecret(id, sessionId, true);\n\t\t}\n\n\t\treturn convertStoredSessionToSession(session, descriptor, cloudIfMissing);\n\t}\n\n\tasync deleteStoredSessions(\n\t\tid: IntegrationIds,\n\t\tdescriptor: IntegrationAuthenticationSessionDescriptor,\n\t\tcloud?: boolean,\n\t\toptions?: { preserveConfigured?: boolean },\n\t): Promise<void> {\n\t\tawait this.deleteSecrets(id, this.resolveConnectionId(id, descriptor), cloud, options);\n\t}\n\n\tasync deleteAllStoredSessions(id: IntegrationIds, cloud?: boolean, domain?: string): Promise<void> {\n\t\tawait this.deleteAllSecrets(id, cloud, domain);\n\t}\n\n\t/**\n\t * Reclaims all stored configuration + secrets for the given (typically retired) integration ids.\n\t * Used to clean up state left behind when an id is removed from the model — e.g. the local\n\t * self-managed `github-enterprise`/`gitlab-self-hosted` ids dropped when integrations went cloud-only.\n\t * Domains are read from the stored config (secrets are keyed by domain); ids with no stored config\n\t * are a no-op.\n\t */\n\tasync purgeStoredConfiguration(ids: readonly string[]): Promise<void> {\n\t\tconst stored =\n\t\t\tthis.ctx.storage.get<Record<string, StoredConfiguredIntegrationDescriptor[] | undefined>>(\n\t\t\t\t'integrations:configured',\n\t\t\t);\n\t\tif (stored == null) return;\n\n\t\tlet changed = false;\n\t\tfor (const id of ids) {\n\t\t\tif (!(id in stored)) continue;\n\n\t\t\tfor (const descriptor of stored[id] ?? []) {\n\t\t\t\t// Mirror the hydration backfill so the secret keys we delete match what was written: fall back\n\t\t\t\t// through domain, the provider's canonical domain, then the integration id (never empty). A\n\t\t\t\t// legacy cloud descriptor stored neither id nor domain, so an empty connection id here would\n\t\t\t\t// miss the canonical-domain secret (e.g. `integration.auth.cloud:github|github.com`) and orphan it.\n\t\t\t\tconst connectionId =\n\t\t\t\t\tdescriptor.id || descriptor.domain || providersMetadata[id as IntegrationIds]?.domain || id;\n\t\t\t\tawait this.ctx.storage.deleteSecret(this.getLocalSecretKey(id as IntegrationIds, connectionId));\n\t\t\t\tawait this.ctx.storage.deleteSecret(this.getCloudSecretKey(id as IntegrationIds, connectionId));\n\t\t\t\tif (descriptor.domain) {\n\t\t\t\t\tawait this.ctx.storage.deleteWorkspace(`connected:${id}:${descriptor.domain}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tchanged = true;\n\t\t}\n\t\tif (!changed) return;\n\n\t\tconst remaining = Object.fromEntries(Object.entries(stored).filter(([key]) => !ids.includes(key)));\n\t\tawait this.ctx.storage.store('integrations:configured', remaining as StoredIntegrationConfigurations);\n\t}\n\n\tasync deleteSecrets(\n\t\tid: IntegrationIds,\n\t\tconnectionId: string,\n\t\tcloud?: boolean,\n\t\toptions?: { preserveConfigured?: boolean },\n\t): Promise<void> {\n\t\tif (cloud == null || cloud === false) {\n\t\t\tawait this.ctx.storage.deleteSecret(this.getLocalSecretKey(id, connectionId));\n\t\t}\n\n\t\tif (cloud == null || cloud === true) {\n\t\t\tawait this.ctx.storage.deleteSecret(this.getCloudSecretKey(id, connectionId));\n\t\t}\n\n\t\tif (options?.preserveConfigured) return;\n\n\t\tawait this.removeConfigured(id, { connectionId: connectionId, cloud: cloud });\n\t}\n\n\tasync deleteAllSecrets(id: IntegrationIds, cloud?: boolean, domain?: string): Promise<void> {\n\t\t// Delete every connection's secret (multi-account): secrets are keyed per connection id, so a\n\t\t// single canonical-domain delete would orphan secondary tokens. When a domain is given (self-managed\n\t\t// disconnect of one host), scope to that host so other hosts under the same provider id survive.\n\t\tconst descriptors = this.configured.get(id);\n\t\tconst connectionIds = [\n\t\t\t...new Set(\n\t\t\t\t(domain != null ? descriptors?.filter(c => this.domainsMatch(id, c.domain, domain)) : descriptors)?.map(\n\t\t\t\t\tc => c.id,\n\t\t\t\t) ?? [],\n\t\t\t),\n\t\t];\n\t\tif (connectionIds.length) {\n\t\t\tfor (const connectionId of connectionIds) {\n\t\t\t\tawait this.deleteSecrets(id, connectionId, cloud);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// A domain-scoped clear that matched nothing means this host has no stored connections, so it must be\n\t\t// a no-op. The canonical-domain fallback below would target a bogus key (self-managed canonical domain\n\t\t// is empty), so skip it.\n\t\tif (domain != null) return;\n\n\t\t// No configured connections (e.g. an orphaned secret with no descriptor): fall back to the\n\t\t// provider's canonical domain, which is the legacy session id for cloud providers. Mirror the\n\t\t// hydration backfill's `|| id` guard so a self-managed provider (empty canonical domain) doesn't\n\t\t// target an ambiguous empty connection id.\n\t\tawait this.deleteSecrets(id, providersMetadata[id]?.domain || id, cloud);\n\t}\n\n\tasync writeSecret(id: IntegrationIds, session: ProviderAuthenticationSession | StoredSession): Promise<void> {\n\t\tawait this.ctx.storage.storeSecret(\n\t\t\tthis.getSecretKey(id, session.id, session.cloud ?? false),\n\t\t\tJSON.stringify(session),\n\t\t);\n\n\t\tawait this.addOrUpdateConfigured({\n\t\t\tid: session.id,\n\t\t\tintegrationId: id,\n\t\t\tdomain: isSelfManagedHostIntegrationId(id) ? session.domain : undefined,\n\t\t\tbaseUrl: isSelfManagedHostIntegrationId(id) ? session.baseUrl : undefined,\n\t\t\texpiresAt: session.expiresAt,\n\t\t\tscopes: session.scopes.join(','),\n\t\t\tcloud: session.cloud ?? false,\n\t\t\ttype: session.type,\n\t\t\taccountName: session.account?.label || undefined,\n\t\t});\n\t}\n\n\tasync readSecret(\n\t\tid: IntegrationIds,\n\t\tsessionId: string,\n\t\tcloud: boolean = false,\n\t): Promise<StoredSession | undefined> {\n\t\tlet storedSession: StoredSession | undefined;\n\t\ttry {\n\t\t\tconst sessionJSON = await this.ctx.storage.getSecret(this.getSecretKey(id, sessionId, cloud));\n\t\t\tif (sessionJSON) {\n\t\t\t\tstoredSession = JSON.parse(sessionJSON);\n\t\t\t\tif (storedSession != null) {\n\t\t\t\t\tconst configured = this.configured.get(id);\n\t\t\t\t\tconst connectionId = storedSession.id ?? sessionId;\n\t\t\t\t\tconst sessionCloud = storedSession.cloud ?? cloud;\n\t\t\t\t\tconst domain = isSelfManagedHostIntegrationId(id)\n\t\t\t\t\t\t? (storedSession.domain ?? storedSession.id)\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tif (\n\t\t\t\t\t\tconfigured == null ||\n\t\t\t\t\t\tconfigured.length === 0 ||\n\t\t\t\t\t\t!configured.some(\n\t\t\t\t\t\t\tc => c.id === connectionId && c.integrationId === id && c.cloud === sessionCloud,\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\tawait this.addOrUpdateConfigured({\n\t\t\t\t\t\t\tid: connectionId,\n\t\t\t\t\t\t\tintegrationId: id,\n\t\t\t\t\t\t\tdomain: domain,\n\t\t\t\t\t\t\t// Rebuilt from the secret, so it must carry the address the session does; omitting it\n\t\t\t\t\t\t\t// leaves the descriptor claiming no context path and makes reconcile read every\n\t\t\t\t\t\t\t// routine check-in as a re-pointed connection.\n\t\t\t\t\t\t\tbaseUrl: isSelfManagedHostIntegrationId(id) ? storedSession.baseUrl : undefined,\n\t\t\t\t\t\t\texpiresAt: storedSession.expiresAt,\n\t\t\t\t\t\t\tscopes: storedSession.scopes.join(','),\n\t\t\t\t\t\t\tcloud: sessionCloud,\n\t\t\t\t\t\t\ttype: storedSession.type,\n\t\t\t\t\t\t\taccountName: storedSession.account?.label || undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (_ex) {\n\t\t\ttry {\n\t\t\t\tawait this.deleteSecrets(id, sessionId, cloud);\n\t\t\t} catch {}\n\t\t}\n\t\treturn storedSession;\n\t}\n\n\tprivate getSecretKey(\n\t\tid: IntegrationIds,\n\t\tsessionId: string,\n\t\tcloud: boolean = false,\n\t): `integration.auth:${IntegrationIds}|${string}` | `integration.auth.cloud:${IntegrationIds}|${string}` {\n\t\treturn cloud ? this.getCloudSecretKey(id, sessionId) : this.getLocalSecretKey(id, sessionId);\n\t}\n\n\tprivate getLocalSecretKey(id: IntegrationIds, sessionId: string): `integration.auth:${IntegrationIds}|${string}` {\n\t\treturn `integration.auth:${id}|${sessionId}`;\n\t}\n\n\tprivate getCloudSecretKey(\n\t\tid: IntegrationIds,\n\t\tsessionId: string,\n\t): `integration.auth.cloud:${IntegrationIds}|${string}` {\n\t\treturn `integration.auth.cloud:${id}|${sessionId}`;\n\t}\n\n\tprivate getPrimaryScope(\n\t\tid: IntegrationIds,\n\t\tdescriptors: ConfiguredIntegrationDescriptor[],\n\t\tdomain: string | undefined,\n\t): ConfiguredIntegrationDescriptor[] {\n\t\treturn isSelfManagedHostIntegrationId(id)\n\t\t\t? descriptors.filter(d => this.isInPrimaryScope(id, d, domain))\n\t\t\t: descriptors;\n\t}\n\n\tprivate isInPrimaryScope(\n\t\tid: IntegrationIds,\n\t\tdescriptor: ConfiguredIntegrationDescriptor,\n\t\tdomain: string | undefined,\n\t): boolean {\n\t\treturn !isSelfManagedHostIntegrationId(id) || this.domainsMatch(id, descriptor.domain, domain);\n\t}\n\n\t/**\n\t * Resolves the connection id used as the secret-key session id for an auth descriptor.\n\t * - If the descriptor targets a specific connection (`connectionId`), that wins.\n\t * - Otherwise returns the primary connection's id for the provider (matching the descriptor's domain\n\t * for self-managed hosts; any connection for cloud hosts, where the stored domain is undefined).\n\t * - Falls back to the descriptor domain when nothing is configured yet, preserving legacy\n\t * (pre-multi-account) reads with zero secret migration.\n\t */\n\tresolveConnectionId(id: IntegrationIds, descriptor: IntegrationAuthenticationSessionDescriptor): string {\n\t\t// An empty connectionId is not a real target — treat it as \"no target\" so it resolves the primary\n\t\t// rather than keying a secret under an empty id.\n\t\tif (descriptor.connectionId) return descriptor.connectionId;\n\n\t\tconst domain = isSelfManagedHostIntegrationId(id) ? descriptor.domain : undefined;\n\t\tconst candidates = this.scopeConnectionCandidates(id, domain, descriptor.cloud);\n\t\treturn (candidates?.find(c => c.primary) ?? candidates?.[0])?.id ?? descriptor.domain;\n\t}\n\n\t/**\n\t * Returns the id of the configured connection an unscoped descriptor resolves to (the primary for the\n\t * descriptor's domain, matching {@link resolveConnectionId}'s selection), or `undefined` when nothing is\n\t * configured for it. Unlike {@link resolveConnectionId} this only returns a genuine per-connection cloud\n\t * token id (never a local/PAT or domain-derived legacy id), so callers can safely use it to scope a cloud\n\t * token fetch to one host of a multi-host self-managed provider, or fall through to the provider-global\n\t * primary endpoint on `undefined`.\n\t */\n\t/**\n\t * The address already stored for a self-managed connection — see {@link ConfiguredIntegrationDescriptor.baseUrl}.\n\t *\n\t * Exists so a session rebuilt from a backend response that omits the domain can keep the address the\n\t * connection was configured with, rather than silently falling back to the bare host and losing a context\n\t * path the user never changed. Looked up by connection id first, then by host for an unscoped fetch.\n\t */\n\t/**\n\t * Whether `connectionId` names a cloud connection configured for `domain`'s host.\n\t *\n\t * The caller-facing question is \"is this id evidence that a token belongs to this host\". A connection id\n\t * can arrive from a caller rather than from our own configuration, so it is only evidence once it matches\n\t * a descriptor we stored for that host — see the host guard in `CloudIntegrationAuthenticationProvider`.\n\t */\n\tisConnectionConfiguredForHost(\n\t\tid: IntegrationIds,\n\t\tconnectionId: string | undefined,\n\t\tdomain: string | undefined,\n\t): boolean {\n\t\tif (connectionId == null) return false;\n\n\t\tconst connection = this.getConfigured(id, { cloud: true }).find(c => c.id === connectionId);\n\t\tif (connection == null) return false;\n\n\t\treturn !isSelfManagedHostIntegrationId(id) || this.domainsMatch(id, connection.domain, domain);\n\t}\n\n\tgetConfiguredBaseUrl(id: IntegrationIds, connectionId: string | undefined, domain?: string): string | undefined {\n\t\tif (!isSelfManagedHostIntegrationId(id)) return undefined;\n\n\t\tconst configured = this.getConfigured(id, { cloud: true });\n\t\tconst byId = connectionId != null ? configured.find(c => c.id === connectionId) : undefined;\n\t\tif (byId != null) return byId.baseUrl;\n\n\t\treturn configured.find(c => this.domainsMatch(id, c.domain, domain))?.baseUrl;\n\t}\n\n\tgetConfiguredConnectionId(id: IntegrationIds, domain: string | undefined, cloud?: boolean): string | undefined {\n\t\tconst scoped = isSelfManagedHostIntegrationId(id) ? domain : undefined;\n\t\tconst candidates = this.scopeConnectionCandidates(id, scoped, cloud);\n\t\tconst connection = candidates?.find(c => c.primary) ?? candidates?.[0];\n\t\tif (connection == null) return undefined;\n\n\t\t// Only return a real per-connection cloud token id. Reject a local (PAT) descriptor (surfaced by\n\t\t// scopeConnectionCandidates' non-cloud fallback) and a domain-derived legacy id (the descriptor's own\n\t\t// domain or the provider's canonical domain — see ConfiguredIntegrationDescriptor.id), either of which\n\t\t// would target `v1/provider-tokens/tokens/{domain}` (rejected as a non-uuid). Callers then fall through\n\t\t// to the provider-scoped primary endpoint.\n\t\tif (!connection.cloud) return undefined;\n\n\t\tconst canonicalDomain = providersMetadata[id]?.domain;\n\t\tif (connection.id === connection.domain || (canonicalDomain && connection.id === canonicalDomain)) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn connection.id;\n\t}\n\n\t/**\n\t * Selects configured descriptors for a provider matching `domain`, further narrowed to the `cloud`\n\t * variant when specified. A connection id can have both a local (PAT) and cloud descriptor whose ids\n\t * differ (e.g. a legacy local descriptor keyed by domain alongside a real cloud token id); scoping by\n\t * `cloud` keeps a cloud-scoped read/delete from resolving the local id (and vice versa). Falls back to\n\t * the domain-only set when no variant matches, so legacy single-variant resolution is unchanged.\n\t */\n\tprivate scopeConnectionCandidates(\n\t\tid: IntegrationIds,\n\t\tdomain: string | undefined,\n\t\tcloud: boolean | undefined,\n\t): ConfiguredIntegrationDescriptor[] | undefined {\n\t\tconst candidates = this.configured.get(id)?.filter(c => this.domainsMatch(id, c.domain, domain));\n\t\tif (cloud == null || candidates == null) return candidates;\n\n\t\tconst scoped = candidates.filter(c => c.cloud === cloud);\n\t\treturn scoped.length ? scoped : candidates;\n\t}\n\n\t/**\n\t * Marks the given connection as the primary/default for the provider and clears the flag on its\n\t * siblings. Persists immediately instead of relying on a session re-store to carry the primary flag.\n\t */\n\tasync setPrimaryConnection(id: IntegrationIds, connectionId: string): Promise<void> {\n\t\tconst descriptors = this.configured.get(id);\n\t\tif (descriptors == null || descriptors.length === 0) return;\n\t\tif (!descriptors.some(d => d.id === connectionId)) return;\n\n\t\tconst target =\n\t\t\tdescriptors.find(d => d.id === connectionId && d.cloud) ?? descriptors.find(d => d.id === connectionId);\n\t\tif (target == null) return;\n\n\t\tconst domain = isSelfManagedHostIntegrationId(id) ? target.domain : undefined;\n\t\t// A connection id can have both a local (PAT) and cloud descriptor. Mark the primary on a single\n\t\t// canonical variant (prefer cloud, since multi-account primaries are cloud-driven) within the\n\t\t// provider/host scope, so other self-managed hosts keep their own default connection.\n\t\tconst primaryIsCloud = descriptors.some(\n\t\t\td => d.id === connectionId && d.cloud && this.isInPrimaryScope(id, d, domain),\n\t\t);\n\n\t\tlet changed = false;\n\t\tconst updated = descriptors.map(d => {\n\t\t\tif (!this.isInPrimaryScope(id, d, domain)) return d;\n\n\t\t\tconst primary = d.id === connectionId && (d.cloud ?? false) === primaryIsCloud;\n\t\t\tif ((d.primary ?? false) === primary) return d;\n\n\t\t\tchanged = true;\n\t\t\treturn { ...d, primary: primary };\n\t\t});\n\t\tif (!changed) return;\n\n\t\tthis.configured.set(id, updated);\n\t\tthis.fireChange(id);\n\t\tawait this.storeConfigured();\n\t}\n\n\t/**\n\t * Removes a single connection (its secret + configured descriptor). If it was the primary and other\n\t * connections remain, a secondary is promoted (see {@link removeConfigured}). Unlike\n\t * {@link deleteAllStoredSessions}, this only affects the targeted connection. Pass `cloud` to scope\n\t * the removal to the cloud/local variant when a local PAT and a cloud session share a connection id.\n\t */\n\tasync deleteConnection(id: IntegrationIds, connectionId: string, cloud?: boolean): Promise<void> {\n\t\tawait this.deleteSecrets(id, connectionId, cloud);\n\t}\n\n\tprivate _addedIds = new Set<IntegrationIds>();\n\tprivate _removedIds = new Set<IntegrationIds>();\n\tprivate _fireChangeDebounced?: () => void;\n\n\tprivate normalizeConfiguredDomain(id: IntegrationIds, domain: string | undefined): string | undefined {\n\t\tif (!isSelfManagedHostIntegrationId(id)) return domain;\n\n\t\treturn hostFromDomain(domain) ?? domain;\n\t}\n\n\tprivate domainsMatch(id: IntegrationIds, first: string | undefined, second: string | undefined): boolean {\n\t\tif (first === second) return true;\n\t\tif (!isSelfManagedHostIntegrationId(id)) return false;\n\t\tif (first == null || second == null) return false;\n\n\t\treturn areDomainsOnSameHost(first, second);\n\t}\n\n\tprivate fireChange(added?: IntegrationIds, removed?: IntegrationIds) {\n\t\tthis._fireChangeDebounced ??= debounce(() => {\n\t\t\tconst added = [...this._addedIds];\n\t\t\tthis._addedIds.clear();\n\t\t\tconst removed = [...this._removedIds];\n\t\t\tthis._removedIds.clear();\n\n\t\t\tthis._onDidChange.fire({ added: added, removed: removed });\n\t\t}, 250);\n\n\t\tif (added != null) {\n\t\t\tthis._addedIds.add(added);\n\t\t}\n\t\tif (removed != null) {\n\t\t\tthis._removedIds.add(removed);\n\t\t}\n\t\tthis._fireChangeDebounced();\n\t}\n}\n\nfunction convertStoredSessionToSession(\n\tstoredSession: StoredSession | undefined,\n\tdescriptor: IntegrationAuthenticationSessionDescriptor,\n\tcloudIfMissing: boolean,\n): ProviderAuthenticationSession | undefined {\n\tif (storedSession == null) return undefined;\n\n\treturn {\n\t\tid: storedSession.id,\n\t\taccessToken: storedSession.accessToken,\n\t\taccount: {\n\t\t\tid: storedSession.account?.id ?? '',\n\t\t\tlabel: storedSession.account?.label ?? '',\n\t\t},\n\t\tscopes: storedSession.scopes,\n\t\tcloud: storedSession.cloud ?? cloudIfMissing,\n\t\texpiresAt: storedSession.expiresAt ? new Date(storedSession.expiresAt) : undefined,\n\t\tdomain: storedSession.domain ?? descriptor.domain,\n\t\tbaseUrl: getStoredBaseUrl(storedSession.baseUrl, storedSession.domain, storedSession.protocol),\n\t\tprotocol: storedSession.protocol,\n\t\ttype: storedSession.type,\n\t\t// Carried for providers whose client needs an app key alongside the token (e.g. Trello); without\n\t\t// this a rehydrated session silently loses the key and every read no-ops.\n\t\tappKey: storedSession.appKey,\n\t};\n}\n\nfunction getStoredBaseUrl(\n\tbaseUrl: string | undefined,\n\tdomain: string | undefined,\n\tprotocol?: string,\n): string | undefined {\n\tif (baseUrl != null) return baseUrl;\n\n\tconst legacyUrl = baseUrlFromDomain(domain, protocol);\n\treturn legacyUrl != null && new URL(legacyUrl).pathname !== '/' ? legacyUrl : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"configuredIntegrationService.js","names":[],"sources":["../../../../../plus/integrations/src/authentication/configuredIntegrationService.ts"],"sourcesContent":["import { debounce } from '@gitlens/utils/debounce.js';\nimport type { Disposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { flatten } from '@gitlens/utils/iterable.js';\nimport type {\n\tIntegrationIds,\n\tStoredConfiguredIntegrationDescriptor,\n\tStoredIntegrationConfigurations,\n} from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport { providersMetadata } from '../providers/models.js';\nimport {\n\tareDomainsOnSameHost,\n\tbaseUrlFromDomain,\n\thostFromDomain,\n\tsameConfiguredBaseUrl,\n} from '../utils/domain.utils.js';\nimport { isSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport type { IntegrationAuthenticationSessionDescriptor } from './integrationAuthenticationProvider.js';\nimport type {\n\tCloudIntegrationAuthType,\n\tConfiguredIntegrationDescriptor,\n\tProviderAuthenticationSession,\n} from './models.js';\n\ninterface StoredSession {\n\tid: string;\n\taccessToken: string;\n\taccount?: { label?: string; displayName?: string; id: string };\n\tscopes: string[];\n\tcloud?: boolean;\n\ttype: CloudIntegrationAuthType | undefined;\n\texpiresAt?: string;\n\tdomain?: string;\n\t/** See {@link ProviderAuthenticationSession.baseUrl}: the configured address, path included. */\n\tbaseUrl?: string;\n\tprotocol?: string;\n\t/** The provider app key paired with the token for providers whose client needs one (e.g. Trello). */\n\tappKey?: string;\n}\n\nexport interface ConfiguredIntegrationsChangeEvent {\n\treadonly added: readonly IntegrationIds[];\n\treadonly removed: readonly IntegrationIds[];\n}\n\nexport class ConfiguredIntegrationService implements Disposable {\n\tprivate readonly _onDidChange = new Emitter<ConfiguredIntegrationsChangeEvent>();\n\tprivate storeConfiguredQueue: Promise<void> = Promise.resolve();\n\t/**\n\t * One provider's secret and descriptor mutations run one at a time, so a sign-out never interleaves with a\n\t * token being stored: storing writes the secret and then registers its descriptor, and a delete in between would\n\t * miss a connection it cannot see yet, or be undone by the pending write (#5948).\n\t */\n\tprivate readonly _locks = new Map<IntegrationIds, Promise<void>>();\n\t/** Monotonic across providers; see {@link getSignOutMark}. */\n\tprivate _signOutEpoch = 0;\n\t/** The epoch of the latest sign-out per scope: a provider, a self-managed host, or a single connection. */\n\tprivate readonly _signOuts = new Map<string, number>();\n\tget onDidChange(): Event<ConfiguredIntegrationsChangeEvent> {\n\t\treturn this._onDidChange.event;\n\t}\n\n\tconstructor(private readonly ctx: IntegrationServiceContext) {}\n\n\tdispose(): void {\n\t\tthis._onDidChange.dispose();\n\t}\n\n\tprivate _configured?: Map<IntegrationIds, ConfiguredIntegrationDescriptor[]>;\n\tprivate get configured(): Map<IntegrationIds, ConfiguredIntegrationDescriptor[]> {\n\t\tif (this._configured == null) {\n\t\t\tthis._configured = new Map<IntegrationIds, ConfiguredIntegrationDescriptor[]>();\n\n\t\t\tconst storedConfigured = this.ctx.storage.get('integrations:configured');\n\t\t\tfor (const [id, configured] of Object.entries(storedConfigured ?? {}) as [\n\t\t\t\tIntegrationIds,\n\t\t\t\tStoredConfiguredIntegrationDescriptor[],\n\t\t\t][]) {\n\t\t\t\tif (configured == null) continue;\n\n\t\t\t\tconst descriptors = configured.map(d => ({\n\t\t\t\t\t...d,\n\t\t\t\t\tbaseUrl: isSelfManagedHostIntegrationId(id) ? getStoredBaseUrl(d.baseUrl, d.domain) : d.baseUrl,\n\t\t\t\t\tdomain: this.normalizeConfiguredDomain(id, d.domain),\n\t\t\t\t\t// Backfill a stable connection id for pre-multi-account stored data: the domain for\n\t\t\t\t\t// self-managed hosts, or the provider's canonical domain for cloud (which is the legacy\n\t\t\t\t\t// secret-key session id), so existing secrets keep resolving with zero migration. Fall back\n\t\t\t\t\t// to the integration id (never empty) when no domain is known — self-managed providers carry\n\t\t\t\t\t// `domain: ''` in metadata, and an empty id would collide across connections and produce\n\t\t\t\t\t// ambiguous secret keys (`...|`).\n\t\t\t\t\tid: d.id || d.domain || providersMetadata[id]?.domain || id,\n\t\t\t\t\texpiresAt: d.expiresAt ? new Date(d.expiresAt) : undefined,\n\t\t\t\t}));\n\t\t\t\tthis._configured.set(id, descriptors);\n\t\t\t}\n\t\t}\n\n\t\treturn this._configured;\n\t}\n\n\t// Synchronous: reads the in-memory configured map (lazily hydrated from storage). No async work —\n\t// the old async variant only existed to weave in the built-in VS Code GitHub session, which is gone.\n\tgetConfigured(\n\t\tid?: IntegrationIds,\n\t\toptions?: { cloud?: boolean; domain?: string },\n\t): ConfiguredIntegrationDescriptor[] {\n\t\tconst descriptors: ConfiguredIntegrationDescriptor[] = [];\n\n\t\tconst configured =\n\t\t\tid != null\n\t\t\t\t? this.configured.get(id)\n\t\t\t\t: [...flatten<ConfiguredIntegrationDescriptor>(this.configured.values())];\n\t\tif (!configured?.length) return descriptors;\n\n\t\tif (options?.domain != null || options?.cloud != null) {\n\t\t\tfor (const descriptor of configured) {\n\t\t\t\tif (\n\t\t\t\t\t(options?.domain != null &&\n\t\t\t\t\t\t!this.domainsMatch(id ?? descriptor.integrationId, descriptor.domain, options.domain)) ||\n\t\t\t\t\t(options?.cloud === true && !descriptor.cloud) ||\n\t\t\t\t\t(options?.cloud === false && descriptor.cloud)\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tdescriptors.push(descriptor);\n\t\t\t}\n\t\t} else {\n\t\t\tdescriptors.push(...configured);\n\t\t}\n\n\t\treturn descriptors;\n\t}\n\n\tprivate async storeConfigured(): Promise<void> {\n\t\t// We need to convert the map to a record to store\n\t\tconst configured: StoredIntegrationConfigurations = {} as unknown as StoredIntegrationConfigurations;\n\t\tfor (const [id, descriptors] of this.configured) {\n\t\t\tconfigured[id] = descriptors.map(d => ({\n\t\t\t\t...d,\n\t\t\t\texpiresAt: d.expiresAt\n\t\t\t\t\t? d.expiresAt instanceof Date\n\t\t\t\t\t\t? d.expiresAt.toISOString()\n\t\t\t\t\t\t: d.expiresAt\n\t\t\t\t\t: undefined,\n\t\t\t}));\n\t\t}\n\n\t\tconst pending = this.storeConfiguredQueue.then(() =>\n\t\t\tthis.ctx.storage.store('integrations:configured', configured),\n\t\t);\n\t\tthis.storeConfiguredQueue = pending.catch(() => {});\n\t\tawait pending;\n\t}\n\n\tprivate async addOrUpdateConfigured(descriptor: ConfiguredIntegrationDescriptor): Promise<void> {\n\t\tdescriptor = {\n\t\t\t...descriptor,\n\t\t\tbaseUrl: isSelfManagedHostIntegrationId(descriptor.integrationId)\n\t\t\t\t? getStoredBaseUrl(descriptor.baseUrl, descriptor.domain)\n\t\t\t\t: descriptor.baseUrl,\n\t\t\tdomain: this.normalizeConfiguredDomain(descriptor.integrationId, descriptor.domain),\n\t\t};\n\t\tconst descriptors = this.configured.get(descriptor.integrationId) ?? [];\n\t\t// Key connections by their stable id (+ cloud, to preserve legacy local/cloud coexistence) so\n\t\t// multiple accounts on the same provider+domain no longer overwrite each other.\n\t\tconst existing = descriptors.find(d => d.id === descriptor.id && d.cloud === descriptor.cloud);\n\n\t\t// The first connection for a provider/domain is primary by default; an explicit flag (or the\n\t\t// existing value) always wins. Self-managed providers can span multiple hosts under the same\n\t\t// integration id, so each host needs its own primary.\n\t\tconst primaryScope = this.getPrimaryScope(descriptor.integrationId, descriptors, descriptor.domain);\n\t\tconst primary = descriptor.primary ?? existing?.primary ?? !primaryScope.some(d => d.primary);\n\t\t// Preserve a previously-resolved type/accountName when the incoming write doesn't carry them (e.g.\n\t\t// the model re-stores the primary session with an empty account, after reconcile enriched it).\n\t\tconst type = descriptor.type ?? existing?.type;\n\t\tconst accountName = descriptor.accountName ?? existing?.accountName;\n\t\tconst normalized: ConfiguredIntegrationDescriptor = {\n\t\t\t...descriptor,\n\t\t\tprimary: primary,\n\t\t\ttype: type,\n\t\t\taccountName: accountName,\n\t\t};\n\n\t\tlet changed: boolean;\n\t\tif (existing != null) {\n\t\t\t// `baseUrl` is compared alongside `domain`, not folded into it: `domain` is normalized to a bare\n\t\t\t// host, so a connection re-pointed to another context path on the SAME host is identical by domain\n\t\t\t// and would otherwise be treated as unchanged — leaving the stored descriptor, and every warm\n\t\t\t// integration listening for the change, on the old address.\n\t\t\tif (\n\t\t\t\texisting.domain === normalized.domain &&\n\t\t\t\tsameConfiguredBaseUrl(existing.baseUrl, normalized.baseUrl) &&\n\t\t\t\texisting.expiresAt === normalized.expiresAt &&\n\t\t\t\texisting.scopes === normalized.scopes &&\n\t\t\t\t(existing.primary ?? false) === (normalized.primary ?? false) &&\n\t\t\t\texisting.type === normalized.type &&\n\t\t\t\texisting.accountName === normalized.accountName\n\t\t\t) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Only fire the change event on address/scopes/primary/type/accountName changes (ignore expiresAt churn)\n\t\t\tchanged =\n\t\t\t\texisting.domain !== normalized.domain ||\n\t\t\t\t!sameConfiguredBaseUrl(existing.baseUrl, normalized.baseUrl) ||\n\t\t\t\texisting.scopes !== normalized.scopes ||\n\t\t\t\t(existing.primary ?? false) !== (normalized.primary ?? false) ||\n\t\t\t\texisting.type !== normalized.type ||\n\t\t\t\texisting.accountName !== normalized.accountName;\n\n\t\t\t// remove the existing descriptor from the array\n\t\t\tdescriptors.splice(descriptors.indexOf(existing), 1);\n\t\t} else {\n\t\t\tchanged = true;\n\t\t}\n\n\t\tdescriptors.push(normalized);\n\t\tthis.configured.set(descriptor.integrationId, descriptors);\n\n\t\tif (changed) {\n\t\t\tthis.fireChange(descriptor.integrationId);\n\t\t}\n\t\tawait this.storeConfigured();\n\t}\n\n\tprivate async removeConfigured(\n\t\tid: IntegrationIds,\n\t\toptions: { connectionId: string; cloud: boolean | undefined },\n\t): Promise<void> {\n\t\tconst existing = this.configured.get(id);\n\t\tif (existing == null || existing.length === 0) return;\n\n\t\tconst removedPrimaryDomains = new Set<string | undefined>();\n\t\tconst descriptors: ConfiguredIntegrationDescriptor[] = [];\n\t\tfor (const d of existing) {\n\t\t\tif (\n\t\t\t\td.id === options.connectionId &&\n\t\t\t\t(options?.cloud == null ||\n\t\t\t\t\t(options?.cloud === true && d.cloud === true) ||\n\t\t\t\t\t(options?.cloud === false && d.cloud === false))\n\t\t\t) {\n\t\t\t\tif (d.primary) {\n\t\t\t\t\tremovedPrimaryDomains.add(d.domain);\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tdescriptors.push(d);\n\t\t}\n\n\t\tif (descriptors.length === existing.length) return; // nothing matched\n\n\t\t// Promote a secondary to primary when the removed connection was the primary and others remain\n\t\t// in the same primary scope, so the provider/host stays in a well-defined connected state.\n\t\tfor (const domain of removedPrimaryDomains) {\n\t\t\tif (descriptors.some(d => this.isInPrimaryScope(id, d, domain) && d.primary)) continue;\n\n\t\t\tconst index = descriptors.findIndex(d => this.isInPrimaryScope(id, d, domain));\n\t\t\tif (index !== -1) {\n\t\t\t\tdescriptors[index] = { ...descriptors[index], primary: true };\n\t\t\t}\n\t\t}\n\n\t\tthis.configured.set(id, descriptors);\n\t\tthis.fireChange(undefined, id);\n\t\tawait this.storeConfigured();\n\t}\n\n\tprivate withLock<T>(id: IntegrationIds, fn: () => Promise<T>): Promise<T> {\n\t\tconst run = (this._locks.get(id) ?? Promise.resolve()).then(fn);\n\t\tconst settled = run.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\tthis._locks.set(id, settled);\n\t\tvoid settled.then(() => {\n\t\t\tif (this._locks.get(id) === settled) {\n\t\t\t\tthis._locks.delete(id);\n\t\t\t}\n\t\t});\n\t\treturn run;\n\t}\n\n\t/**\n\t * Taken before a token is read or fetched, and handed to {@link storeSession}: a sign-out that lands after it\n\t * (a disconnect, a reauthentication, the connection being removed) refuses the store, so a token fetched across\n\t * it is not written back and reconnected on the next resolution (gitkraken/kepler#3546).\n\t */\n\tgetSignOutMark(): number {\n\t\treturn this._signOutEpoch;\n\t}\n\n\tprivate markSignOut(scope: string): void {\n\t\tthis._signOuts.set(scope, ++this._signOutEpoch);\n\t}\n\n\tprivate getHostScope(id: IntegrationIds, domain: string): string {\n\t\treturn `${id}|host:${hostFromDomain(domain) ?? domain}`;\n\t}\n\n\tprivate isSignedOutSince(id: IntegrationIds, session: ProviderAuthenticationSession, mark: number): boolean {\n\t\tconst since = (scope: string) => (this._signOuts.get(scope) ?? 0) > mark;\n\t\tif (since(id)) return true;\n\t\tif (isSelfManagedHostIntegrationId(id) && session.domain && since(this.getHostScope(id, session.domain))) {\n\t\t\treturn true;\n\t\t}\n\t\treturn since(`${id}|connection:${session.id}`);\n\t}\n\n\t/**\n\t * Stores the session's secret and descriptor together. With a `signOutMark`, nothing is stored, and `false` is\n\t * returned, when a sign-out covering the session landed after the mark was taken.\n\t */\n\tstoreSession(\n\t\tid: IntegrationIds,\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { signOutMark?: number },\n\t): Promise<boolean> {\n\t\treturn this.withLock(id, async () => {\n\t\t\tif (options?.signOutMark != null && this.isSignedOutSince(id, session, options.signOutMark)) return false;\n\n\t\t\tawait this.writeSecret(id, session);\n\t\t\treturn true;\n\t\t});\n\t}\n\n\tgetStoredSession(\n\t\tid: IntegrationIds,\n\t\tdescriptor: IntegrationAuthenticationSessionDescriptor,\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\t// Under the lock too: reading can repair storage (a legacy local-key cloud session, a missing descriptor).\n\t\treturn this.withLock(id, async () => {\n\t\t\tconst sessionId = this.resolveConnectionId(id, descriptor);\n\t\t\tlet session = descriptor.cloud === true ? undefined : await this.readSecret(id, sessionId, false);\n\n\t\t\tlet cloudIfMissing = false;\n\t\t\tif (session != null) {\n\t\t\t\t// Check the `expiresAt` field\n\t\t\t\t// If it has an expiresAt property and the key is the old type, then it's a cloud session,\n\t\t\t\t// so delete it from the local key and\n\t\t\t\t// store with the \"cloud\" type key, and then use that one.\n\t\t\t\t// Otherwise it's a local session under the local key, so just return it.\n\t\t\t\tif (session.expiresAt != null) {\n\t\t\t\t\tcloudIfMissing = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.deleteSecrets(id, session.id);\n\t\t\t\t\t\tawait this.writeSecret(id, session);\n\t\t\t\t\t} catch {}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If no local session we try to restore a session with the cloud key\n\t\t\tif (session == null && descriptor.cloud !== false) {\n\t\t\t\tcloudIfMissing = true;\n\t\t\t\tsession = await this.readSecret(id, sessionId, true);\n\t\t\t}\n\n\t\t\treturn convertStoredSessionToSession(session, descriptor, cloudIfMissing);\n\t\t});\n\t}\n\n\t/**\n\t * Deletes one connection's stored session. `signOut` is for a delete that signs the integration out (a forced new\n\t * session): it also refuses the store of any token of the provider, or of the descriptor's self-managed host,\n\t * fetched before it (see {@link getSignOutMark}). A cleanup of a replaced token leaves it unset.\n\t */\n\tdeleteStoredSessions(\n\t\tid: IntegrationIds,\n\t\tdescriptor: IntegrationAuthenticationSessionDescriptor,\n\t\tcloud?: boolean,\n\t\toptions?: { preserveConfigured?: boolean; signOut?: boolean },\n\t): Promise<void> {\n\t\treturn this.withLock(id, async () => {\n\t\t\tif (options?.signOut) {\n\t\t\t\tthis.markSignOut(\n\t\t\t\t\tisSelfManagedHostIntegrationId(id) && descriptor.domain\n\t\t\t\t\t\t? this.getHostScope(id, descriptor.domain)\n\t\t\t\t\t\t: id,\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait this.deleteSecrets(id, this.resolveConnectionId(id, descriptor), cloud, options);\n\t\t});\n\t}\n\n\t/** Signs out of every stored session of the provider, or of one self-managed host when `domain` is given. */\n\tdeleteAllStoredSessions(id: IntegrationIds, cloud?: boolean, domain?: string): Promise<void> {\n\t\treturn this.withLock(id, async () => {\n\t\t\tthis.markSignOut(isSelfManagedHostIntegrationId(id) && domain != null ? this.getHostScope(id, domain) : id);\n\t\t\tawait this.deleteAllSecrets(id, cloud, domain);\n\t\t});\n\t}\n\n\t/**\n\t * Reclaims all stored configuration + secrets for the given (typically retired) integration ids.\n\t * Used to clean up state left behind when an id is removed from the model — e.g. the local\n\t * self-managed `github-enterprise`/`gitlab-self-hosted` ids dropped when integrations went cloud-only.\n\t * Domains are read from the stored config (secrets are keyed by domain); ids with no stored config\n\t * are a no-op.\n\t */\n\tasync purgeStoredConfiguration(ids: readonly string[]): Promise<void> {\n\t\tconst stored =\n\t\t\tthis.ctx.storage.get<Record<string, StoredConfiguredIntegrationDescriptor[] | undefined>>(\n\t\t\t\t'integrations:configured',\n\t\t\t);\n\t\tif (stored == null) return;\n\n\t\tlet changed = false;\n\t\tfor (const id of ids) {\n\t\t\tif (!(id in stored)) continue;\n\n\t\t\tfor (const descriptor of stored[id] ?? []) {\n\t\t\t\t// Mirror the hydration backfill so the secret keys we delete match what was written: fall back\n\t\t\t\t// through domain, the provider's canonical domain, then the integration id (never empty). A\n\t\t\t\t// legacy cloud descriptor stored neither id nor domain, so an empty connection id here would\n\t\t\t\t// miss the canonical-domain secret (e.g. `integration.auth.cloud:github|github.com`) and orphan it.\n\t\t\t\tconst connectionId =\n\t\t\t\t\tdescriptor.id || descriptor.domain || providersMetadata[id as IntegrationIds]?.domain || id;\n\t\t\t\tawait this.ctx.storage.deleteSecret(this.getLocalSecretKey(id as IntegrationIds, connectionId));\n\t\t\t\tawait this.ctx.storage.deleteSecret(this.getCloudSecretKey(id as IntegrationIds, connectionId));\n\t\t\t\tif (descriptor.domain) {\n\t\t\t\t\tawait this.ctx.storage.deleteWorkspace(`connected:${id}:${descriptor.domain}`);\n\t\t\t\t}\n\t\t\t}\n\t\t\tchanged = true;\n\t\t}\n\t\tif (!changed) return;\n\n\t\tconst remaining = Object.fromEntries(Object.entries(stored).filter(([key]) => !ids.includes(key)));\n\t\tawait this.ctx.storage.store('integrations:configured', remaining as StoredIntegrationConfigurations);\n\t}\n\n\tprivate async deleteSecrets(\n\t\tid: IntegrationIds,\n\t\tconnectionId: string,\n\t\tcloud?: boolean,\n\t\toptions?: { preserveConfigured?: boolean },\n\t): Promise<void> {\n\t\tif (cloud == null || cloud === false) {\n\t\t\tawait this.ctx.storage.deleteSecret(this.getLocalSecretKey(id, connectionId));\n\t\t}\n\n\t\tif (cloud == null || cloud === true) {\n\t\t\tawait this.ctx.storage.deleteSecret(this.getCloudSecretKey(id, connectionId));\n\t\t}\n\n\t\tif (options?.preserveConfigured) return;\n\n\t\tawait this.removeConfigured(id, { connectionId: connectionId, cloud: cloud });\n\t}\n\n\tprivate async deleteAllSecrets(id: IntegrationIds, cloud?: boolean, domain?: string): Promise<void> {\n\t\t// Delete every connection's secret (multi-account): secrets are keyed per connection id, so a\n\t\t// single canonical-domain delete would orphan secondary tokens. When a domain is given (self-managed\n\t\t// disconnect of one host), scope to that host so other hosts under the same provider id survive.\n\t\tconst descriptors = this.configured.get(id);\n\t\tconst connectionIds = [\n\t\t\t...new Set(\n\t\t\t\t(domain != null ? descriptors?.filter(c => this.domainsMatch(id, c.domain, domain)) : descriptors)?.map(\n\t\t\t\t\tc => c.id,\n\t\t\t\t) ?? [],\n\t\t\t),\n\t\t];\n\t\tif (connectionIds.length) {\n\t\t\tfor (const connectionId of connectionIds) {\n\t\t\t\tawait this.deleteSecrets(id, connectionId, cloud);\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\t// A domain-scoped clear that matched nothing means this host has no stored connections, so it must be\n\t\t// a no-op. The canonical-domain fallback below would target a bogus key (self-managed canonical domain\n\t\t// is empty), so skip it.\n\t\tif (domain != null) return;\n\n\t\t// No configured connections (e.g. an orphaned secret with no descriptor): fall back to the\n\t\t// provider's canonical domain, which is the legacy session id for cloud providers. Mirror the\n\t\t// hydration backfill's `|| id` guard so a self-managed provider (empty canonical domain) doesn't\n\t\t// target an ambiguous empty connection id.\n\t\tawait this.deleteSecrets(id, providersMetadata[id]?.domain || id, cloud);\n\t}\n\n\tprivate async writeSecret(\n\t\tid: IntegrationIds,\n\t\tsession: ProviderAuthenticationSession | StoredSession,\n\t): Promise<void> {\n\t\tawait this.ctx.storage.storeSecret(\n\t\t\tthis.getSecretKey(id, session.id, session.cloud ?? false),\n\t\t\tJSON.stringify(session),\n\t\t);\n\n\t\tawait this.addOrUpdateConfigured({\n\t\t\tid: session.id,\n\t\t\tintegrationId: id,\n\t\t\tdomain: isSelfManagedHostIntegrationId(id) ? session.domain : undefined,\n\t\t\tbaseUrl: isSelfManagedHostIntegrationId(id) ? session.baseUrl : undefined,\n\t\t\texpiresAt: session.expiresAt,\n\t\t\tscopes: session.scopes.join(','),\n\t\t\tcloud: session.cloud ?? false,\n\t\t\ttype: session.type,\n\t\t\taccountName: session.account?.label || undefined,\n\t\t});\n\t}\n\n\tprivate async readSecret(\n\t\tid: IntegrationIds,\n\t\tsessionId: string,\n\t\tcloud: boolean = false,\n\t): Promise<StoredSession | undefined> {\n\t\tlet storedSession: StoredSession | undefined;\n\t\ttry {\n\t\t\tconst sessionJSON = await this.ctx.storage.getSecret(this.getSecretKey(id, sessionId, cloud));\n\t\t\tif (sessionJSON) {\n\t\t\t\tstoredSession = JSON.parse(sessionJSON);\n\t\t\t\tif (storedSession != null) {\n\t\t\t\t\tconst configured = this.configured.get(id);\n\t\t\t\t\tconst connectionId = storedSession.id ?? sessionId;\n\t\t\t\t\tconst sessionCloud = storedSession.cloud ?? cloud;\n\t\t\t\t\tconst domain = isSelfManagedHostIntegrationId(id)\n\t\t\t\t\t\t? (storedSession.domain ?? storedSession.id)\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tif (\n\t\t\t\t\t\tconfigured == null ||\n\t\t\t\t\t\tconfigured.length === 0 ||\n\t\t\t\t\t\t!configured.some(\n\t\t\t\t\t\t\tc => c.id === connectionId && c.integrationId === id && c.cloud === sessionCloud,\n\t\t\t\t\t\t)\n\t\t\t\t\t) {\n\t\t\t\t\t\tawait this.addOrUpdateConfigured({\n\t\t\t\t\t\t\tid: connectionId,\n\t\t\t\t\t\t\tintegrationId: id,\n\t\t\t\t\t\t\tdomain: domain,\n\t\t\t\t\t\t\t// Rebuilt from the secret, so it must carry the address the session does; omitting it\n\t\t\t\t\t\t\t// leaves the descriptor claiming no context path and makes reconcile read every\n\t\t\t\t\t\t\t// routine check-in as a re-pointed connection.\n\t\t\t\t\t\t\tbaseUrl: isSelfManagedHostIntegrationId(id) ? storedSession.baseUrl : undefined,\n\t\t\t\t\t\t\texpiresAt: storedSession.expiresAt,\n\t\t\t\t\t\t\tscopes: storedSession.scopes.join(','),\n\t\t\t\t\t\t\tcloud: sessionCloud,\n\t\t\t\t\t\t\ttype: storedSession.type,\n\t\t\t\t\t\t\taccountName: storedSession.account?.label || undefined,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (_ex) {\n\t\t\ttry {\n\t\t\t\tawait this.deleteSecrets(id, sessionId, cloud);\n\t\t\t} catch {}\n\t\t}\n\t\treturn storedSession;\n\t}\n\n\tprivate getSecretKey(\n\t\tid: IntegrationIds,\n\t\tsessionId: string,\n\t\tcloud: boolean = false,\n\t): `integration.auth:${IntegrationIds}|${string}` | `integration.auth.cloud:${IntegrationIds}|${string}` {\n\t\treturn cloud ? this.getCloudSecretKey(id, sessionId) : this.getLocalSecretKey(id, sessionId);\n\t}\n\n\tprivate getLocalSecretKey(id: IntegrationIds, sessionId: string): `integration.auth:${IntegrationIds}|${string}` {\n\t\treturn `integration.auth:${id}|${sessionId}`;\n\t}\n\n\tprivate getCloudSecretKey(\n\t\tid: IntegrationIds,\n\t\tsessionId: string,\n\t): `integration.auth.cloud:${IntegrationIds}|${string}` {\n\t\treturn `integration.auth.cloud:${id}|${sessionId}`;\n\t}\n\n\tprivate getPrimaryScope(\n\t\tid: IntegrationIds,\n\t\tdescriptors: ConfiguredIntegrationDescriptor[],\n\t\tdomain: string | undefined,\n\t): ConfiguredIntegrationDescriptor[] {\n\t\treturn isSelfManagedHostIntegrationId(id)\n\t\t\t? descriptors.filter(d => this.isInPrimaryScope(id, d, domain))\n\t\t\t: descriptors;\n\t}\n\n\tprivate isInPrimaryScope(\n\t\tid: IntegrationIds,\n\t\tdescriptor: ConfiguredIntegrationDescriptor,\n\t\tdomain: string | undefined,\n\t): boolean {\n\t\treturn !isSelfManagedHostIntegrationId(id) || this.domainsMatch(id, descriptor.domain, domain);\n\t}\n\n\t/**\n\t * Resolves the connection id used as the secret-key session id for an auth descriptor.\n\t * - If the descriptor targets a specific connection (`connectionId`), that wins.\n\t * - Otherwise returns the primary connection's id for the provider (matching the descriptor's domain\n\t * for self-managed hosts; any connection for cloud hosts, where the stored domain is undefined).\n\t * - Falls back to the descriptor domain when nothing is configured yet, preserving legacy\n\t * (pre-multi-account) reads with zero secret migration.\n\t */\n\tresolveConnectionId(id: IntegrationIds, descriptor: IntegrationAuthenticationSessionDescriptor): string {\n\t\t// An empty connectionId is not a real target — treat it as \"no target\" so it resolves the primary\n\t\t// rather than keying a secret under an empty id.\n\t\tif (descriptor.connectionId) return descriptor.connectionId;\n\n\t\tconst domain = isSelfManagedHostIntegrationId(id) ? descriptor.domain : undefined;\n\t\tconst candidates = this.scopeConnectionCandidates(id, domain, descriptor.cloud);\n\t\treturn (candidates?.find(c => c.primary) ?? candidates?.[0])?.id ?? descriptor.domain;\n\t}\n\n\t/**\n\t * Returns the id of the configured connection an unscoped descriptor resolves to (the primary for the\n\t * descriptor's domain, matching {@link resolveConnectionId}'s selection), or `undefined` when nothing is\n\t * configured for it. Unlike {@link resolveConnectionId} this only returns a genuine per-connection cloud\n\t * token id (never a local/PAT or domain-derived legacy id), so callers can safely use it to scope a cloud\n\t * token fetch to one host of a multi-host self-managed provider, or fall through to the provider-global\n\t * primary endpoint on `undefined`.\n\t */\n\t/**\n\t * The address already stored for a self-managed connection — see {@link ConfiguredIntegrationDescriptor.baseUrl}.\n\t *\n\t * Exists so a session rebuilt from a backend response that omits the domain can keep the address the\n\t * connection was configured with, rather than silently falling back to the bare host and losing a context\n\t * path the user never changed. Looked up by connection id first, then by host for an unscoped fetch.\n\t */\n\t/**\n\t * Whether `connectionId` names a cloud connection configured for `domain`'s host.\n\t *\n\t * The caller-facing question is \"is this id evidence that a token belongs to this host\". A connection id\n\t * can arrive from a caller rather than from our own configuration, so it is only evidence once it matches\n\t * a descriptor we stored for that host — see the host guard in `CloudIntegrationAuthenticationProvider`.\n\t */\n\tisConnectionConfiguredForHost(\n\t\tid: IntegrationIds,\n\t\tconnectionId: string | undefined,\n\t\tdomain: string | undefined,\n\t): boolean {\n\t\tif (connectionId == null) return false;\n\n\t\tconst connection = this.getConfigured(id, { cloud: true }).find(c => c.id === connectionId);\n\t\tif (connection == null) return false;\n\n\t\treturn !isSelfManagedHostIntegrationId(id) || this.domainsMatch(id, connection.domain, domain);\n\t}\n\n\tgetConfiguredBaseUrl(id: IntegrationIds, connectionId: string | undefined, domain?: string): string | undefined {\n\t\tif (!isSelfManagedHostIntegrationId(id)) return undefined;\n\n\t\tconst configured = this.getConfigured(id, { cloud: true });\n\t\tconst byId = connectionId != null ? configured.find(c => c.id === connectionId) : undefined;\n\t\tif (byId != null) return byId.baseUrl;\n\n\t\treturn configured.find(c => this.domainsMatch(id, c.domain, domain))?.baseUrl;\n\t}\n\n\tgetConfiguredConnectionId(id: IntegrationIds, domain: string | undefined, cloud?: boolean): string | undefined {\n\t\tconst scoped = isSelfManagedHostIntegrationId(id) ? domain : undefined;\n\t\tconst candidates = this.scopeConnectionCandidates(id, scoped, cloud);\n\t\tconst connection = candidates?.find(c => c.primary) ?? candidates?.[0];\n\t\tif (connection == null) return undefined;\n\n\t\t// Only return a real per-connection cloud token id. Reject a local (PAT) descriptor (surfaced by\n\t\t// scopeConnectionCandidates' non-cloud fallback) and a domain-derived legacy id (the descriptor's own\n\t\t// domain or the provider's canonical domain — see ConfiguredIntegrationDescriptor.id), either of which\n\t\t// would target `v1/provider-tokens/tokens/{domain}` (rejected as a non-uuid). Callers then fall through\n\t\t// to the provider-scoped primary endpoint.\n\t\tif (!connection.cloud) return undefined;\n\n\t\tconst canonicalDomain = providersMetadata[id]?.domain;\n\t\tif (connection.id === connection.domain || (canonicalDomain && connection.id === canonicalDomain)) {\n\t\t\treturn undefined;\n\t\t}\n\t\treturn connection.id;\n\t}\n\n\t/**\n\t * Selects configured descriptors for a provider matching `domain`, further narrowed to the `cloud`\n\t * variant when specified. A connection id can have both a local (PAT) and cloud descriptor whose ids\n\t * differ (e.g. a legacy local descriptor keyed by domain alongside a real cloud token id); scoping by\n\t * `cloud` keeps a cloud-scoped read/delete from resolving the local id (and vice versa). Falls back to\n\t * the domain-only set when no variant matches, so legacy single-variant resolution is unchanged.\n\t */\n\tprivate scopeConnectionCandidates(\n\t\tid: IntegrationIds,\n\t\tdomain: string | undefined,\n\t\tcloud: boolean | undefined,\n\t): ConfiguredIntegrationDescriptor[] | undefined {\n\t\tconst candidates = this.configured.get(id)?.filter(c => this.domainsMatch(id, c.domain, domain));\n\t\tif (cloud == null || candidates == null) return candidates;\n\n\t\tconst scoped = candidates.filter(c => c.cloud === cloud);\n\t\treturn scoped.length ? scoped : candidates;\n\t}\n\n\t/**\n\t * Marks the given connection as the primary/default for the provider and clears the flag on its\n\t * siblings. Persists immediately instead of relying on a session re-store to carry the primary flag.\n\t */\n\tsetPrimaryConnection(id: IntegrationIds, connectionId: string): Promise<void> {\n\t\treturn this.withLock(id, () => this.setPrimaryConnectionCore(id, connectionId));\n\t}\n\n\tprivate async setPrimaryConnectionCore(id: IntegrationIds, connectionId: string): Promise<void> {\n\t\tconst descriptors = this.configured.get(id);\n\t\tif (descriptors == null || descriptors.length === 0) return;\n\t\tif (!descriptors.some(d => d.id === connectionId)) return;\n\n\t\tconst target =\n\t\t\tdescriptors.find(d => d.id === connectionId && d.cloud) ?? descriptors.find(d => d.id === connectionId);\n\t\tif (target == null) return;\n\n\t\tconst domain = isSelfManagedHostIntegrationId(id) ? target.domain : undefined;\n\t\t// A connection id can have both a local (PAT) and cloud descriptor. Mark the primary on a single\n\t\t// canonical variant (prefer cloud, since multi-account primaries are cloud-driven) within the\n\t\t// provider/host scope, so other self-managed hosts keep their own default connection.\n\t\tconst primaryIsCloud = descriptors.some(\n\t\t\td => d.id === connectionId && d.cloud && this.isInPrimaryScope(id, d, domain),\n\t\t);\n\n\t\tlet changed = false;\n\t\tconst updated = descriptors.map(d => {\n\t\t\tif (!this.isInPrimaryScope(id, d, domain)) return d;\n\n\t\t\tconst primary = d.id === connectionId && (d.cloud ?? false) === primaryIsCloud;\n\t\t\tif ((d.primary ?? false) === primary) return d;\n\n\t\t\tchanged = true;\n\t\t\treturn { ...d, primary: primary };\n\t\t});\n\t\tif (!changed) return;\n\n\t\tthis.configured.set(id, updated);\n\t\tthis.fireChange(id);\n\t\tawait this.storeConfigured();\n\t}\n\n\t/**\n\t * Removes a single connection (its secret + configured descriptor). If it was the primary and other\n\t * connections remain, a secondary is promoted (see {@link removeConfigured}). Unlike\n\t * {@link deleteAllStoredSessions}, this only affects the targeted connection. Pass `cloud` to scope\n\t * the removal to the cloud/local variant when a local PAT and a cloud session share a connection id.\n\t */\n\tdeleteConnection(id: IntegrationIds, connectionId: string, cloud?: boolean): Promise<void> {\n\t\treturn this.withLock(id, async () => {\n\t\t\t// The connection is gone, so a token of it fetched before must not be stored back.\n\t\t\tthis.markSignOut(`${id}|connection:${connectionId}`);\n\t\t\tawait this.deleteSecrets(id, connectionId, cloud);\n\t\t});\n\t}\n\n\tprivate _addedIds = new Set<IntegrationIds>();\n\tprivate _removedIds = new Set<IntegrationIds>();\n\tprivate _fireChangeDebounced?: () => void;\n\n\tprivate normalizeConfiguredDomain(id: IntegrationIds, domain: string | undefined): string | undefined {\n\t\tif (!isSelfManagedHostIntegrationId(id)) return domain;\n\n\t\treturn hostFromDomain(domain) ?? domain;\n\t}\n\n\tprivate domainsMatch(id: IntegrationIds, first: string | undefined, second: string | undefined): boolean {\n\t\tif (first === second) return true;\n\t\tif (!isSelfManagedHostIntegrationId(id)) return false;\n\t\tif (first == null || second == null) return false;\n\n\t\treturn areDomainsOnSameHost(first, second);\n\t}\n\n\tprivate fireChange(added?: IntegrationIds, removed?: IntegrationIds) {\n\t\tthis._fireChangeDebounced ??= debounce(() => {\n\t\t\tconst added = [...this._addedIds];\n\t\t\tthis._addedIds.clear();\n\t\t\tconst removed = [...this._removedIds];\n\t\t\tthis._removedIds.clear();\n\n\t\t\tthis._onDidChange.fire({ added: added, removed: removed });\n\t\t}, 250);\n\n\t\tif (added != null) {\n\t\t\tthis._addedIds.add(added);\n\t\t}\n\t\tif (removed != null) {\n\t\t\tthis._removedIds.add(removed);\n\t\t}\n\t\tthis._fireChangeDebounced();\n\t}\n}\n\nfunction convertStoredSessionToSession(\n\tstoredSession: StoredSession | undefined,\n\tdescriptor: IntegrationAuthenticationSessionDescriptor,\n\tcloudIfMissing: boolean,\n): ProviderAuthenticationSession | undefined {\n\tif (storedSession == null) return undefined;\n\n\treturn {\n\t\tid: storedSession.id,\n\t\taccessToken: storedSession.accessToken,\n\t\taccount: {\n\t\t\tid: storedSession.account?.id ?? '',\n\t\t\tlabel: storedSession.account?.label ?? '',\n\t\t},\n\t\tscopes: storedSession.scopes,\n\t\tcloud: storedSession.cloud ?? cloudIfMissing,\n\t\texpiresAt: storedSession.expiresAt ? new Date(storedSession.expiresAt) : undefined,\n\t\tdomain: storedSession.domain ?? descriptor.domain,\n\t\tbaseUrl: getStoredBaseUrl(storedSession.baseUrl, storedSession.domain, storedSession.protocol),\n\t\tprotocol: storedSession.protocol,\n\t\ttype: storedSession.type,\n\t\t// Carried for providers whose client needs an app key alongside the token (e.g. Trello); without\n\t\t// this a rehydrated session silently loses the key and every read no-ops.\n\t\tappKey: storedSession.appKey,\n\t};\n}\n\nfunction getStoredBaseUrl(\n\tbaseUrl: string | undefined,\n\tdomain: string | undefined,\n\tprotocol?: string,\n): string | undefined {\n\tif (baseUrl != null) return baseUrl;\n\n\tconst legacyUrl = baseUrlFromDomain(domain, protocol);\n\treturn legacyUrl != null && new URL(legacyUrl).pathname !== '/' ? legacyUrl : undefined;\n}\n"],"mappings":"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"}
@@ -40,11 +40,15 @@ var IntegrationAuthenticationProviderBase = class {
40
40
  let session;
41
41
  let previousToken;
42
42
  let refetched;
43
- if (options?.forceNewSession) await this.configuredIntegrationService.deleteStoredSessions(this.authProviderId, {
44
- ...descriptor,
45
- cloud: true
46
- }, true);
47
- else {
43
+ let signOutMark;
44
+ if (options?.forceNewSession) {
45
+ await this.configuredIntegrationService.deleteStoredSessions(this.authProviderId, {
46
+ ...descriptor,
47
+ cloud: true
48
+ }, true, { signOut: true });
49
+ signOutMark = this.configuredIntegrationService.getSignOutMark();
50
+ } else {
51
+ signOutMark = this.configuredIntegrationService.getSignOutMark();
48
52
  const stored = await this.configuredIntegrationService.getStoredSession(this.authProviderId, options?.sync ? {
49
53
  ...descriptor,
50
54
  cloud: true
@@ -61,8 +65,8 @@ var IntegrationAuthenticationProviderBase = class {
61
65
  refreshIfExpired: isExpiredSession,
62
66
  refreshRejectedToken
63
67
  });
68
+ if (session != null && !await this.configuredIntegrationService.storeSession(this.authProviderId, session, { signOutMark })) session = void 0;
64
69
  if (session != null) {
65
- await this.configuredIntegrationService.storeSession(this.authProviderId, session);
66
70
  if (refetched?.id != null && refetched.id !== session.id) await this.configuredIntegrationService.deleteStoredSessions(this.authProviderId, {
67
71
  ...descriptor,
68
72
  connectionId: refetched.id,