@gitkraken/core-gitlens 0.18.0 → 0.20.0
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 +19 -1
- package/dist/git/models/issue.d.ts +13 -3
- package/dist/git/models/issue.js +3 -1
- package/dist/git/models/issue.js.map +1 -1
- package/dist/git/models/pullRequest.d.ts +13 -2
- package/dist/git/models/pullRequest.js +3 -1
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git/utils/issue.utils.js +2 -1
- package/dist/git/utils/issue.utils.js.map +1 -1
- package/dist/git/utils/pullRequest.utils.js +1 -0
- package/dist/git/utils/pullRequest.utils.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +1 -0
- package/dist/plus/integrations/collectionMetadata.js +1 -0
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +7 -6
- package/dist/plus/integrations/models/gitHostIntegration.js +4 -3
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +5 -0
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +8 -6
- package/dist/plus/integrations/providers/azure/azure.js +3 -2
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +4 -2
- package/dist/plus/integrations/providers/azure/models.js +6 -5
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +15 -13
- package/dist/plus/integrations/providers/azureDevOps.js +26 -22
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +6 -3
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
- package/dist/plus/integrations/providers/models.d.ts +26 -4
- package/dist/plus/integrations/providers/models.js +27 -5
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +2 -1
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/etag.js +3 -1
- package/dist/plus/integrations/reads/etag.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +3 -1
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.js +8 -2
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/paging.d.ts +4 -0
- package/dist/plus/integrations/reads/paging.js +9 -2
- package/dist/plus/integrations/reads/paging.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.js +5 -1
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +7 -0
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +21 -2
- package/package.json +1 -1
|
@@ -77,6 +77,11 @@ export interface PullRequestEtagFields {
|
|
|
77
77
|
* only for hosts that keep none (Azure DevOps and Server). Part of the etag whenever it is set, not an include.
|
|
78
78
|
*/
|
|
79
79
|
revision?: string;
|
|
80
|
+
/**
|
|
81
|
+
* A full row marked `viewerUnresolved`, which no cheap check ever sets: such a row never compares unchanged, so it
|
|
82
|
+
* is read in full again rather than kept without a viewer until the pull request changes.
|
|
83
|
+
*/
|
|
84
|
+
viewerUnresolved?: true;
|
|
80
85
|
}
|
|
81
86
|
/**
|
|
82
87
|
* The fields of a pull request {@link pullRequestRevision} reads, as a full row carries them. A `PullRequestShape`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","names":[],"sources":["../../../../../plus/integrations/src/models/integration.ts"],"sourcesContent":["import type { CollectionMetadata, CollectionScope } from '@gitkraken/provider-apis';\nimport { getDeferredRequestFailure } from '@gitlens/git/errors.js';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { AutolinkReference, DynamicAutolinkReference } from '@gitlens/git/models/autolink.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type {\n\tIssueOrPullRequest,\n\tIssueOrPullRequestState,\n\tIssueOrPullRequestType,\n} from '@gitlens/git/models/issueOrPullRequest.js';\nimport type {\n\tPullRequest,\n\tPullRequestMergeableState,\n\tPullRequestReviewDecision,\n\tPullRequestReviewState,\n\tPullRequestState,\n\tPullRequestStatusCheckRollupState,\n} from '@gitlens/git/models/pullRequest.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug, trace } from '@gitlens/utils/decorators/log.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 { fnv1aHash64 } from '@gitlens/utils/hash.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { ScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { PromiseCache } from '@gitlens/utils/promiseCache.js';\nimport type {\n\tIntegrationAuthenticationProviderDescriptor,\n\tIntegrationAuthenticationSessionDescriptor,\n} from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ConnectionResource, ProviderAuthenticationSession } from '../authentication/models.js';\nimport { RejectedTokenTracker } from '../authentication/rejectedTokenTracker.js';\nimport type { ProviderRefusal, ProviderScopeFailure } from '../collectionMetadata.js';\nimport {\n\tattributeScopedAuthFailures,\n\thasOnlyScopedAuthFailures,\n\tmarkCredentialRefusals,\n\ttoCollectionScopeFailure,\n} from '../collectionMetadata.js';\nimport type { IntegrationIds, IssuesCloudHostIntegrationId, IssuesHostIntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tisProviderUnreachableError,\n\tRequestClientError,\n\ttoError,\n} from '../errors.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport { providersMetadata } from '../providers/models.js';\nimport { isAzureProviderId } from '../providers/providerErrors.js';\nimport type { ProvidersApi } from '../providers/providersApi.js';\nimport { throwIfAllSettledFailed } from '../providers/utils/providerPaging.js';\nimport type { ProviderWarningCause, ProviderWarningScope } from '../results.js';\nimport type { Sources } from '../telemetry.js';\nimport { areDomainsOnSameHost } from '../utils/domain.utils.js';\nimport { isSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport type { GitHostIntegration } from './gitHostIntegration.js';\nimport { getCachedIssue } from './issueCache.js';\nimport type { AccountWideIssuesResult, SearchMyIssuesOptions } from './issueReads.js';\nimport type { IssuesIntegration } from './issuesIntegration.js';\n\nexport type Integration = GitHostIntegration | IssuesIntegration;\nexport type IntegrationById<T extends IntegrationIds> = T extends IssuesHostIntegrationIds\n\t? IssuesIntegration\n\t: GitHostIntegration;\nexport type IntegrationType = 'git' | 'issues';\n\n// The issue-read contracts live in their own module (pure data, and their relationship to each other is the\n// point of reading them together); re-exported here so the providers that implement these reads keep one import.\nexport type {\n\tAccountWideIssuesResult,\n\tProviderIssueSearchPage,\n\tProviderSearchCount,\n\tSearchMyIssuesOptions,\n} from './issueReads.js';\nexport type { ProviderPullRequestCount, ProviderPullRequestSearchPage } from './pullRequestReads.js';\n\n// Keep this in step with `isSelfManagedHostIntegrationId`: it is the type-level twin of that predicate, and a\n// disagreement compiles cleanly while the runtime writes a domain-keyed key the type says is unkeyed (so the\n// local-disconnect flag is written under one key and read under another).\nexport type IntegrationKey<T extends IntegrationIds = IntegrationIds> = T extends\n\t| GitCloudHostIntegrationId\n\t| IssuesCloudHostIntegrationId\n\t? `${T}`\n\t: `${T}:${string}`;\n\nexport type IntegrationConnectedKey<T extends IntegrationIds = IntegrationIds> = `connected:${IntegrationKey<T>}`;\n\nexport type IntegrationResult<T> =\n\t| { value: T; duration?: number; error?: Error }\n\t| { error: Error; duration?: number; value?: never }\n\t| undefined;\n\n/** A per-connection read's session that could not be resolved for a retryable reason (see `resolveReadSession`). */\nexport class ReadSessionFailure {\n\tconstructor(readonly error: Error) {}\n}\n\nexport function isReadSessionFailure(\n\tsession: ProviderAuthenticationSession | ReadSessionFailure | undefined,\n): session is ReadSessionFailure {\n\treturn session instanceof ReadSessionFailure;\n}\n\n/**\n * One target's answer in a batch read. A slot rejected by an authentication refusal carries the scope failure it was\n * recorded as (see `IntegrationBase.settleBatchRefusals`).\n */\nexport type BatchSlot<T> = PromiseFulfilledResult<T> | (PromiseRejectedResult & { failure?: ProviderScopeFailure });\n\n/**\n * An input a pull request's etag can be widened to, each of which a host changes without moving `updatedAt`. Every\n * entry costs its own fields in the cheap check, so a caller picks only the ones it needs. `'checks'` is the check\n * rollup.\n */\nexport type PullRequestEtagInclude = 'mergeable' | 'reviewDecision' | 'checks';\n\n/** Every {@link PullRequestEtagInclude}, in the canonical order an etag lists them in. */\nexport const pullRequestEtagIncludes: readonly PullRequestEtagInclude[] = ['mergeable', 'reviewDecision', 'checks'];\n\n/**\n * The change state a batch read's etag is computed from (see `reads/etag.ts`), in the NORMALIZED vocabulary of\n * `PullRequestShape` — the values a full row carries after its provider's whole conversion chain, so a cheap\n * check and a full read of the same pull request yield the same fields. Each of `mergeableState`, `reviewDecision`\n * and `statusCheckRollupState` counts only when its {@link PullRequestEtagInclude} is requested.\n */\nexport interface PullRequestEtagFields {\n\tstate: PullRequestState;\n\tisDraft?: boolean;\n\tupdatedDate: Date;\n\theadSha?: string;\n\tmergeableState?: PullRequestMergeableState;\n\treviewDecision?: PullRequestReviewDecision;\n\tstatusCheckRollupState?: PullRequestStatusCheckRollupState;\n\t/**\n\t * A fingerprint of the fields a host changes without moving an update time ({@link pullRequestRevision}); set\n\t * only for hosts that keep none (Azure DevOps and Server). Part of the etag whenever it is set, not an include.\n\t */\n\trevision?: string;\n}\n\n/**\n * The fields of a pull request {@link pullRequestRevision} reads, as a full row carries them. A `PullRequestShape`\n * is one, so a full read passes its row as is and a cheap check builds one from the same mapped values.\n */\nexport interface PullRequestRevisionSource {\n\treadonly title: string;\n\treadonly body?: string;\n\treadonly refs?: { readonly base: { readonly branch: string } };\n\treadonly reviewRequests?: readonly PullRequestRevisionReviewer[];\n\treadonly latestReviews?: readonly PullRequestRevisionReviewer[];\n}\n\ninterface PullRequestRevisionReviewer {\n\treadonly reviewer: { readonly id: string };\n\treadonly state: PullRequestReviewState;\n}\n\n/**\n * {@link PullRequestEtagFields.revision}: a 64-bit hash of a pull request's title, description, target branch and\n * reviewers (each one's id and review state, in a canonical order). Hashed to keep the etag short, so a collision is\n * the only way a change can hide, at about 2^-64.\n */\nexport function pullRequestRevision(pr: PullRequestRevisionSource): string {\n\treturn fnv1aHash64(\n\t\tJSON.stringify([\n\t\t\tpr.title,\n\t\t\tpr.body ?? null,\n\t\t\tpr.refs?.base.branch ?? null,\n\t\t\ttoRevisionReviewers(pr.reviewRequests),\n\t\t\ttoRevisionReviewers(pr.latestReviews),\n\t\t]),\n\t);\n}\n\n/** Each reviewer as its serialized `[id, state]`, sorted, so the host's order never moves the revision. */\nfunction toRevisionReviewers(reviewers: readonly PullRequestRevisionReviewer[] | undefined): string[] | null {\n\treturn reviewers?.map(r => JSON.stringify([r.reviewer.id, r.state])).sort() ?? null;\n}\n\n/**\n * An input an issue's etag can be widened to, which a host changes without moving `updatedAt`. `'reactions'` is the\n * thumbs-up count, and widens the etag only on hosts whose rows really fetch one (GitHub/GHE and GitLab).\n */\nexport type IssueEtagInclude = 'reactions';\n\n/** Every {@link IssueEtagInclude}, in the canonical order an etag lists them in. */\nexport const issueEtagIncludes: readonly IssueEtagInclude[] = ['reactions'];\n\n/** The issue twin of {@link PullRequestEtagFields}, in {@link IssueShape}'s normalized vocabulary. */\nexport interface IssueEtagFields {\n\tstate: IssueOrPullRequestState | undefined;\n\tupdatedDate: Date;\n\t/** Counts only when `'reactions'` is requested, and only from a row whose read fetched reactions. */\n\tthumbsUpCount?: number;\n\t/**\n\t * Azure DevOps only: the version of the rule a work item's body is read by ({@link azureWorkItemDescriptionRule}).\n\t * A change of rule changes the body without moving `updatedDate`, so a row read under an earlier one must not\n\t * compare unchanged.\n\t */\n\tdescriptionRule?: number;\n\t/**\n\t * Azure DevOps only: the version of the rule a work item's `viewer` is set by ({@link azureWorkItemViewerRule}).\n\t * A full row that names someone but got no viewer (its organization's identity couldn't be read) leaves it out, so\n\t * it never compares unchanged and is read in full again rather than kept without one.\n\t */\n\tviewerRule?: number;\n}\n\n/**\n * {@link IssueEtagFields.descriptionRule}: the version of the rule an Azure DevOps work item's body is read by, stamped\n * on both sides of its etag. Bumped whenever the rule changes which field a work item's body comes from, so every row\n * a caller holds from before is read in full once. 1: the first rich text field with content (provider-apis'\n * `getAzureWorkItemDescription`), where it used to be only Description. The rule lives in provider-apis, so a bump of\n * that package that changes it must bump this too; the etag tests pin the rule to catch one.\n *\n * It can't cover a row whose read found the organization's field catalog unavailable: that row's body falls back to\n * the built-in fields, and a cheap check, which reads no body, can't tell it from a complete one, so it stays as read\n * until the work item changes.\n */\nexport const azureWorkItemDescriptionRule = 1;\n\n/**\n * {@link IssueEtagFields.viewerRule}: the version of the rule an Azure DevOps work item's `viewer` is set by, stamped\n * on both sides of its etag, so every row a caller holds from before it was set is read in full once. 1: the user's\n * identity in the work item's organization (gitkraken/kepler#4291).\n */\nexport const azureWorkItemViewerRule = 1;\n\n/** A batch read's target, in the form its provider addresses it by: a repository coordinate, or a tracker's resource. */\ntype BatchTarget = { owner: string; repo: string; project?: string } | { resourceId: string };\n\n/**\n * The scope a batch read records a refused target under. It is decided here rather than in the read, which has the\n * same targets, because naming the refusal's cause (`describeRefusal`) needs it too. Azure DevOps: the organization\n * and project, since its refusals come from those (an organization's OAuth policy or tenant, a project's permissions)\n * and its work items belong to no repository. Every other git host: the repository, `owner/repo`, which is how its\n * repo-scoped reads record one. A tracker: the resource.\n */\nfunction batchTargetScope(providerId: IntegrationIds, target: BatchTarget): CollectionScope {\n\tif ('resourceId' in target) return { providerId: providerId, resourceId: target.resourceId };\n\tif (isAzureProviderId(providerId)) {\n\t\treturn { providerId: providerId, resourceId: target.owner, projectId: target.project };\n\t}\n\treturn { providerId: providerId, repositoryId: `${target.owner}/${target.repo}` };\n}\n\ntype SyncReqUsecase = Exclude<\n\t| 'getAccountForCommit'\n\t| 'getAccountForEmail'\n\t| 'getAccountForResource'\n\t| 'getCurrentAccount'\n\t| 'getDefaultBranch'\n\t| 'getIssue'\n\t| 'getIssueOrPullRequest'\n\t| 'getIssuesBatch'\n\t| 'getIssuesEtagFields'\n\t| 'getIssuesForProject'\n\t| 'getIssuesForRepos'\n\t| 'getMyPullRequestsForUser'\n\t| 'getOrganizationsForUser'\n\t| 'getProjectsForOrg'\n\t| 'getProjectsForResources'\n\t| 'getPullRequest'\n\t| 'getPullRequestsBatch'\n\t| 'getPullRequestsEtagFields'\n\t| 'getPullRequestsForBranches'\n\t| 'getRepositoriesForOrg'\n\t| 'getRepositoriesForUser'\n\t| 'getPullRequestForBranch'\n\t| 'getPullRequestForCommit'\n\t| 'getPullRequestsForRepos'\n\t| 'getRepositoryMetadata'\n\t| 'getResourcesForUser'\n\t| 'getSshSigningKeysForEmails'\n\t| 'countIssues'\n\t| 'countPullRequests'\n\t| 'mergePullRequest'\n\t| 'searchIssuesPage'\n\t| 'searchPullRequestsPage'\n\t| 'searchMyIssues'\n\t| 'searchMyPullRequests'\n\t| 'searchPullRequests',\n\t// excluding to show explicitly that we don't want to add 'all' key occasionally\n\t'all'\n>;\n\nexport abstract class IntegrationBase<\n\tID extends IntegrationIds = IntegrationIds,\n\tT extends ResourceDescriptor = ResourceDescriptor,\n> implements Disposable {\n\tabstract readonly type: IntegrationType;\n\n\tprivate readonly _onDidChange = new Emitter<void>();\n\tget onDidChange(): Event<void> {\n\t\treturn this._onDidChange.event;\n\t}\n\n\tconstructor(\n\t\tprotected readonly ctx: IntegrationServiceContext,\n\t\tprotected readonly authenticationService: IntegrationAuthenticationService,\n\t\tprotected readonly getProvidersApi: () => Promise<ProvidersApi>,\n\t\tprivate readonly didChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t) {}\n\n\tdispose(): void {\n\t\tthis._onDidChange.dispose();\n\t}\n\n\tabstract get authProvider(): IntegrationAuthenticationProviderDescriptor;\n\tabstract get id(): ID;\n\tprotected abstract get key(): IntegrationKey<ID>;\n\tabstract get name(): string;\n\tabstract get domain(): string;\n\n\tget authProviderDescriptor(): IntegrationAuthenticationSessionDescriptor {\n\t\treturn { domain: this.domain, scopes: this.authProvider.scopes };\n\t}\n\n\t/**\n\t * The `gl-provider-<key>` glicon key for this provider, which is NOT always its id.\n\t *\n\t * `providersMetadata.iconKey` is the field that says which glyph a provider draws with, and a\n\t * self-managed variant reuses its cloud family's — Jira Data Center draws with Jira's, because it is the\n\t * same product and the id only selects an icon. Returning the id instead named a glyph the registry has\n\t * no entry for, so a consumer rendering a `gl-provider-` glicon from `issue.provider.icon` requested a\n\t * glyph that does not exist.\n\t */\n\tget icon(): string {\n\t\treturn providersMetadata[this.id]?.iconKey ?? this.id;\n\t}\n\n\taccess(): Promise<boolean> {\n\t\treturn this.ctx.account.isTrialOrPaid();\n\t}\n\n\tautolinks():\n\t\t| (AutolinkReference | DynamicAutolinkReference)[]\n\t\t| Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\treturn [];\n\t}\n\n\tprivate get connectedKey(): IntegrationConnectedKey<ID> {\n\t\treturn `connected:${this.key}`;\n\t}\n\n\tget maybeConnected(): boolean | undefined {\n\t\treturn this._session === undefined ? undefined : this._session !== null;\n\t}\n\n\t/** Hash of the current session's access token. Changes on any token change (account switch or refresh). */\n\tprivate _sessionFingerprint: { session: ProviderAuthenticationSession; hash: string } | undefined;\n\tget sessionFingerprint(): string | undefined {\n\t\tif (this._session == null) return undefined;\n\n\t\tif (this._sessionFingerprint?.session !== this._session) {\n\t\t\tthis._sessionFingerprint = { session: this._session, hash: this.getSessionFingerprint(this._session) };\n\t\t}\n\t\treturn this._sessionFingerprint.hash;\n\t}\n\n\tprotected getSessionFingerprint(session: ProviderAuthenticationSession): string {\n\t\treturn fnv1aHash64(session.accessToken);\n\t}\n\n\tget connectionExpired(): boolean | undefined {\n\t\tif (this._session?.expiresAt == null) return undefined;\n\t\treturn new Date(this._session.expiresAt) < new Date();\n\t}\n\n\tprotected _session: ProviderAuthenticationSession | null | undefined;\n\t/**\n\t * Every change to the session (a resolution, a forced re-sync, a disconnect, a reauthentication, a connection\n\t * switch, a refused token) runs here, one at a time. Each one reads storage and the cloud and then settles\n\t * `_session`, the stored tokens and the connected flag; run concurrently, one would publish what it read over\n\t * what another settled meanwhile (gitkraken/kepler#3546). Reads don't queue: they read `_session` as it is.\n\t */\n\tprivate _sessionTransitions: Promise<void> = Promise.resolve();\n\t/**\n\t * Runs `fn` after every transition queued before it. `fn` must not queue another transition and wait for it,\n\t * which would wait for itself: inside a transition, call the `*Locked` methods instead.\n\t */\n\tprotected runSessionTransition<T>(fn: () => Promise<T>): Promise<T> {\n\t\tconst run = this._sessionTransitions.then(fn);\n\t\tthis._sessionTransitions = run.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn run;\n\t}\n\n\t/** An expired session is refreshed first; when that fails, nothing is returned rather than a token the provider refuses. */\n\tgetSession(\n\t\tsource: Sources,\n\t): ProviderAuthenticationSession | Promise<ProviderAuthenticationSession | undefined> | undefined {\n\t\t// Served as is even while a transition runs: a forced re-sync keeps the session it is replacing, and a read\n\t\t// meanwhile reads with it rather than waiting on the cloud.\n\t\tif (this._session === undefined) {\n\t\t\treturn this.ensureSession({ createIfNeeded: false, source: source });\n\t\t}\n\t\tif (this._session == null || !this.isSessionForIntegrationHost(this._session)) return undefined;\n\t\tif (this.connectionExpired) return this.getRefreshedSession();\n\n\t\treturn this._session;\n\t}\n\n\tprivate async getRefreshedSession(): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst failure = await this.refreshSessionIfExpired();\n\t\tif (failure != null) return undefined;\n\n\t\t// The refresh may have been skipped (a non-cloud session) or overtaken by a disconnect or a switch.\n\t\treturn this._session != null &&\n\t\t\tthis.connectionExpired !== true &&\n\t\t\tthis.isSessionForIntegrationHost(this._session)\n\t\t\t? this._session\n\t\t\t: undefined;\n\t}\n\n\tprivate isSessionForIntegrationHost(session: ProviderAuthenticationSession): boolean {\n\t\tif (!isSelfManagedHostIntegrationId(this.id)) return true;\n\n\t\treturn areDomainsOnSameHost(this.domain, session.domain);\n\t}\n\n\t/**\n\t * Resolves the session to read as, for a per-connection (multi-account) read. When `connectionId` is\n\t * omitted this is the integration's primary session, resolved exactly like the existing read flow\n\t * (ensure-connected + refresh-if-expired). When set, it resolves THAT connection's session directly\n\t * from the auth provider — refreshing it if expired — WITHOUT disturbing the cached primary\n\t * `_session`. Never throws: `undefined` when there is no session to read with (the connection is gone,\n\t * the provider isn't connected), `{ error }` when the connection's token could not be fetched.\n\t *\n\t * The two must not be conflated. A token fetch that THROWS is, by `getConnectionSession`'s contract, a\n\t * retryable failure (a rate-limited or failing GK API, a network error), not evidence that the connection is\n\t * gone (#5569). Answering \"no session\" for it made every connection read during a GK API 429 report\n\t * `no-connection`, which a consumer rightly treats as a credential to reconnect (gitkraken/kepler#3546).\n\t * Returned as the read's error instead, the read core surfaces it like any other failed request: a rate\n\t * limit as `rate-limit`, anything else as `other`.\n\t */\n\tprotected async resolveReadSession(\n\t\tconnectionId: string | undefined,\n\t\tscope: ScopedLogger | undefined,\n\t\tsource?: Sources,\n\t): Promise<ProviderAuthenticationSession | ReadSessionFailure | undefined> {\n\t\tif (\n\t\t\tthis.ctx.config.isIntegrationsEnabled?.() === false ||\n\t\t\tthis.ctx.storage.getWorkspace(this.connectedKey) === false\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// A truthy connectionId targets a specific account; an empty string is not a real target, so it falls\n\t\t// through to the primary path below.\n\t\tif (connectionId) {\n\t\t\t// A read of this connection previously failed with an AuthenticationError, so the token in\n\t\t\t// storage is known-refused. Ask for a refresh through the GK cloud before reading again, which\n\t\t\t// is this branch's equivalent of the primary path's `refreshSessionIfExpired`. Claimed here so\n\t\t\t// the forced refresh runs at most once per rejection.\n\t\t\tconst refreshRejectedToken = this._rejectedTokens.claimRefresh(connectionId);\n\t\t\t// Never throws, matching the primary path (whose ensureSession/refreshSessionIfExpired swallow\n\t\t\t// errors), so read methods keep their never-throws contract.\n\t\t\ttry {\n\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\tconst session = await authProvider.getSession(\n\t\t\t\t\t{ ...this.authProviderDescriptor, connectionId: connectionId, cloud: true },\n\t\t\t\t\t{ source: source, refreshRejectedToken: refreshRejectedToken },\n\t\t\t\t);\n\t\t\t\treturn session != null && this.isSessionForIntegrationHost(session) ? session : undefined;\n\t\t\t} catch (ex) {\n\t\t\t\tif (isCancellationError(ex)) return undefined;\n\n\t\t\t\tscope?.error(ex);\n\t\t\t\treturn new ReadSessionFailure(toError(ex));\n\t\t\t}\n\t\t}\n\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return undefined;\n\n\t\t// The primary path's equivalent of the per-connection failure above: an expired session the cloud could not\n\t\t// replace right now is the refresh's failure, not a missing connection, and is never read with.\n\t\tconst refreshFailure = await this.refreshSessionIfExpired();\n\t\tif (refreshFailure != null) return new ReadSessionFailure(refreshFailure);\n\n\t\treturn this._session != null && this.isSessionForIntegrationHost(this._session) ? this._session : undefined;\n\t}\n\n\t/**\n\t * The session lookups' prelude for a caller that can surface a failure (`throwOnError`): the refresh's failure to\n\t * report, or `'unconnected'`, so a throttled refresh is not mistaken for \"no pull request\" and kept.\n\t */\n\tprotected async prepareSessionLookup(): Promise<Error | 'unconnected' | undefined> {\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return 'unconnected';\n\n\t\tconst failure = await this.refreshSessionIfExpired();\n\t\tif (failure != null) return failure;\n\n\t\t// A disconnect, or a refetch the cloud answered for another host, can leave no session to read with.\n\t\treturn this._session != null ? undefined : 'unconnected';\n\t}\n\n\t/**\n\t * {@link resolveReadSession} for a read that already rethrows its provider's failures (a repository or account\n\t * lookup), or whose callers treat any failure as \"nothing to show\": a token that could not be fetched is\n\t * thrown like those failures, so it is classified as the failed request it is rather than as a missing\n\t * connection.\n\t */\n\tprotected async resolveReadSessionOrThrow(\n\t\tconnectionId: string | undefined,\n\t\tscope: ScopedLogger | undefined,\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (isReadSessionFailure(session)) throw session.error;\n\n\t\treturn session;\n\t}\n\n\t@debug()\n\tasync connect(source: Sources): Promise<boolean> {\n\t\ttry {\n\t\t\treturn Boolean(await this.ensureSession({ createIfNeeded: true, source: source }));\n\t\t} catch (_ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprotected providerOnConnect?(): void | Promise<void>;\n\n\t@gate()\n\t@debug()\n\tasync disconnect(options?: { silent?: boolean; currentSessionOnly?: boolean }): Promise<void> {\n\t\tif (options?.currentSessionOnly && this._session === null) return;\n\n\t\tlet signOut = !options?.currentSessionOnly;\n\n\t\t// Asked before queueing: the user's answer can take any time, and the queue must not wait on it.\n\t\tif (this._session != null && !options?.currentSessionOnly && !options?.silent) {\n\t\t\tconst decision = await this.ctx.hooks?.onConfirmDisconnect?.({\n\t\t\t\tintegrationName: this.name,\n\t\t\t\tofferSignOut: this.authenticationService.supports(this.authProvider.id),\n\t\t\t});\n\t\t\tif (decision == null) return;\n\n\t\t\tsignOut = decision.signOut;\n\t\t}\n\n\t\t// Waits for a forced re-sync in flight, so the session it settles cannot land over the disconnect.\n\t\tawait this.runSessionTransition(() => this.disconnectLocked(options, signOut));\n\t}\n\n\tprivate async disconnectLocked(\n\t\toptions: { silent?: boolean; currentSessionOnly?: boolean } | undefined,\n\t\tsignOut: boolean,\n\t): Promise<void> {\n\t\tif (options?.currentSessionOnly && this._session === null) return;\n\n\t\tconst connected = this._session != null;\n\n\t\tif (signOut) {\n\t\t\t// Disconnecting a provider signs out of ALL its connected accounts (multi-account), not just the\n\t\t\t// primary — otherwise secondary connections' secrets/config would be orphaned. Removing a single\n\t\t\t// account is done via IntegrationService.deleteConnection instead. Pass this instance's descriptor\n\t\t\t// so self-managed disconnects stay scoped to this host: those group every host under one provider\n\t\t\t// id, so an unscoped clear would sign the user out of unrelated hosts. deleteAllSessions derives an\n\t\t\t// undefined domain for cloud providers, so they still clear every account as intended.\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t// Awaited, not fire-and-forget: a caller that awaits `disconnect()` has to be able to rely on the\n\t\t\t// secrets and descriptors actually being gone when it resumes. Left floating, the only thing that\n\t\t\t// ever made this land in time was incidental scheduling slack — `syncCloudIntegrations` used to\n\t\t\t// await each provider in turn, so a later iteration's suspension let the previous provider's delete\n\t\t\t// finish. Syncing providers concurrently removes that slack and the clear was observably still\n\t\t\t// pending when the sync returned.\n\t\t\tawait authProvider.deleteAllSessions(this.authProviderDescriptor);\n\t\t}\n\n\t\tthis.resetRequestExceptionCount('all');\n\t\tthis._session = null;\n\n\t\tif (connected) {\n\t\t\t// Don't store the disconnected flag if silently disconnecting or disconnecting this only for\n\t\t\t// this current VS Code session (will be re-connected on next restart)\n\t\t\tif (!options?.currentSessionOnly && !options?.silent) {\n\t\t\t\tvoid this.ctx.storage.storeWorkspace(this.connectedKey, false).catch();\n\t\t\t}\n\n\t\t\tthis._onDidChange.fire();\n\t\t\tif (!options?.currentSessionOnly) {\n\t\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'disconnected' });\n\t\t\t}\n\t\t}\n\n\t\tawait this.providerOnDisconnect?.();\n\t}\n\n\tprotected providerOnDisconnect?(): void | Promise<void>;\n\n\t@debug()\n\tasync reauthenticate(): Promise<void> {\n\t\t// `forceNewSession` below deletes the stored secrets to reconnect. Ahead of the guard — see\n\t\t// `onStoredTokensReplaced`.\n\t\tthis.onStoredTokensReplaced();\n\n\t\tconst cleared = await this.runSessionTransition(() => {\n\t\t\tif (this._session === undefined) return Promise.resolve(false);\n\n\t\t\tthis._session = undefined;\n\t\t\treturn Promise.resolve(true);\n\t\t});\n\t\tif (!cleared) return;\n\n\t\tvoid (await this.ensureSession({ createIfNeeded: true, forceNewSession: true }));\n\t}\n\n\trefresh(): void {\n\t\tvoid this.ensureSession({ createIfNeeded: false });\n\t}\n\n\t/**\n\t * Drops the cached session when its token is no longer `accessToken` (a session replaced outside the\n\t * integration) and resolves it again, in one transition, so nothing in flight publishes the replaced one.\n\t */\n\tprotected resyncSessionIfTokenChanged(accessToken: string | undefined): Promise<void> {\n\t\treturn this.runSessionTransition(async () => {\n\t\t\tif (this._session != null && this._session.accessToken !== accessToken) {\n\t\t\t\tthis._session = undefined;\n\t\t\t}\n\t\t\tawait this.resolveSessionLocked({ createIfNeeded: false });\n\t\t});\n\t}\n\n\tprivate _syncRequestsPerFailedUsecase = new Set<SyncReqUsecase>();\n\thasSessionSyncRequests(): boolean {\n\t\treturn this._syncRequestsPerFailedUsecase.size > 0;\n\t}\n\trequestSessionSyncForUsecase(syncReqUsecase: SyncReqUsecase): void {\n\t\tthis._syncRequestsPerFailedUsecase.add(syncReqUsecase);\n\t}\n\n\t/**\n\t * The per-connection counterpart of the primary session's expire-and-resync recovery: a token the\n\t * provider refused is refreshed once through the GK cloud, which exchanges the refresh token server-side\n\t * (the client never holds one). Armed by {@link handleProviderException} and consumed by\n\t * {@link resolveReadSession}; a rejection it declines belongs to {@link trackRequestException} instead.\n\t */\n\tprivate readonly _rejectedTokens = new RejectedTokenTracker();\n\n\t/**\n\t * Called by every path that replaces or removes the stored tokens — a disconnect, a forced re-sync, a\n\t * reauthentication, or the connection set changing. A rejection names a specific credential, so once that\n\t * credential is gone the rejection describes nothing and must not force a refresh (or, for a deleted\n\t * connection, outlive it).\n\t *\n\t * Deliberately NOT conditioned on `_session`. A per-connection read resolves through the auth provider and\n\t * never populates the cached primary session, so the connections this recovery exists for are exactly the\n\t * ones with no `_session` to inspect — the same blind spot the recovery itself was added to fix. Callers\n\t * that guard on `_session` therefore invoke this ahead of that guard.\n\t */\n\tprotected onStoredTokensReplaced(): void {\n\t\tthis._rejectedTokens.clear();\n\t\tthis.invalidateDiscoveryCaches();\n\t}\n\n\t/**\n\t * Drops what this integration discovered through its session and kept for later reads: a tracker's sites,\n\t * projects or teams. Called by every path that replaces the stored tokens (see {@link onStoredTokensReplaced}),\n\t * which includes the forced re-sync a consumer's refresh runs, and by a read asked to `forceSync`. A refresh is\n\t * the consumer saying the account may have changed, and a project created or deleted since the first read would\n\t * otherwise stay invisible, or keep being searched, for the rest of the session (#5907).\n\t */\n\tinvalidateDiscoveryCaches(): void {}\n\n\tprivate static readonly requestExceptionLimit = 5;\n\tprivate requestExceptionCount = 0;\n\n\tresetRequestExceptionCount(syncReqUsecase: SyncReqUsecase | 'all'): void {\n\t\tthis.requestExceptionCount = 0;\n\t\tif (syncReqUsecase === 'all') {\n\t\t\tthis._syncRequestsPerFailedUsecase.clear();\n\t\t\t// 'all' is the whole-integration reset: a disconnect, or a re-sync that produced a new access token.\n\t\t\tthis.onStoredTokensReplaced();\n\t\t} else {\n\t\t\tthis._syncRequestsPerFailedUsecase.delete(syncReqUsecase);\n\t\t}\n\t}\n\n\t/**\n\t * Resets request exceptions without resetting the amount of syncs\n\t */\n\tsmoothifyRequestExceptionCount(): void {\n\t\t// On resync we reset exception count only to avoid infinitive syncs on failure\n\t\tthis.requestExceptionCount = 0;\n\t}\n\n\tasync reset(): Promise<void> {\n\t\tawait this.runSessionTransition(async () => {\n\t\t\tawait this.disconnectLocked({ silent: true }, true);\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t});\n\t}\n\n\t/**\n\t * Drops the in-memory session so the next access re-resolves it from storage. Used when the primary\n\t * connection changed underneath a warm integration (e.g. after `setPrimaryConnection`/\n\t * `deleteConnection`). Unlike {@link reset}/{@link disconnect}, it deletes nothing from storage.\n\t */\n\tswitchConnection(): Promise<void> {\n\t\t// A connection was deleted, or a different one became primary. Ahead of the guard — see\n\t\t// `onStoredTokensReplaced`.\n\t\tthis.onStoredTokensReplaced();\n\n\t\t// Queued, so a resolution already running finishes with the former primary and this one then re-resolves.\n\t\treturn this.runSessionTransition(async () => {\n\t\t\tif (this._session === undefined) return;\n\n\t\t\tconst wasConnected = this._session != null;\n\t\t\tthis._session = undefined;\n\t\t\tthis._onDidChange.fire();\n\n\t\t\tconst session = await this.resolveSessionLocked({ createIfNeeded: false });\n\t\t\tif (session != null || !wasConnected) return;\n\n\t\t\tthis._onDidChange.fire();\n\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'disconnected' });\n\t\t\tawait this.providerOnDisconnect?.();\n\t\t});\n\t}\n\n\tprivate skippedNonCloudReported = false;\n\t/**\n\t * Resolves the re-sync's failure, when its token fetch threw (cancellations included), so a caller can report it.\n\t * Never rejects: the cloud sync fans this out over every integration and must not fail on one.\n\t */\n\t@debug()\n\tsyncCloudConnection(state: 'connected' | 'disconnected', forceSync: boolean): Promise<Error | undefined> {\n\t\treturn this.runSessionTransition(() => this.syncCloudConnectionLocked(state, forceSync));\n\t}\n\n\tprivate async syncCloudConnectionLocked(\n\t\tstate: 'connected' | 'disconnected',\n\t\tforceSync: boolean,\n\t): Promise<Error | undefined> {\n\t\tconst scope = getScopedLogger();\n\t\t// Initially the condition on `this._session.cloud` has been added here: https://github.com/gitkraken/vscode-gitlens/commit/e95e70c430bd162924cc3bd5c1e8ab90e6293449#diff-4213141a45cccaab7aa2e40028b155a87eb913b07388485831403e60ce5555e4R237\n\t\t// I'm not sure about reasons, but it seems we want to replace it with the cloud session if it's connected.\n\t\t// Gradually we'll stop having non-cloud sessions.\n\t\t// However this is needed to be tested with PATs, e.g. with a GitLab PAT.\n\t\tif (this._session?.cloud === false && state !== 'connected') {\n\t\t\tif (this.id !== GitCloudHostIntegrationId.GitHub && !this.skippedNonCloudReported) {\n\t\t\t\tthis.ctx.hooks?.session?.onRefreshSkipped?.({\n\t\t\t\t\tid: this.id,\n\t\t\t\t\treason: 'skip-non-cloud',\n\t\t\t\t\tcloud: false,\n\t\t\t\t});\n\t\t\t\tthis.skippedNonCloudReported = true;\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet failure: Error | undefined;\n\t\tswitch (state) {\n\t\t\tcase 'connected': {\n\t\t\t\tconst oldSession = this._session;\n\t\t\t\tlet resyncing = false;\n\t\t\t\tif (forceSync) {\n\t\t\t\t\t// Get a new session from the cloud. The stored one is refetched rather than deleted first (see\n\t\t\t\t\t// `GetSessionOptions.refetch`), so a fetch that fails transiently keeps the token it had.\n\t\t\t\t\t// Not left to the token-changed check below, which needs an `oldSession` — see\n\t\t\t\t\t// `onStoredTokensReplaced`.\n\t\t\t\t\tthis.onStoredTokensReplaced();\n\t\t\t\t\t// The cached session is kept while the replacement is fetched (the refetch does not read it): a\n\t\t\t\t\t// transient failure then leaves it as it was, expiry included, and a read meanwhile does not have to\n\t\t\t\t\t// resolve a session of its own, which during a GK API outage fails the same way and reports the\n\t\t\t\t\t// integration as not connected (gitkraken/kepler#3546). Clear our \"stay disconnected\" flag.\n\t\t\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t\t\t\tresyncing = true;\n\t\t\t\t} else {\n\t\t\t\t\t// Only sync if we're not connected and not disabled and don't have pending errors\n\t\t\t\t\tif (\n\t\t\t\t\t\tthis._session != null ||\n\t\t\t\t\t\tthis.requestExceptionCount > 0 ||\n\t\t\t\t\t\tthis.ctx.storage.getWorkspace(this.connectedKey) === false\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\n\t\t\t\t\tforceSync = true;\n\t\t\t\t}\n\n\t\t\t\t// sync option, rather than createIfNeeded, makes sure we don't call connectCloudIntegrations and open a gkdev window\n\t\t\t\t// if there was no session or some problem fetching/refreshing the existing session from the cloud api\n\t\t\t\tlet newSession: ProviderAuthenticationSession | undefined;\n\t\t\t\tlet refetchFailed = false;\n\t\t\t\ttry {\n\t\t\t\t\tnewSession = await this.resolveSessionLocked({ sync: forceSync, refetch: resyncing });\n\t\t\t\t} catch (ex) {\n\t\t\t\t\trefetchFailed = true;\n\t\t\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\t}\n\t\t\t\t\t// Not evidence the connection is gone (#5569). A cancelled refetch is still reported: its caller must\n\t\t\t\t\t// not read with the session it could not replace.\n\t\t\t\t\tfailure = toError(ex);\n\t\t\t\t}\n\n\t\t\t\tif (oldSession && newSession && newSession.accessToken !== oldSession.accessToken) {\n\t\t\t\t\tthis.resetRequestExceptionCount('all');\n\t\t\t\t}\n\n\t\t\t\t// The forced re-sync above kept the stored token and its descriptor until a replacement arrived.\n\t\t\t\t// Drop both only when the replacement fetch came back definitively empty, so a connection whose\n\t\t\t\t// token is really gone is cleanly disconnected (#5497) while a healthy one survives a blip (#5569).\n\t\t\t\tif (resyncing && newSession == null && !refetchFailed) {\n\t\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\t\tawait authProvider.deleteSession(this.authProviderDescriptor, { preserveConfigured: false });\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'disconnected':\n\t\t\t\tawait this.disconnectLocked({ silent: true }, true);\n\t\t\t\tbreak;\n\t\t}\n\t\tthis._resyncs++;\n\t\tthis._lastResyncFailure = failure;\n\t\treturn failure;\n\t}\n\n\t/**\n\t * Proves the credential with an uncached check, rejecting with the provider's `AuthenticationError` when it is\n\t * refused, and with anything else when the check proves nothing. A `403` proves nothing either: the credential\n\t * authenticated and was only denied the check's request.\n\t *\n\t * Implemented by the providers whose reads can reach their scopes without an uncached request to the connection\n\t * first: discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud), or an SDK fan-out across\n\t * the requested repositories (Bitbucket Data Center). There, a dead token comes back as scoped refusals with no\n\t * failure of the connection: the same shape as one scope refusing a sound credential.\n\t * {@link confirmScopedAuthFailures} uses this to tell the two apart.\n\t */\n\tprotected validateCredential?(session: ProviderAuthenticationSession): Promise<void>;\n\n\t/**\n\t * Credentials that passed {@link validateCredential} within the last minute. A probe already in flight is\n\t * shared rather than repeated, and a refused one is not kept. Cleared whenever a credential is refused (see\n\t * {@link handleProviderException}), so a pass is never vouched for past a refusal this instance has seen.\n\t */\n\tprivate readonly _validatedCredentials = new PromiseCache<string, void>({ createTTL: 60 * 1000, capacity: 10 });\n\n\t/**\n\t * Whether a scope's refusal is the credential's own, by the provider's account of it: e.g. a token missing the\n\t * OAuth scopes the read needs. The probe cannot see that, because it proves the token authenticates, not that\n\t * it is authorized. So {@link confirmScopedAuthFailures} publishes one for the connection, unscoped, which asks\n\t * for the reconnect (consenting to the scopes again) that fixes it, and keeps the results of the scopes that\n\t * answered.\n\t */\n\tprotected isCredentialRefusal?(refusal: ProviderRefusal): boolean;\n\n\t/**\n\t * Names why a confirmed credential was refused by one scope, from what the refusal said (see\n\t * `ProviderWarning.cause`). Called only by {@link confirmScopedAuthFailures}, once the credential passed: the\n\t * refusals this names are indistinguishable from a dead credential until then.\n\t */\n\tprotected describeRefusal?(\n\t\tsession: ProviderAuthenticationSession,\n\t\trefusal: ProviderRefusal,\n\t\tscope: ProviderWarningScope,\n\t): ProviderWarningCause | undefined;\n\n\t/**\n\t * Settles what a read's only-scoped authentication failures mean, when the provider can tell (see\n\t * {@link validateCredential}).\n\t *\n\t * A refused credential is thrown: the caller's `catch` then fails the read as a whole, exactly as it does when\n\t * discovery is not cached and the first request is the one refused. Without this, a token revoked after\n\t * discovery would publish scoped `auth` warnings, which a consumer must not answer with a reconnect (see\n\t * `ProviderWarning.scope`), next to results served from the cache, for as long as the token stays stored.\n\t *\n\t * A confirmed credential makes each refusal the scope's own, so its cause is named through\n\t * {@link describeRefusal}. A probe that fails for another reason proves nothing either way and leaves the read\n\t * as it was.\n\t *\n\t * A refusal the provider pins on the credential itself (see {@link isCredentialRefusal}) is published for the\n\t * connection instead, without a probe.\n\t *\n\t * A token that passed within the last minute is not probed again, so a scope that keeps refusing it costs one\n\t * probe a minute, and a revocation right after a probe can take up to a minute to surface: until then its\n\t * refusals stay scoped, and may be named. A probe that proved nothing is not remembered, so the next read probes\n\t * again, and a credential no probe can confirm (e.g. a Bitbucket Data Center project access token) is probed on\n\t * every read that has scoped refusals.\n\t */\n\tprotected async confirmScopedAuthFailures(\n\t\tsession: ProviderAuthenticationSession,\n\t\tmetadata: CollectionMetadata | undefined,\n\t): Promise<void> {\n\t\tif (!hasOnlyScopedAuthFailures(metadata)) return;\n\n\t\t// Published for the connection, as a refusal outside any scope would be, while the scopes that answered keep\n\t\t// their results. That already asks for a reconnect, so there is nothing left for a probe to settle.\n\t\tif (this.markCredentialRefusals(metadata)) return;\n\t\tif ((await this.confirmCredential(session)) !== 'confirmed') return;\n\n\t\tthis.nameRefusalCauses(session, metadata);\n\t}\n\n\t/**\n\t * Settles what a batch read's authentication refusals mean before the call is judged, as\n\t * {@link confirmScopedAuthFailures} settles an account-wide read's: each refused slot comes back carrying the scope\n\t * failure it was recorded as, against the scope its target names (see {@link batchTargetScope}), and a read\n\t * publishes that instead of the bare reason.\n\t *\n\t * When any target answered, that proved the credential, so each refusal is its own target's. When every target\n\t * failed, the call fails as a whole, as it always has, unless every failure is a refusal of a credential that checks\n\t * out. Every target refusing is also what a dead credential looks like, so the credential is checked once first,\n\t * with {@link validateCredential}. A check that proves nothing leaves the refusals scoped and unnamed, as it leaves\n\t * an account-wide read's. A provider with no check cannot tell, and a refusal it pins on the credential (see\n\t * {@link isCredentialRefusal}) needs none, so either still fails the call.\n\t *\n\t * `keepOtherFailures` is for the etag cheap check, whose failed targets fall through to a full read one by one:\n\t * there, only a refused credential fails the call, always on one of the refusals, and every other failure stays\n\t * its own slot's. A batch with refusals and other failures but no answer then has its credential checked, as when\n\t * every target refused, and a provider with no check leaves its refusals scoped and unnamed.\n\t */\n\tprotected async settleBatchRefusals<T>(\n\t\tsession: ProviderAuthenticationSession,\n\t\ttargets: readonly BatchTarget[],\n\t\tslots: PromiseSettledResult<T>[],\n\t\toptions?: { keepOtherFailures?: boolean },\n\t): Promise<BatchSlot<T>[]> {\n\t\tconst keepOtherFailures = options?.keepOtherFailures ?? false;\n\t\tconst failures = slots.map((slot, i) =>\n\t\t\tslot.status === 'rejected' && slot.reason instanceof AuthenticationError\n\t\t\t\t? toCollectionScopeFailure(batchTargetScope(this.id, targets[i]), slot.reason)\n\t\t\t\t: undefined,\n\t\t);\n\t\tconst refused = failures.filter(f => f != null);\n\t\tif (!refused.length) {\n\t\t\tif (!keepOtherFailures) {\n\t\t\t\tthrowIfAllSettledFailed(slots);\n\t\t\t}\n\t\t\treturn slots;\n\t\t}\n\n\t\tconst metadata: CollectionMetadata = { completeness: 'partial', failures: refused };\n\t\tconst credentialRefused = this.markCredentialRefusals(metadata);\n\t\tif (slots.some(slot => slot.status === 'fulfilled')) {\n\t\t\tthis.nameRefusalCauses(session, metadata);\n\t\t} else if (credentialRefused) {\n\t\t\tthrowIfAllSettledFailed(keepOtherFailures ? slots.filter((_, i) => failures[i] != null) : slots);\n\t\t} else if (refused.length < slots.length && !keepOtherFailures) {\n\t\t\t// Fails on a target that failed for another reason: failing on a refusal would run the credential's own\n\t\t\t// recovery (a session expiry or a disconnect strike) for what may be only its target's refusal.\n\t\t\tthrowIfAllSettledFailed(slots.filter((_, i) => failures[i] == null));\n\t\t} else {\n\t\t\tconst confirmation = await this.confirmCredential(session);\n\t\t\tif (confirmation == null) {\n\t\t\t\tif (refused.length === slots.length) {\n\t\t\t\t\tthrowIfAllSettledFailed(slots);\n\t\t\t\t}\n\t\t\t} else if (confirmation === 'confirmed') {\n\t\t\t\tthis.nameRefusalCauses(session, metadata);\n\t\t\t}\n\t\t}\n\n\t\treturn slots.map((slot, i) => {\n\t\t\tconst failure = failures[i];\n\t\t\treturn slot.status === 'rejected' && failure != null ? { ...slot, failure: failure } : slot;\n\t\t});\n\t}\n\n\t/**\n\t * Shows one notice, and spends at most one strike, for a batch read whose targets failed with a server error or a\n\t * timeout. Our own clients leave both to the batch (see `reportRequestFailure`) instead of spending them per\n\t * target. `strike` is true only when every target failed and the call's own failure spent none: a call that\n\t * answered some targets showed the host is up, and budgets strikes the same way as the rest of its failures.\n\t */\n\tprotected reportDeferredRequestFailures(\n\t\tslots: readonly PromiseSettledResult<unknown>[] | undefined,\n\t\tstrike: boolean,\n\t): void {\n\t\tlet notify: (() => void) | undefined;\n\t\tfor (const slot of slots ?? []) {\n\t\t\tif (slot.status !== 'rejected') continue;\n\n\t\t\tnotify = getDeferredRequestFailure(slot.reason);\n\t\t\tif (notify != null) break;\n\t\t}\n\n\t\tif (notify == null) return;\n\n\t\tif (strike) {\n\t\t\tthis.trackRequestException();\n\t\t}\n\t\tnotify();\n\t}\n\n\t/**\n\t * The failure handling of an etag cheap check that failed as a whole. Only a refused credential runs\n\t * {@link handleProviderException}, for its session expiry, re-authentication and strike. Anything else is only\n\t * logged, spending no strike and showing no notice: every target the check held then falls through to a full\n\t * read, which spends and notifies if the host really is failing, or is dropped, as rate-limited or without a\n\t * connection, with the check's warning. A rate limit costs nothing in `handleProviderException` either.\n\t */\n\tprotected handleEtagCheckException(\n\t\tsyncReqUsecase: SyncReqUsecase,\n\t\tex: Error,\n\t\toptions: { scope: ScopedLogger | undefined; connectionId: string | undefined },\n\t): void {\n\t\tif (ex instanceof AuthenticationError) {\n\t\t\tthis.handleProviderException(syncReqUsecase, ex, options);\n\t\t\treturn;\n\t\t}\n\t\tif (isCancellationError(ex)) return;\n\n\t\tif (options.scope != null) {\n\t\t\toptions.scope.error(ex);\n\t\t} else {\n\t\t\tLogger.error(ex);\n\t\t}\n\t}\n\n\t/** Marks the scoped refusals the provider pins on the credential (see {@link isCredentialRefusal}). */\n\tprivate markCredentialRefusals(metadata: CollectionMetadata | undefined): boolean {\n\t\tconst isCredentialRefusal = this.isCredentialRefusal?.bind(this);\n\t\treturn isCredentialRefusal != null && markCredentialRefusals(metadata, isCredentialRefusal);\n\t}\n\n\t/**\n\t * Checks the credential with {@link validateCredential}, rethrowing the provider's refusal when it is refused.\n\t * `undefined` when the provider has no check, and `'unproven'` when the check proved nothing either way.\n\t */\n\tprivate async confirmCredential(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<'confirmed' | 'unproven' | undefined> {\n\t\tconst validateCredential = this.validateCredential?.bind(this);\n\t\tif (validateCredential == null) return undefined;\n\n\t\t// Keyed by the address as well as the token: a self-managed instance serves every installation on its host,\n\t\t// and one installation accepting a token says nothing about another.\n\t\tconst key = [session.domain, session.baseUrl ?? '', session.accessToken].join('\\n');\n\t\ttry {\n\t\t\tawait this._validatedCredentials.getOrCreate(key, () => validateCredential(session));\n\t\t} catch (ex) {\n\t\t\t// A 403 answered a credential that authenticated and was only denied the check's own request, so it proves\n\t\t\t// nothing about the scopes' refusals either.\n\t\t\tif (ex instanceof AuthenticationError && ex.reason !== AuthenticationErrorReason.Forbidden) throw ex;\n\n\t\t\treturn 'unproven';\n\t\t}\n\t\treturn 'confirmed';\n\t}\n\n\t/** Names each scoped refusal's cause (see {@link describeRefusal}); only once the credential is proven. */\n\tprivate nameRefusalCauses(session: ProviderAuthenticationSession, metadata: CollectionMetadata | undefined): void {\n\t\tconst describeRefusal = this.describeRefusal?.bind(this);\n\t\tif (describeRefusal == null) return;\n\n\t\tattributeScopedAuthFailures(metadata, (refusal, scope) => describeRefusal(session, refusal, scope));\n\t}\n\n\t/** Returns whether it spent a strike toward disconnecting (see {@link trackRequestException}). */\n\tprotected handleProviderException(\n\t\tsyncReqUsecase: SyncReqUsecase,\n\t\tex: Error,\n\t\toptions?: { scope?: ScopedLogger | undefined; silent?: boolean; connectionId?: string },\n\t): boolean {\n\t\tif (isCancellationError(ex)) return false;\n\n\t\t// A refused credential may be one a probe passed moments ago: stop vouching for it.\n\t\tif (ex instanceof AuthenticationError) {\n\t\t\tthis._validatedCredentials.clear();\n\t\t}\n\n\t\tif (options?.scope != null) {\n\t\t\toptions.scope.error(ex);\n\t\t} else {\n\t\t\tLogger.error(ex);\n\t\t}\n\n\t\t// A per-connection (multi-account) read resolved its session through `resolveReadSession`'s\n\t\t// `connectionId` branch, which deliberately never touches the cached primary `_session`. So the\n\t\t// primary-session recovery below cannot apply to it: expiring `_session` would mark a session this\n\t\t// read never used, while the rejected connection kept its stored token and re-sent it on every\n\t\t// later read — a token the provider has already refused (expired scopes, a revoked grant, an\n\t\t// uninstalled app) is not self-healing, so the read failed identically until the user reconnected\n\t\t// by hand. Record the rejection against the connection instead; `resolveReadSession` consumes it\n\t\t// and forces the cloud `/refresh` on the next read of that connection. When there is nothing to\n\t\t// recover, fall through to the shared failure budget, which disconnects after\n\t\t// `requestExceptionLimit` and surfaces the reconnect prompt.\n\t\tif (ex instanceof AuthenticationError && options?.connectionId) {\n\t\t\tif (this._rejectedTokens.recordRejection(options.connectionId)) return false;\n\n\t\t\tthis.trackRequestException(options);\n\t\t\treturn true;\n\t\t}\n\n\t\tif (ex instanceof AuthenticationError && this._session?.cloud && !this.hasSessionSyncRequests()) {\n\t\t\tthis.requestSessionSyncForUsecase(syncReqUsecase);\n\t\t\t// Expired now, so a read in progress stops using it, and again once any transition in flight settles, in\n\t\t\t// case it publishes the same token anew (compared by token: a re-read is a new object).\n\t\t\tconst refused = this._session.accessToken;\n\t\t\tthis.expireSessionIfToken(refused);\n\t\t\tvoid this.runSessionTransition(() => {\n\t\t\t\tthis.expireSessionIfToken(refused);\n\t\t\t\treturn Promise.resolve();\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!(ex instanceof AuthenticationError) && !(ex instanceof RequestClientError)) return false;\n\n\t\tthis.trackRequestException(options);\n\t\treturn true;\n\t}\n\n\tprivate expireSessionIfToken(accessToken: string): void {\n\t\tif (this._session?.accessToken !== accessToken || this.connectionExpired === true) return;\n\n\t\tthis._session = { ...this._session, expiresAt: new Date(Date.now() - 1) };\n\t}\n\n\t/** Counts completed cloud re-syncs, so a refresh queued behind one reuses its outcome rather than running another. */\n\tprivate _resyncs = 0;\n\tprivate _lastResyncFailure: Error | undefined;\n\tprivate missingExpirityReported = false;\n\t/**\n\t * Resolves the refresh's failure while the session it could not replace is still unusable, so a read can report\n\t * that instead of reading with an expired token or as a missing connection.\n\t */\n\t@gate()\n\tprotected async refreshSessionIfExpired(): Promise<Error | undefined> {\n\t\tif (this._session?.expiresAt != null && this._session.expiresAt < new Date()) {\n\t\t\t// The current session is expired, so get the latest from the cloud and refresh if needed. Queued, so it\n\t\t\t// sees what any transition in flight settles: a re-sync that already replaced the session, or ran meanwhile\n\t\t\t// and could not, is not repeated, and a disconnect stands.\n\t\t\tconst resyncs = this._resyncs;\n\t\t\treturn this.runSessionTransition(async () => {\n\t\t\t\tlet failure: Error | undefined;\n\t\t\t\tif (this._resyncs !== resyncs) {\n\t\t\t\t\tfailure = this._lastResyncFailure;\n\t\t\t\t} else if (this._session != null && this.connectionExpired === true) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfailure = await this.syncCloudConnectionLocked('connected', true);\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tfailure = toError(ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Only a session still there and still expired is this read's failure, including one a provider\n\t\t\t\t// refusal expired again after the re-sync replaced it.\n\t\t\t\tif (this._session == null || this.connectionExpired !== true) return undefined;\n\n\t\t\t\treturn failure ?? new Error(`The ${this.name} session could not be refreshed`);\n\t\t\t});\n\t\t} else if (\n\t\t\tthis._session?.expiresAt == null &&\n\t\t\tthis.id !== GitCloudHostIntegrationId.GitHub &&\n\t\t\t!this.missingExpirityReported\n\t\t) {\n\t\t\tthis.ctx.hooks?.session?.onRefreshSkipped?.({\n\t\t\t\tid: this.id,\n\t\t\t\treason: 'missing-expiry',\n\t\t\t\tcloud: this._session?.cloud,\n\t\t\t});\n\t\t\tthis.missingExpirityReported = true;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t@trace()\n\ttrackRequestException(options?: { silent?: boolean }): void {\n\t\tthis.requestExceptionCount++;\n\n\t\tif (this.requestExceptionCount >= IntegrationBase.requestExceptionLimit && this._session !== null) {\n\t\t\tif (!options?.silent) {\n\t\t\t\tthis.ctx.hooks?.ui?.onDisconnectedAfterTooManyFailures?.(this.name);\n\t\t\t}\n\t\t\tvoid this.disconnect({ currentSessionOnly: true });\n\t\t}\n\t}\n\n\t@gate()\n\t@trace({ exit: true })\n\tasync isConnected(): Promise<boolean> {\n\t\t// Not `getSession()`: an expired session is still a connection, and refreshing it here would put a GK API round\n\t\t// trip on every connection check and report a throttled refresh as no connection (gitkraken/kepler#3546).\n\t\tconst session =\n\t\t\tthis._session === undefined\n\t\t\t\t? await this.ensureSession({ createIfNeeded: false, source: 'integrations' })\n\t\t\t\t: this._session;\n\t\treturn session != null && this.isSessionForIntegrationHost(session);\n\t}\n\n\t@gate()\n\tprivate async ensureSession(\n\t\toptions:\n\t\t\t| {\n\t\t\t\t\tcreateIfNeeded?: boolean;\n\t\t\t\t\tforceNewSession?: boolean;\n\t\t\t\t\tsync?: never;\n\t\t\t\t\trefetch?: never;\n\t\t\t\t\tsource?: Sources;\n\t\t\t }\n\t\t\t| {\n\t\t\t\t\tcreateIfNeeded?: never;\n\t\t\t\t\tforceNewSession?: never;\n\t\t\t\t\tsync: boolean;\n\t\t\t\t\trefetch?: boolean;\n\t\t\t\t\tsource?: Sources;\n\t\t\t },\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tif (options.createIfNeeded || options.forceNewSession) {\n\t\t\t// The sign-in runs outside the queue: it waits on the user, and its cloud sync re-enters this integration\n\t\t\t// (`syncCloudConnection`, `isConnected`), which would wait for it. What it stored is then resolved in a\n\t\t\t// transition, so a disconnect during the sign-in still stands.\n\t\t\tif (!(await this.signIn(options))) return this._session ?? undefined;\n\n\t\t\treturn this.runSessionTransition(() => this.resolveSessionLocked({ createIfNeeded: false }));\n\t\t}\n\n\t\treturn this.runSessionTransition(() => this.resolveSessionLocked(options));\n\t}\n\n\t/**\n\t * Asks the auth provider for a session interactively, storing what it gets; whether one came back. A declined\n\t * consent or any other failure is not one, and never throws: it is resolved, like any read, from storage.\n\t */\n\tprivate async signIn(options: {\n\t\tcreateIfNeeded?: boolean;\n\t\tforceNewSession?: boolean;\n\t\tsource?: Sources;\n\t}): Promise<boolean> {\n\t\tif (this.ctx.config.isIntegrationsEnabled?.() === false) return false;\n\n\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\ttry {\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\treturn (await authProvider.getSession(this.authProviderDescriptor, options)) != null;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/** Resolves and publishes the session. Only ever runs inside a transition (see {@link runSessionTransition}). */\n\tprivate async resolveSessionLocked(options: {\n\t\tcreateIfNeeded?: boolean;\n\t\tsync?: boolean;\n\t\trefetch?: boolean;\n\t\tsource?: Sources;\n\t}): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst { createIfNeeded, source, sync, refetch } = options;\n\t\t// A forced re-sync keeps the session it is replacing; serving it would skip the refetch the caller forced.\n\t\tif (this._session != null && !refetch) {\n\t\t\tif (this.isSessionForIntegrationHost(this._session)) return this._session;\n\n\t\t\tthis._session = null;\n\t\t}\n\t\tif (this.ctx.config.isIntegrationsEnabled?.() === false) return undefined;\n\n\t\tif (createIfNeeded || sync) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t} else if (this.ctx.storage.getWorkspace(this.connectedKey) === false) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet session: ProviderAuthenticationSession | undefined | null;\n\t\ttry {\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\tsession = await authProvider.getSession(\n\t\t\t\tthis.authProviderDescriptor,\n\t\t\t\tsync\n\t\t\t\t\t? { sync: sync, refetch: refetch, source: source }\n\t\t\t\t\t: { createIfNeeded: createIfNeeded, source: source },\n\t\t\t);\n\n\t\t\tif (session?.expiresAt != null && session.expiresAt < new Date()) {\n\t\t\t\tsession = null;\n\t\t\t}\n\t\t\tif (session != null && !this.isSessionForIntegrationHost(session)) {\n\t\t\t\tsession = undefined;\n\t\t\t}\n\t\t} catch (ex) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\n\t\t\t// Only interactive paths can prompt for consent, so a sync failure must reach the rethrow below.\n\t\t\tif (!sync && ex instanceof Error && ex.message.includes('User did not consent')) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// On a forced re-sync, propagate so syncCloudConnection can tell a failure from a definitive empty\n\t\t\t// result (#5569); other callers keep swallowing to null so reads never throw.\n\t\t\tif (sync) {\n\t\t\t\t// Throwing skips the reset below, and a lingering count blocks the next non-forced sync.\n\t\t\t\tthis.smoothifyRequestExceptionCount();\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\tsession = null;\n\t\t}\n\n\t\tif (session === undefined && !createIfNeeded && !sync) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t}\n\n\t\tthis._session = session ?? null;\n\t\tthis.smoothifyRequestExceptionCount();\n\n\t\tif (session != null) {\n\t\t\tawait this.ctx.storage.storeWorkspace(this.connectedKey, true);\n\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tthis._onDidChange.fire();\n\t\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'connected' });\n\t\t\t\t// Fired detached, so there is no caller left to catch anything: every implementor is async, and\n\t\t\t\t// a rejection would surface as a process-level unhandled rejection in the host. It is a\n\t\t\t\t// best-effort warm-up, so swallow the failure with a warning instead. Never awaited by the\n\t\t\t\t// transition: its reads can disconnect, which queues a transition of their own.\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.providerOnConnect?.();\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tscope?.warn(\n\t\t\t\t\t\t\t`Failed to run providerOnConnect for ${this.key}: ${ex instanceof Error ? ex.message : String(ex)}`,\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}\n\n\t\treturn session ?? undefined;\n\t}\n\n\tgetIgnoreSSLErrors(): boolean | 'force' {\n\t\treturn this.authenticationService.ignoreSSLErrors(this);\n\t}\n\n\tasync searchMyIssues(\n\t\tresource?: ResourceDescriptor,\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined>;\n\tasync searchMyIssues(\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined>;\n\t@trace()\n\tasync searchMyIssues(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchMyIssuesResult(resources, cancellation, connectionId))?.value;\n\t}\n\n\t/**\n\t * Result-returning core of {@link searchMyIssues}. Recovers thrown errors into `{ error }` so callers\n\t * (e.g. the ProviderBackend account-wide issues read) can surface a per-provider warning instead of a\n\t * silent empty result. Returns the normalized {@link IssueShape} (there is no raw account-wide issue read).\n\t */\n\tasync searchMyIssuesResult(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IntegrationResult<IssueShape[] | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\t// `connectionId` targets a specific account (multi-account); omitted reads the primary.\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null || isReadSessionFailure(session)) return session && { error: session.error };\n\n\t\tconst start = performance.now();\n\t\ttry {\n\t\t\tconst issues = await this.searchProviderMyIssues(\n\t\t\t\tsession,\n\t\t\t\tresources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tthis.resetRequestExceptionCount('searchMyIssues');\n\t\t\treturn { value: issues, duration: performance.now() - start };\n\t\t} catch (ex) {\n\t\t\tthis.handleProviderException('searchMyIssues', ex, { scope: scope, connectionId: connectionId });\n\t\t\treturn { error: toError(ex), duration: performance.now() - start };\n\t\t}\n\t}\n\n\tprotected abstract searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined>;\n\n\t/**\n\t * Paging/truncation-aware variant of {@link searchProviderMyIssues}. The default wraps the normalized read\n\t * as a complete single page; providers with native cursors or fan-out metadata override it.\n\t */\n\tprotected async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\t_options?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\t// The default read has no assignee scoping to broaden, so `_options` is inert here; a provider whose\n\t\t// account-wide read is user-scoped (GitHub/GitLab/Azure) overrides this and honors `includeAllAssignees`.\n\t\tconst values = await this.searchProviderMyIssues(session, resources, cancellation);\n\t\tif (values == null) return undefined;\n\t\treturn { values: values, truncated: false };\n\t}\n\n\t/**\n\t * Result-returning, truncation-aware account-wide issue read. Recovers thrown errors into `{ error }` and\n\t * carries the `truncated` flag so the ProviderBackend facade can report an incomplete read honestly.\n\t */\n\tasync searchMyIssuesWithTruncationResult(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<IntegrationResult<AccountWideIssuesResult | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null || isReadSessionFailure(session)) return session && { error: session.error };\n\n\t\ttry {\n\t\t\tconst result = await this.searchProviderMyIssuesWithTruncation(\n\t\t\t\tsession,\n\t\t\t\tresources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined,\n\t\t\t\tcancellation,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tawait this.confirmScopedAuthFailures(session, result?.metadata);\n\t\t\tthis.resetRequestExceptionCount('searchMyIssues');\n\t\t\treturn { value: result };\n\t\t} catch (ex) {\n\t\t\tthis.handleProviderException('searchMyIssues', ex, { scope: scope, connectionId: connectionId });\n\t\t\treturn { error: toError(ex) };\n\t\t}\n\t}\n\n\t@trace()\n\tasync getLinkedIssueOrPullRequest(\n\t\tresource: T,\n\t\tlink: { id: string; key: string },\n\t\toptions?: { expiryOverride?: boolean | number; type?: IssueOrPullRequestType; throwOnError?: boolean },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tif ((await this.prepareSessionLookup()) != null) return undefined;\n\n\t\tconst { throwOnError, ...cacheOptions } = options ?? {};\n\n\t\tconst issueOrPR = this.ctx.cache.getIssueOrPullRequest(\n\t\t\tlink.key,\n\t\t\toptions?.type,\n\t\t\tresource,\n\t\t\tthis,\n\t\t\tcacheable => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.getProviderLinkedIssueOrPullRequest(\n\t\t\t\t\t\t\tthis._session!,\n\t\t\t\t\t\t\tresource,\n\t\t\t\t\t\t\tlink,\n\t\t\t\t\t\t\toptions?.type,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getIssueOrPullRequest');\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t// A failed lookup is not an answer — and a missed issue/PR never expires from the cache.\n\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\tthis.handleProviderException('getIssueOrPullRequest', ex, { scope: scope });\n\t\t\t\t\t\tif (throwOnError) throw ex;\n\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\tcacheOptions,\n\t\t);\n\t\treturn issueOrPR;\n\t}\n\n\tprotected abstract getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tlink: { id: string; key: string },\n\t\ttype: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined>;\n\n\tasync getIssue(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { connectionId?: string; expiryOverride?: boolean | number },\n\t): Promise<Issue | undefined> {\n\t\treturn (await this.getIssueResult(resource, id, options))?.value;\n\t}\n\n\tgetIssueResult(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { connectionId?: string; expiryOverride?: boolean | number },\n\t): Promise<IntegrationResult<Issue | undefined>> {\n\t\treturn this.getIssueResultCore(resource, id, options, session => this.getProviderIssue(session, resource, id));\n\t}\n\n\t@trace()\n\tprotected async getIssueResultCore(\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t\toptions: { connectionId?: string; expiryOverride?: boolean | number } | undefined,\n\t\tgetProviderIssue: (session: ProviderAuthenticationSession) => Promise<Issue | undefined>,\n\t): Promise<IntegrationResult<Issue | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\tconst { connectionId: requestedConnectionId, expiryOverride } = options ?? {};\n\t\tconst connectionId = requestedConnectionId || undefined;\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null || isReadSessionFailure(session)) return session && { error: session.error };\n\n\t\ttry {\n\t\t\tconst issue = await getCachedIssue({\n\t\t\t\tcache: this.ctx.cache,\n\t\t\t\tid: id,\n\t\t\t\tresource: resource,\n\t\t\t\tintegration: this,\n\t\t\t\tload: async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst issue = await getProviderIssue(session);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getIssue');\n\t\t\t\t\t\treturn issue;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\t\t\t\tthis.handleProviderException('getIssue', toError(ex), {\n\t\t\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tcacheOptions: {\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\texpiryOverride: expiryOverride,\n\t\t\t\t\tetag: `${this.id}:${this.maybeConnected ?? false}:${this.getSessionFingerprint(session)}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn { value: issue };\n\t\t} catch (ex) {\n\t\t\treturn { error: toError(ex) };\n\t\t}\n\t}\n\n\tprotected abstract getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tid: string,\n\t): Promise<Issue | undefined>;\n\n\tasync getCurrentAccount(options?: {\n\t\tavatarSize?: number;\n\t\tconnectionId?: string;\n\t\texpiryOverride?: boolean | number;\n\t}): Promise<Account | undefined> {\n\t\tconst scope = getScopedLogger();\n\t\tconst { connectionId: requestedConnectionId, expiryOverride, ...opts } = options ?? {};\n\t\tconst connectionId = requestedConnectionId || undefined;\n\t\tconst session = await this.resolveReadSessionOrThrow(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\tconst sessionFingerprint = this.getSessionFingerprint(session);\n\n\t\tconst currentAccount = await this.ctx.cache.getCurrentAccount(\n\t\t\tthis,\n\n\t\t\t(cacheable: any) => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst account = await this.getProviderCurrentAccount?.(session, opts);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getCurrentAccount');\n\t\t\t\t\t\treturn account;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tif (isCancellationError(ex) && !isProviderUnreachableError(ex)) {\n\t\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.handleProviderException('getCurrentAccount', ex, {\n\t\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Invalidate the cache on error, except for auth errors\n\t\t\t\t\t\tif (!(ex instanceof AuthenticationError)) {\n\t\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Re-throw to the caller\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\texpiryOverride: expiryOverride,\n\t\t\t\texpireOnError: false,\n\t\t\t\tetag: `${this.id}:${this.maybeConnected ?? false}:${sessionFingerprint}`,\n\t\t\t},\n\t\t);\n\t\treturn currentAccount;\n\t}\n\n\tprotected getProviderCurrentAccount?(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { avatarSize?: number },\n\t): Promise<Account | undefined>;\n\n\t/** Whether this integration can answer {@link getCurrentAccount} at all — Jira Data Center cannot. */\n\tget supportsCurrentAccount(): boolean {\n\t\treturn this.getProviderCurrentAccount != null;\n\t}\n\n\t/**\n\t * Resolves the account for a specific session/token — including connections other than the current\n\t * primary (multi-account) — using this integration's provider API base URL and auth type. Returns\n\t * undefined when the provider doesn't support account lookup. Uncached (callers cache per connection).\n\t */\n\tgetProviderAccountForSession(session: ProviderAuthenticationSession): Promise<Account | undefined> {\n\t\treturn this.getProviderCurrentAccount?.(session) ?? Promise.resolve(undefined);\n\t}\n\n\t/**\n\t * Resolves the workspace a specific session's token was granted for, separate from the person, asking the\n\t * provider every time so a renamed workspace is picked up. Undefined when the provider has no such workspace.\n\t */\n\tgetProviderResourceForSession(_session: ProviderAuthenticationSession): Promise<ConnectionResource | undefined> {\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\t@trace()\n\tasync getPullRequest(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { expiryOverride?: boolean | number; throwOnError?: boolean },\n\t): Promise<PullRequest | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst refreshFailure = await this.prepareSessionLookup();\n\t\tif (refreshFailure === 'unconnected') return undefined;\n\t\tif (refreshFailure != null) {\n\t\t\tif (options?.throwOnError) throw refreshFailure;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst pr = await this.ctx.cache.getPullRequest(\n\t\t\tid,\n\t\t\tresource,\n\t\t\tthis,\n\t\t\tcacheable => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.getProviderPullRequest?.(this._session!, resource, id);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getPullRequest');\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t// A failed lookup is not an answer — and the by-id bucket never expires a miss.\n\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\tthis.handleProviderException('getPullRequest', ex, { scope: scope });\n\t\t\t\t\t\tif (options?.throwOnError) throw ex;\n\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\t{ expiryOverride: options?.expiryOverride },\n\t\t);\n\t\treturn pr;\n\t}\n\n\tprotected getProviderPullRequest?(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tid: string,\n\t): Promise<PullRequest | undefined>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"integration.js","names":[],"sources":["../../../../../plus/integrations/src/models/integration.ts"],"sourcesContent":["import type { CollectionMetadata, CollectionScope } from '@gitkraken/provider-apis';\nimport { getDeferredRequestFailure } from '@gitlens/git/errors.js';\nimport type { Account } from '@gitlens/git/models/author.js';\nimport type { AutolinkReference, DynamicAutolinkReference } from '@gitlens/git/models/autolink.js';\nimport type { Issue, IssueShape } from '@gitlens/git/models/issue.js';\nimport type {\n\tIssueOrPullRequest,\n\tIssueOrPullRequestState,\n\tIssueOrPullRequestType,\n} from '@gitlens/git/models/issueOrPullRequest.js';\nimport type {\n\tPullRequest,\n\tPullRequestMergeableState,\n\tPullRequestReviewDecision,\n\tPullRequestReviewState,\n\tPullRequestState,\n\tPullRequestStatusCheckRollupState,\n} from '@gitlens/git/models/pullRequest.js';\nimport type { ResourceDescriptor } from '@gitlens/git/models/resourceDescriptor.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { gate } from '@gitlens/utils/decorators/gate.js';\nimport { debug, trace } from '@gitlens/utils/decorators/log.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 { fnv1aHash64 } from '@gitlens/utils/hash.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { ScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { PromiseCache } from '@gitlens/utils/promiseCache.js';\nimport type {\n\tIntegrationAuthenticationProviderDescriptor,\n\tIntegrationAuthenticationSessionDescriptor,\n} from '../authentication/integrationAuthenticationProvider.js';\nimport type { IntegrationAuthenticationService } from '../authentication/integrationAuthenticationService.js';\nimport type { ConnectionResource, ProviderAuthenticationSession } from '../authentication/models.js';\nimport { RejectedTokenTracker } from '../authentication/rejectedTokenTracker.js';\nimport type { ProviderRefusal, ProviderScopeFailure } from '../collectionMetadata.js';\nimport {\n\tattributeScopedAuthFailures,\n\thasOnlyScopedAuthFailures,\n\tmarkCredentialRefusals,\n\ttoCollectionScopeFailure,\n} from '../collectionMetadata.js';\nimport type { IntegrationIds, IssuesCloudHostIntegrationId, IssuesHostIntegrationIds } from '../constants.js';\nimport { GitCloudHostIntegrationId } from '../constants.js';\nimport type { IntegrationServiceContext } from '../context.js';\nimport {\n\tAuthenticationError,\n\tAuthenticationErrorReason,\n\tisProviderUnreachableError,\n\tRequestClientError,\n\ttoError,\n} from '../errors.js';\nimport type { IntegrationConnectionChangeEvent } from '../integrationService.js';\nimport { providersMetadata } from '../providers/models.js';\nimport { isAzureProviderId } from '../providers/providerErrors.js';\nimport type { ProvidersApi } from '../providers/providersApi.js';\nimport { throwIfAllSettledFailed } from '../providers/utils/providerPaging.js';\nimport type { ProviderWarningCause, ProviderWarningScope } from '../results.js';\nimport type { Sources } from '../telemetry.js';\nimport { areDomainsOnSameHost } from '../utils/domain.utils.js';\nimport { isSelfManagedHostIntegrationId } from '../utils/integration.utils.js';\nimport type { GitHostIntegration } from './gitHostIntegration.js';\nimport { getCachedIssue } from './issueCache.js';\nimport type { AccountWideIssuesResult, SearchMyIssuesOptions } from './issueReads.js';\nimport type { IssuesIntegration } from './issuesIntegration.js';\n\nexport type Integration = GitHostIntegration | IssuesIntegration;\nexport type IntegrationById<T extends IntegrationIds> = T extends IssuesHostIntegrationIds\n\t? IssuesIntegration\n\t: GitHostIntegration;\nexport type IntegrationType = 'git' | 'issues';\n\n// The issue-read contracts live in their own module (pure data, and their relationship to each other is the\n// point of reading them together); re-exported here so the providers that implement these reads keep one import.\nexport type {\n\tAccountWideIssuesResult,\n\tProviderIssueSearchPage,\n\tProviderSearchCount,\n\tSearchMyIssuesOptions,\n} from './issueReads.js';\nexport type { ProviderPullRequestCount, ProviderPullRequestSearchPage } from './pullRequestReads.js';\n\n// Keep this in step with `isSelfManagedHostIntegrationId`: it is the type-level twin of that predicate, and a\n// disagreement compiles cleanly while the runtime writes a domain-keyed key the type says is unkeyed (so the\n// local-disconnect flag is written under one key and read under another).\nexport type IntegrationKey<T extends IntegrationIds = IntegrationIds> = T extends\n\t| GitCloudHostIntegrationId\n\t| IssuesCloudHostIntegrationId\n\t? `${T}`\n\t: `${T}:${string}`;\n\nexport type IntegrationConnectedKey<T extends IntegrationIds = IntegrationIds> = `connected:${IntegrationKey<T>}`;\n\nexport type IntegrationResult<T> =\n\t| { value: T; duration?: number; error?: Error }\n\t| { error: Error; duration?: number; value?: never }\n\t| undefined;\n\n/** A per-connection read's session that could not be resolved for a retryable reason (see `resolveReadSession`). */\nexport class ReadSessionFailure {\n\tconstructor(readonly error: Error) {}\n}\n\nexport function isReadSessionFailure(\n\tsession: ProviderAuthenticationSession | ReadSessionFailure | undefined,\n): session is ReadSessionFailure {\n\treturn session instanceof ReadSessionFailure;\n}\n\n/**\n * One target's answer in a batch read. A slot rejected by an authentication refusal carries the scope failure it was\n * recorded as (see `IntegrationBase.settleBatchRefusals`).\n */\nexport type BatchSlot<T> = PromiseFulfilledResult<T> | (PromiseRejectedResult & { failure?: ProviderScopeFailure });\n\n/**\n * An input a pull request's etag can be widened to, each of which a host changes without moving `updatedAt`. Every\n * entry costs its own fields in the cheap check, so a caller picks only the ones it needs. `'checks'` is the check\n * rollup.\n */\nexport type PullRequestEtagInclude = 'mergeable' | 'reviewDecision' | 'checks';\n\n/** Every {@link PullRequestEtagInclude}, in the canonical order an etag lists them in. */\nexport const pullRequestEtagIncludes: readonly PullRequestEtagInclude[] = ['mergeable', 'reviewDecision', 'checks'];\n\n/**\n * The change state a batch read's etag is computed from (see `reads/etag.ts`), in the NORMALIZED vocabulary of\n * `PullRequestShape` — the values a full row carries after its provider's whole conversion chain, so a cheap\n * check and a full read of the same pull request yield the same fields. Each of `mergeableState`, `reviewDecision`\n * and `statusCheckRollupState` counts only when its {@link PullRequestEtagInclude} is requested.\n */\nexport interface PullRequestEtagFields {\n\tstate: PullRequestState;\n\tisDraft?: boolean;\n\tupdatedDate: Date;\n\theadSha?: string;\n\tmergeableState?: PullRequestMergeableState;\n\treviewDecision?: PullRequestReviewDecision;\n\tstatusCheckRollupState?: PullRequestStatusCheckRollupState;\n\t/**\n\t * A fingerprint of the fields a host changes without moving an update time ({@link pullRequestRevision}); set\n\t * only for hosts that keep none (Azure DevOps and Server). Part of the etag whenever it is set, not an include.\n\t */\n\trevision?: string;\n\t/**\n\t * A full row marked `viewerUnresolved`, which no cheap check ever sets: such a row never compares unchanged, so it\n\t * is read in full again rather than kept without a viewer until the pull request changes.\n\t */\n\tviewerUnresolved?: true;\n}\n\n/**\n * The fields of a pull request {@link pullRequestRevision} reads, as a full row carries them. A `PullRequestShape`\n * is one, so a full read passes its row as is and a cheap check builds one from the same mapped values.\n */\nexport interface PullRequestRevisionSource {\n\treadonly title: string;\n\treadonly body?: string;\n\treadonly refs?: { readonly base: { readonly branch: string } };\n\treadonly reviewRequests?: readonly PullRequestRevisionReviewer[];\n\treadonly latestReviews?: readonly PullRequestRevisionReviewer[];\n}\n\ninterface PullRequestRevisionReviewer {\n\treadonly reviewer: { readonly id: string };\n\treadonly state: PullRequestReviewState;\n}\n\n/**\n * {@link PullRequestEtagFields.revision}: a 64-bit hash of a pull request's title, description, target branch and\n * reviewers (each one's id and review state, in a canonical order). Hashed to keep the etag short, so a collision is\n * the only way a change can hide, at about 2^-64.\n */\nexport function pullRequestRevision(pr: PullRequestRevisionSource): string {\n\treturn fnv1aHash64(\n\t\tJSON.stringify([\n\t\t\tpr.title,\n\t\t\tpr.body ?? null,\n\t\t\tpr.refs?.base.branch ?? null,\n\t\t\ttoRevisionReviewers(pr.reviewRequests),\n\t\t\ttoRevisionReviewers(pr.latestReviews),\n\t\t]),\n\t);\n}\n\n/** Each reviewer as its serialized `[id, state]`, sorted, so the host's order never moves the revision. */\nfunction toRevisionReviewers(reviewers: readonly PullRequestRevisionReviewer[] | undefined): string[] | null {\n\treturn reviewers?.map(r => JSON.stringify([r.reviewer.id, r.state])).sort() ?? null;\n}\n\n/**\n * An input an issue's etag can be widened to, which a host changes without moving `updatedAt`. `'reactions'` is the\n * thumbs-up count, and widens the etag only on hosts whose rows really fetch one (GitHub/GHE and GitLab).\n */\nexport type IssueEtagInclude = 'reactions';\n\n/** Every {@link IssueEtagInclude}, in the canonical order an etag lists them in. */\nexport const issueEtagIncludes: readonly IssueEtagInclude[] = ['reactions'];\n\n/** The issue twin of {@link PullRequestEtagFields}, in {@link IssueShape}'s normalized vocabulary. */\nexport interface IssueEtagFields {\n\tstate: IssueOrPullRequestState | undefined;\n\tupdatedDate: Date;\n\t/** Counts only when `'reactions'` is requested, and only from a row whose read fetched reactions. */\n\tthumbsUpCount?: number;\n\t/**\n\t * Azure DevOps only: the version of the rule a work item's body is read by ({@link azureWorkItemDescriptionRule}).\n\t * A change of rule changes the body without moving `updatedDate`, so a row read under an earlier one must not\n\t * compare unchanged.\n\t */\n\tdescriptionRule?: number;\n\t/**\n\t * Azure DevOps only: the version of the rule a work item's `viewer` is set by ({@link azureWorkItemViewerRule}).\n\t * A full row that names someone but got no viewer (its organization's identity couldn't be read) leaves it out, so\n\t * it never compares unchanged and is read in full again rather than kept without one.\n\t */\n\tviewerRule?: number;\n}\n\n/**\n * {@link IssueEtagFields.descriptionRule}: the version of the rule an Azure DevOps work item's body is read by, stamped\n * on both sides of its etag. Bumped whenever the rule changes which field a work item's body comes from, so every row\n * a caller holds from before is read in full once. 1: the first rich text field with content (provider-apis'\n * `getAzureWorkItemDescription`), where it used to be only Description. The rule lives in provider-apis, so a bump of\n * that package that changes it must bump this too; the etag tests pin the rule to catch one.\n *\n * It can't cover a row whose read found the organization's field catalog unavailable: that row's body falls back to\n * the built-in fields, and a cheap check, which reads no body, can't tell it from a complete one, so it stays as read\n * until the work item changes.\n */\nexport const azureWorkItemDescriptionRule = 1;\n\n/**\n * {@link IssueEtagFields.viewerRule}: the version of the rule an Azure DevOps work item's `viewer` is set by, stamped\n * on both sides of its etag, so every row a caller holds from before it was set is read in full once. 1: the user's\n * identity in the work item's organization (gitkraken/kepler#4291).\n */\nexport const azureWorkItemViewerRule = 1;\n\n/** A batch read's target, in the form its provider addresses it by: a repository coordinate, or a tracker's resource. */\ntype BatchTarget = { owner: string; repo: string; project?: string } | { resourceId: string };\n\n/**\n * The scope a batch read records a refused target under. It is decided here rather than in the read, which has the\n * same targets, because naming the refusal's cause (`describeRefusal`) needs it too. Azure DevOps: the organization\n * and project, since its refusals come from those (an organization's OAuth policy or tenant, a project's permissions)\n * and its work items belong to no repository. Every other git host: the repository, `owner/repo`, which is how its\n * repo-scoped reads record one. A tracker: the resource.\n */\nfunction batchTargetScope(providerId: IntegrationIds, target: BatchTarget): CollectionScope {\n\tif ('resourceId' in target) return { providerId: providerId, resourceId: target.resourceId };\n\tif (isAzureProviderId(providerId)) {\n\t\treturn { providerId: providerId, resourceId: target.owner, projectId: target.project };\n\t}\n\treturn { providerId: providerId, repositoryId: `${target.owner}/${target.repo}` };\n}\n\ntype SyncReqUsecase = Exclude<\n\t| 'getAccountForCommit'\n\t| 'getAccountForEmail'\n\t| 'getAccountForResource'\n\t| 'getCurrentAccount'\n\t| 'getDefaultBranch'\n\t| 'getIssue'\n\t| 'getIssueOrPullRequest'\n\t| 'getIssuesBatch'\n\t| 'getIssuesEtagFields'\n\t| 'getIssuesForProject'\n\t| 'getIssuesForRepos'\n\t| 'getMyPullRequestsForUser'\n\t| 'getOrganizationsForUser'\n\t| 'getProjectsForOrg'\n\t| 'getProjectsForResources'\n\t| 'getPullRequest'\n\t| 'getPullRequestsBatch'\n\t| 'getPullRequestsEtagFields'\n\t| 'getPullRequestsForBranches'\n\t| 'getRepositoriesForOrg'\n\t| 'getRepositoriesForUser'\n\t| 'getPullRequestForBranch'\n\t| 'getPullRequestForCommit'\n\t| 'getPullRequestsForRepos'\n\t| 'getRepositoryMetadata'\n\t| 'getResourcesForUser'\n\t| 'getSshSigningKeysForEmails'\n\t| 'countIssues'\n\t| 'countPullRequests'\n\t| 'mergePullRequest'\n\t| 'searchIssuesPage'\n\t| 'searchPullRequestsPage'\n\t| 'searchMyIssues'\n\t| 'searchMyPullRequests'\n\t| 'searchPullRequests',\n\t// excluding to show explicitly that we don't want to add 'all' key occasionally\n\t'all'\n>;\n\nexport abstract class IntegrationBase<\n\tID extends IntegrationIds = IntegrationIds,\n\tT extends ResourceDescriptor = ResourceDescriptor,\n> implements Disposable {\n\tabstract readonly type: IntegrationType;\n\n\tprivate readonly _onDidChange = new Emitter<void>();\n\tget onDidChange(): Event<void> {\n\t\treturn this._onDidChange.event;\n\t}\n\n\tconstructor(\n\t\tprotected readonly ctx: IntegrationServiceContext,\n\t\tprotected readonly authenticationService: IntegrationAuthenticationService,\n\t\tprotected readonly getProvidersApi: () => Promise<ProvidersApi>,\n\t\tprivate readonly didChangeConnection: Emitter<IntegrationConnectionChangeEvent>,\n\t) {}\n\n\tdispose(): void {\n\t\tthis._onDidChange.dispose();\n\t}\n\n\tabstract get authProvider(): IntegrationAuthenticationProviderDescriptor;\n\tabstract get id(): ID;\n\tprotected abstract get key(): IntegrationKey<ID>;\n\tabstract get name(): string;\n\tabstract get domain(): string;\n\n\tget authProviderDescriptor(): IntegrationAuthenticationSessionDescriptor {\n\t\treturn { domain: this.domain, scopes: this.authProvider.scopes };\n\t}\n\n\t/**\n\t * The `gl-provider-<key>` glicon key for this provider, which is NOT always its id.\n\t *\n\t * `providersMetadata.iconKey` is the field that says which glyph a provider draws with, and a\n\t * self-managed variant reuses its cloud family's — Jira Data Center draws with Jira's, because it is the\n\t * same product and the id only selects an icon. Returning the id instead named a glyph the registry has\n\t * no entry for, so a consumer rendering a `gl-provider-` glicon from `issue.provider.icon` requested a\n\t * glyph that does not exist.\n\t */\n\tget icon(): string {\n\t\treturn providersMetadata[this.id]?.iconKey ?? this.id;\n\t}\n\n\taccess(): Promise<boolean> {\n\t\treturn this.ctx.account.isTrialOrPaid();\n\t}\n\n\tautolinks():\n\t\t| (AutolinkReference | DynamicAutolinkReference)[]\n\t\t| Promise<(AutolinkReference | DynamicAutolinkReference)[]> {\n\t\treturn [];\n\t}\n\n\tprivate get connectedKey(): IntegrationConnectedKey<ID> {\n\t\treturn `connected:${this.key}`;\n\t}\n\n\tget maybeConnected(): boolean | undefined {\n\t\treturn this._session === undefined ? undefined : this._session !== null;\n\t}\n\n\t/** Hash of the current session's access token. Changes on any token change (account switch or refresh). */\n\tprivate _sessionFingerprint: { session: ProviderAuthenticationSession; hash: string } | undefined;\n\tget sessionFingerprint(): string | undefined {\n\t\tif (this._session == null) return undefined;\n\n\t\tif (this._sessionFingerprint?.session !== this._session) {\n\t\t\tthis._sessionFingerprint = { session: this._session, hash: this.getSessionFingerprint(this._session) };\n\t\t}\n\t\treturn this._sessionFingerprint.hash;\n\t}\n\n\tprotected getSessionFingerprint(session: ProviderAuthenticationSession): string {\n\t\treturn fnv1aHash64(session.accessToken);\n\t}\n\n\tget connectionExpired(): boolean | undefined {\n\t\tif (this._session?.expiresAt == null) return undefined;\n\t\treturn new Date(this._session.expiresAt) < new Date();\n\t}\n\n\tprotected _session: ProviderAuthenticationSession | null | undefined;\n\t/**\n\t * Every change to the session (a resolution, a forced re-sync, a disconnect, a reauthentication, a connection\n\t * switch, a refused token) runs here, one at a time. Each one reads storage and the cloud and then settles\n\t * `_session`, the stored tokens and the connected flag; run concurrently, one would publish what it read over\n\t * what another settled meanwhile (gitkraken/kepler#3546). Reads don't queue: they read `_session` as it is.\n\t */\n\tprivate _sessionTransitions: Promise<void> = Promise.resolve();\n\t/**\n\t * Runs `fn` after every transition queued before it. `fn` must not queue another transition and wait for it,\n\t * which would wait for itself: inside a transition, call the `*Locked` methods instead.\n\t */\n\tprotected runSessionTransition<T>(fn: () => Promise<T>): Promise<T> {\n\t\tconst run = this._sessionTransitions.then(fn);\n\t\tthis._sessionTransitions = run.then(\n\t\t\t() => undefined,\n\t\t\t() => undefined,\n\t\t);\n\t\treturn run;\n\t}\n\n\t/** An expired session is refreshed first; when that fails, nothing is returned rather than a token the provider refuses. */\n\tgetSession(\n\t\tsource: Sources,\n\t): ProviderAuthenticationSession | Promise<ProviderAuthenticationSession | undefined> | undefined {\n\t\t// Served as is even while a transition runs: a forced re-sync keeps the session it is replacing, and a read\n\t\t// meanwhile reads with it rather than waiting on the cloud.\n\t\tif (this._session === undefined) {\n\t\t\treturn this.ensureSession({ createIfNeeded: false, source: source });\n\t\t}\n\t\tif (this._session == null || !this.isSessionForIntegrationHost(this._session)) return undefined;\n\t\tif (this.connectionExpired) return this.getRefreshedSession();\n\n\t\treturn this._session;\n\t}\n\n\tprivate async getRefreshedSession(): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst failure = await this.refreshSessionIfExpired();\n\t\tif (failure != null) return undefined;\n\n\t\t// The refresh may have been skipped (a non-cloud session) or overtaken by a disconnect or a switch.\n\t\treturn this._session != null &&\n\t\t\tthis.connectionExpired !== true &&\n\t\t\tthis.isSessionForIntegrationHost(this._session)\n\t\t\t? this._session\n\t\t\t: undefined;\n\t}\n\n\tprivate isSessionForIntegrationHost(session: ProviderAuthenticationSession): boolean {\n\t\tif (!isSelfManagedHostIntegrationId(this.id)) return true;\n\n\t\treturn areDomainsOnSameHost(this.domain, session.domain);\n\t}\n\n\t/**\n\t * Resolves the session to read as, for a per-connection (multi-account) read. When `connectionId` is\n\t * omitted this is the integration's primary session, resolved exactly like the existing read flow\n\t * (ensure-connected + refresh-if-expired). When set, it resolves THAT connection's session directly\n\t * from the auth provider — refreshing it if expired — WITHOUT disturbing the cached primary\n\t * `_session`. Never throws: `undefined` when there is no session to read with (the connection is gone,\n\t * the provider isn't connected), `{ error }` when the connection's token could not be fetched.\n\t *\n\t * The two must not be conflated. A token fetch that THROWS is, by `getConnectionSession`'s contract, a\n\t * retryable failure (a rate-limited or failing GK API, a network error), not evidence that the connection is\n\t * gone (#5569). Answering \"no session\" for it made every connection read during a GK API 429 report\n\t * `no-connection`, which a consumer rightly treats as a credential to reconnect (gitkraken/kepler#3546).\n\t * Returned as the read's error instead, the read core surfaces it like any other failed request: a rate\n\t * limit as `rate-limit`, anything else as `other`.\n\t */\n\tprotected async resolveReadSession(\n\t\tconnectionId: string | undefined,\n\t\tscope: ScopedLogger | undefined,\n\t\tsource?: Sources,\n\t): Promise<ProviderAuthenticationSession | ReadSessionFailure | undefined> {\n\t\tif (\n\t\t\tthis.ctx.config.isIntegrationsEnabled?.() === false ||\n\t\t\tthis.ctx.storage.getWorkspace(this.connectedKey) === false\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// A truthy connectionId targets a specific account; an empty string is not a real target, so it falls\n\t\t// through to the primary path below.\n\t\tif (connectionId) {\n\t\t\t// A read of this connection previously failed with an AuthenticationError, so the token in\n\t\t\t// storage is known-refused. Ask for a refresh through the GK cloud before reading again, which\n\t\t\t// is this branch's equivalent of the primary path's `refreshSessionIfExpired`. Claimed here so\n\t\t\t// the forced refresh runs at most once per rejection.\n\t\t\tconst refreshRejectedToken = this._rejectedTokens.claimRefresh(connectionId);\n\t\t\t// Never throws, matching the primary path (whose ensureSession/refreshSessionIfExpired swallow\n\t\t\t// errors), so read methods keep their never-throws contract.\n\t\t\ttry {\n\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\tconst session = await authProvider.getSession(\n\t\t\t\t\t{ ...this.authProviderDescriptor, connectionId: connectionId, cloud: true },\n\t\t\t\t\t{ source: source, refreshRejectedToken: refreshRejectedToken },\n\t\t\t\t);\n\t\t\t\treturn session != null && this.isSessionForIntegrationHost(session) ? session : undefined;\n\t\t\t} catch (ex) {\n\t\t\t\tif (isCancellationError(ex)) return undefined;\n\n\t\t\t\tscope?.error(ex);\n\t\t\t\treturn new ReadSessionFailure(toError(ex));\n\t\t\t}\n\t\t}\n\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return undefined;\n\n\t\t// The primary path's equivalent of the per-connection failure above: an expired session the cloud could not\n\t\t// replace right now is the refresh's failure, not a missing connection, and is never read with.\n\t\tconst refreshFailure = await this.refreshSessionIfExpired();\n\t\tif (refreshFailure != null) return new ReadSessionFailure(refreshFailure);\n\n\t\treturn this._session != null && this.isSessionForIntegrationHost(this._session) ? this._session : undefined;\n\t}\n\n\t/**\n\t * The session lookups' prelude for a caller that can surface a failure (`throwOnError`): the refresh's failure to\n\t * report, or `'unconnected'`, so a throttled refresh is not mistaken for \"no pull request\" and kept.\n\t */\n\tprotected async prepareSessionLookup(): Promise<Error | 'unconnected' | undefined> {\n\t\tconst connected = this.maybeConnected ?? (await this.isConnected());\n\t\tif (!connected) return 'unconnected';\n\n\t\tconst failure = await this.refreshSessionIfExpired();\n\t\tif (failure != null) return failure;\n\n\t\t// A disconnect, or a refetch the cloud answered for another host, can leave no session to read with.\n\t\treturn this._session != null ? undefined : 'unconnected';\n\t}\n\n\t/**\n\t * {@link resolveReadSession} for a read that already rethrows its provider's failures (a repository or account\n\t * lookup), or whose callers treat any failure as \"nothing to show\": a token that could not be fetched is\n\t * thrown like those failures, so it is classified as the failed request it is rather than as a missing\n\t * connection.\n\t */\n\tprotected async resolveReadSessionOrThrow(\n\t\tconnectionId: string | undefined,\n\t\tscope: ScopedLogger | undefined,\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (isReadSessionFailure(session)) throw session.error;\n\n\t\treturn session;\n\t}\n\n\t@debug()\n\tasync connect(source: Sources): Promise<boolean> {\n\t\ttry {\n\t\t\treturn Boolean(await this.ensureSession({ createIfNeeded: true, source: source }));\n\t\t} catch (_ex) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprotected providerOnConnect?(): void | Promise<void>;\n\n\t@gate()\n\t@debug()\n\tasync disconnect(options?: { silent?: boolean; currentSessionOnly?: boolean }): Promise<void> {\n\t\tif (options?.currentSessionOnly && this._session === null) return;\n\n\t\tlet signOut = !options?.currentSessionOnly;\n\n\t\t// Asked before queueing: the user's answer can take any time, and the queue must not wait on it.\n\t\tif (this._session != null && !options?.currentSessionOnly && !options?.silent) {\n\t\t\tconst decision = await this.ctx.hooks?.onConfirmDisconnect?.({\n\t\t\t\tintegrationName: this.name,\n\t\t\t\tofferSignOut: this.authenticationService.supports(this.authProvider.id),\n\t\t\t});\n\t\t\tif (decision == null) return;\n\n\t\t\tsignOut = decision.signOut;\n\t\t}\n\n\t\t// Waits for a forced re-sync in flight, so the session it settles cannot land over the disconnect.\n\t\tawait this.runSessionTransition(() => this.disconnectLocked(options, signOut));\n\t}\n\n\tprivate async disconnectLocked(\n\t\toptions: { silent?: boolean; currentSessionOnly?: boolean } | undefined,\n\t\tsignOut: boolean,\n\t): Promise<void> {\n\t\tif (options?.currentSessionOnly && this._session === null) return;\n\n\t\tconst connected = this._session != null;\n\n\t\tif (signOut) {\n\t\t\t// Disconnecting a provider signs out of ALL its connected accounts (multi-account), not just the\n\t\t\t// primary — otherwise secondary connections' secrets/config would be orphaned. Removing a single\n\t\t\t// account is done via IntegrationService.deleteConnection instead. Pass this instance's descriptor\n\t\t\t// so self-managed disconnects stay scoped to this host: those group every host under one provider\n\t\t\t// id, so an unscoped clear would sign the user out of unrelated hosts. deleteAllSessions derives an\n\t\t\t// undefined domain for cloud providers, so they still clear every account as intended.\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t// Awaited, not fire-and-forget: a caller that awaits `disconnect()` has to be able to rely on the\n\t\t\t// secrets and descriptors actually being gone when it resumes. Left floating, the only thing that\n\t\t\t// ever made this land in time was incidental scheduling slack — `syncCloudIntegrations` used to\n\t\t\t// await each provider in turn, so a later iteration's suspension let the previous provider's delete\n\t\t\t// finish. Syncing providers concurrently removes that slack and the clear was observably still\n\t\t\t// pending when the sync returned.\n\t\t\tawait authProvider.deleteAllSessions(this.authProviderDescriptor);\n\t\t}\n\n\t\tthis.resetRequestExceptionCount('all');\n\t\tthis._session = null;\n\n\t\tif (connected) {\n\t\t\t// Don't store the disconnected flag if silently disconnecting or disconnecting this only for\n\t\t\t// this current VS Code session (will be re-connected on next restart)\n\t\t\tif (!options?.currentSessionOnly && !options?.silent) {\n\t\t\t\tvoid this.ctx.storage.storeWorkspace(this.connectedKey, false).catch();\n\t\t\t}\n\n\t\t\tthis._onDidChange.fire();\n\t\t\tif (!options?.currentSessionOnly) {\n\t\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'disconnected' });\n\t\t\t}\n\t\t}\n\n\t\tawait this.providerOnDisconnect?.();\n\t}\n\n\tprotected providerOnDisconnect?(): void | Promise<void>;\n\n\t@debug()\n\tasync reauthenticate(): Promise<void> {\n\t\t// `forceNewSession` below deletes the stored secrets to reconnect. Ahead of the guard — see\n\t\t// `onStoredTokensReplaced`.\n\t\tthis.onStoredTokensReplaced();\n\n\t\tconst cleared = await this.runSessionTransition(() => {\n\t\t\tif (this._session === undefined) return Promise.resolve(false);\n\n\t\t\tthis._session = undefined;\n\t\t\treturn Promise.resolve(true);\n\t\t});\n\t\tif (!cleared) return;\n\n\t\tvoid (await this.ensureSession({ createIfNeeded: true, forceNewSession: true }));\n\t}\n\n\trefresh(): void {\n\t\tvoid this.ensureSession({ createIfNeeded: false });\n\t}\n\n\t/**\n\t * Drops the cached session when its token is no longer `accessToken` (a session replaced outside the\n\t * integration) and resolves it again, in one transition, so nothing in flight publishes the replaced one.\n\t */\n\tprotected resyncSessionIfTokenChanged(accessToken: string | undefined): Promise<void> {\n\t\treturn this.runSessionTransition(async () => {\n\t\t\tif (this._session != null && this._session.accessToken !== accessToken) {\n\t\t\t\tthis._session = undefined;\n\t\t\t}\n\t\t\tawait this.resolveSessionLocked({ createIfNeeded: false });\n\t\t});\n\t}\n\n\tprivate _syncRequestsPerFailedUsecase = new Set<SyncReqUsecase>();\n\thasSessionSyncRequests(): boolean {\n\t\treturn this._syncRequestsPerFailedUsecase.size > 0;\n\t}\n\trequestSessionSyncForUsecase(syncReqUsecase: SyncReqUsecase): void {\n\t\tthis._syncRequestsPerFailedUsecase.add(syncReqUsecase);\n\t}\n\n\t/**\n\t * The per-connection counterpart of the primary session's expire-and-resync recovery: a token the\n\t * provider refused is refreshed once through the GK cloud, which exchanges the refresh token server-side\n\t * (the client never holds one). Armed by {@link handleProviderException} and consumed by\n\t * {@link resolveReadSession}; a rejection it declines belongs to {@link trackRequestException} instead.\n\t */\n\tprivate readonly _rejectedTokens = new RejectedTokenTracker();\n\n\t/**\n\t * Called by every path that replaces or removes the stored tokens — a disconnect, a forced re-sync, a\n\t * reauthentication, or the connection set changing. A rejection names a specific credential, so once that\n\t * credential is gone the rejection describes nothing and must not force a refresh (or, for a deleted\n\t * connection, outlive it).\n\t *\n\t * Deliberately NOT conditioned on `_session`. A per-connection read resolves through the auth provider and\n\t * never populates the cached primary session, so the connections this recovery exists for are exactly the\n\t * ones with no `_session` to inspect — the same blind spot the recovery itself was added to fix. Callers\n\t * that guard on `_session` therefore invoke this ahead of that guard.\n\t */\n\tprotected onStoredTokensReplaced(): void {\n\t\tthis._rejectedTokens.clear();\n\t\tthis.invalidateDiscoveryCaches();\n\t}\n\n\t/**\n\t * Drops what this integration discovered through its session and kept for later reads: a tracker's sites,\n\t * projects or teams. Called by every path that replaces the stored tokens (see {@link onStoredTokensReplaced}),\n\t * which includes the forced re-sync a consumer's refresh runs, and by a read asked to `forceSync`. A refresh is\n\t * the consumer saying the account may have changed, and a project created or deleted since the first read would\n\t * otherwise stay invisible, or keep being searched, for the rest of the session (#5907).\n\t */\n\tinvalidateDiscoveryCaches(): void {}\n\n\tprivate static readonly requestExceptionLimit = 5;\n\tprivate requestExceptionCount = 0;\n\n\tresetRequestExceptionCount(syncReqUsecase: SyncReqUsecase | 'all'): void {\n\t\tthis.requestExceptionCount = 0;\n\t\tif (syncReqUsecase === 'all') {\n\t\t\tthis._syncRequestsPerFailedUsecase.clear();\n\t\t\t// 'all' is the whole-integration reset: a disconnect, or a re-sync that produced a new access token.\n\t\t\tthis.onStoredTokensReplaced();\n\t\t} else {\n\t\t\tthis._syncRequestsPerFailedUsecase.delete(syncReqUsecase);\n\t\t}\n\t}\n\n\t/**\n\t * Resets request exceptions without resetting the amount of syncs\n\t */\n\tsmoothifyRequestExceptionCount(): void {\n\t\t// On resync we reset exception count only to avoid infinitive syncs on failure\n\t\tthis.requestExceptionCount = 0;\n\t}\n\n\tasync reset(): Promise<void> {\n\t\tawait this.runSessionTransition(async () => {\n\t\t\tawait this.disconnectLocked({ silent: true }, true);\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t});\n\t}\n\n\t/**\n\t * Drops the in-memory session so the next access re-resolves it from storage. Used when the primary\n\t * connection changed underneath a warm integration (e.g. after `setPrimaryConnection`/\n\t * `deleteConnection`). Unlike {@link reset}/{@link disconnect}, it deletes nothing from storage.\n\t */\n\tswitchConnection(): Promise<void> {\n\t\t// A connection was deleted, or a different one became primary. Ahead of the guard — see\n\t\t// `onStoredTokensReplaced`.\n\t\tthis.onStoredTokensReplaced();\n\n\t\t// Queued, so a resolution already running finishes with the former primary and this one then re-resolves.\n\t\treturn this.runSessionTransition(async () => {\n\t\t\tif (this._session === undefined) return;\n\n\t\t\tconst wasConnected = this._session != null;\n\t\t\tthis._session = undefined;\n\t\t\tthis._onDidChange.fire();\n\n\t\t\tconst session = await this.resolveSessionLocked({ createIfNeeded: false });\n\t\t\tif (session != null || !wasConnected) return;\n\n\t\t\tthis._onDidChange.fire();\n\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'disconnected' });\n\t\t\tawait this.providerOnDisconnect?.();\n\t\t});\n\t}\n\n\tprivate skippedNonCloudReported = false;\n\t/**\n\t * Resolves the re-sync's failure, when its token fetch threw (cancellations included), so a caller can report it.\n\t * Never rejects: the cloud sync fans this out over every integration and must not fail on one.\n\t */\n\t@debug()\n\tsyncCloudConnection(state: 'connected' | 'disconnected', forceSync: boolean): Promise<Error | undefined> {\n\t\treturn this.runSessionTransition(() => this.syncCloudConnectionLocked(state, forceSync));\n\t}\n\n\tprivate async syncCloudConnectionLocked(\n\t\tstate: 'connected' | 'disconnected',\n\t\tforceSync: boolean,\n\t): Promise<Error | undefined> {\n\t\tconst scope = getScopedLogger();\n\t\t// Initially the condition on `this._session.cloud` has been added here: https://github.com/gitkraken/vscode-gitlens/commit/e95e70c430bd162924cc3bd5c1e8ab90e6293449#diff-4213141a45cccaab7aa2e40028b155a87eb913b07388485831403e60ce5555e4R237\n\t\t// I'm not sure about reasons, but it seems we want to replace it with the cloud session if it's connected.\n\t\t// Gradually we'll stop having non-cloud sessions.\n\t\t// However this is needed to be tested with PATs, e.g. with a GitLab PAT.\n\t\tif (this._session?.cloud === false && state !== 'connected') {\n\t\t\tif (this.id !== GitCloudHostIntegrationId.GitHub && !this.skippedNonCloudReported) {\n\t\t\t\tthis.ctx.hooks?.session?.onRefreshSkipped?.({\n\t\t\t\t\tid: this.id,\n\t\t\t\t\treason: 'skip-non-cloud',\n\t\t\t\t\tcloud: false,\n\t\t\t\t});\n\t\t\t\tthis.skippedNonCloudReported = true;\n\t\t\t}\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet failure: Error | undefined;\n\t\tswitch (state) {\n\t\t\tcase 'connected': {\n\t\t\t\tconst oldSession = this._session;\n\t\t\t\tlet resyncing = false;\n\t\t\t\tif (forceSync) {\n\t\t\t\t\t// Get a new session from the cloud. The stored one is refetched rather than deleted first (see\n\t\t\t\t\t// `GetSessionOptions.refetch`), so a fetch that fails transiently keeps the token it had.\n\t\t\t\t\t// Not left to the token-changed check below, which needs an `oldSession` — see\n\t\t\t\t\t// `onStoredTokensReplaced`.\n\t\t\t\t\tthis.onStoredTokensReplaced();\n\t\t\t\t\t// The cached session is kept while the replacement is fetched (the refetch does not read it): a\n\t\t\t\t\t// transient failure then leaves it as it was, expiry included, and a read meanwhile does not have to\n\t\t\t\t\t// resolve a session of its own, which during a GK API outage fails the same way and reports the\n\t\t\t\t\t// integration as not connected (gitkraken/kepler#3546). Clear our \"stay disconnected\" flag.\n\t\t\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t\t\t\tresyncing = true;\n\t\t\t\t} else {\n\t\t\t\t\t// Only sync if we're not connected and not disabled and don't have pending errors\n\t\t\t\t\tif (\n\t\t\t\t\t\tthis._session != null ||\n\t\t\t\t\t\tthis.requestExceptionCount > 0 ||\n\t\t\t\t\t\tthis.ctx.storage.getWorkspace(this.connectedKey) === false\n\t\t\t\t\t) {\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\n\t\t\t\t\tforceSync = true;\n\t\t\t\t}\n\n\t\t\t\t// sync option, rather than createIfNeeded, makes sure we don't call connectCloudIntegrations and open a gkdev window\n\t\t\t\t// if there was no session or some problem fetching/refreshing the existing session from the cloud api\n\t\t\t\tlet newSession: ProviderAuthenticationSession | undefined;\n\t\t\t\tlet refetchFailed = false;\n\t\t\t\ttry {\n\t\t\t\t\tnewSession = await this.resolveSessionLocked({ sync: forceSync, refetch: resyncing });\n\t\t\t\t} catch (ex) {\n\t\t\t\t\trefetchFailed = true;\n\t\t\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\t}\n\t\t\t\t\t// Not evidence the connection is gone (#5569). A cancelled refetch is still reported: its caller must\n\t\t\t\t\t// not read with the session it could not replace.\n\t\t\t\t\tfailure = toError(ex);\n\t\t\t\t}\n\n\t\t\t\tif (oldSession && newSession && newSession.accessToken !== oldSession.accessToken) {\n\t\t\t\t\tthis.resetRequestExceptionCount('all');\n\t\t\t\t}\n\n\t\t\t\t// The forced re-sync above kept the stored token and its descriptor until a replacement arrived.\n\t\t\t\t// Drop both only when the replacement fetch came back definitively empty, so a connection whose\n\t\t\t\t// token is really gone is cleanly disconnected (#5497) while a healthy one survives a blip (#5569).\n\t\t\t\tif (resyncing && newSession == null && !refetchFailed) {\n\t\t\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\t\t\tawait authProvider.deleteSession(this.authProviderDescriptor, { preserveConfigured: false });\n\t\t\t\t}\n\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'disconnected':\n\t\t\t\tawait this.disconnectLocked({ silent: true }, true);\n\t\t\t\tbreak;\n\t\t}\n\t\tthis._resyncs++;\n\t\tthis._lastResyncFailure = failure;\n\t\treturn failure;\n\t}\n\n\t/**\n\t * Proves the credential with an uncached check, rejecting with the provider's `AuthenticationError` when it is\n\t * refused, and with anything else when the check proves nothing. A `403` proves nothing either: the credential\n\t * authenticated and was only denied the check's request.\n\t *\n\t * Implemented by the providers whose reads can reach their scopes without an uncached request to the connection\n\t * first: discovery served from a per-token cache (Azure DevOps, Bitbucket, Jira Cloud), or an SDK fan-out across\n\t * the requested repositories (Bitbucket Data Center). There, a dead token comes back as scoped refusals with no\n\t * failure of the connection: the same shape as one scope refusing a sound credential.\n\t * {@link confirmScopedAuthFailures} uses this to tell the two apart.\n\t */\n\tprotected validateCredential?(session: ProviderAuthenticationSession): Promise<void>;\n\n\t/**\n\t * Credentials that passed {@link validateCredential} within the last minute. A probe already in flight is\n\t * shared rather than repeated, and a refused one is not kept. Cleared whenever a credential is refused (see\n\t * {@link handleProviderException}), so a pass is never vouched for past a refusal this instance has seen.\n\t */\n\tprivate readonly _validatedCredentials = new PromiseCache<string, void>({ createTTL: 60 * 1000, capacity: 10 });\n\n\t/**\n\t * Whether a scope's refusal is the credential's own, by the provider's account of it: e.g. a token missing the\n\t * OAuth scopes the read needs. The probe cannot see that, because it proves the token authenticates, not that\n\t * it is authorized. So {@link confirmScopedAuthFailures} publishes one for the connection, unscoped, which asks\n\t * for the reconnect (consenting to the scopes again) that fixes it, and keeps the results of the scopes that\n\t * answered.\n\t */\n\tprotected isCredentialRefusal?(refusal: ProviderRefusal): boolean;\n\n\t/**\n\t * Names why a confirmed credential was refused by one scope, from what the refusal said (see\n\t * `ProviderWarning.cause`). Called only by {@link confirmScopedAuthFailures}, once the credential passed: the\n\t * refusals this names are indistinguishable from a dead credential until then.\n\t */\n\tprotected describeRefusal?(\n\t\tsession: ProviderAuthenticationSession,\n\t\trefusal: ProviderRefusal,\n\t\tscope: ProviderWarningScope,\n\t): ProviderWarningCause | undefined;\n\n\t/**\n\t * Settles what a read's only-scoped authentication failures mean, when the provider can tell (see\n\t * {@link validateCredential}).\n\t *\n\t * A refused credential is thrown: the caller's `catch` then fails the read as a whole, exactly as it does when\n\t * discovery is not cached and the first request is the one refused. Without this, a token revoked after\n\t * discovery would publish scoped `auth` warnings, which a consumer must not answer with a reconnect (see\n\t * `ProviderWarning.scope`), next to results served from the cache, for as long as the token stays stored.\n\t *\n\t * A confirmed credential makes each refusal the scope's own, so its cause is named through\n\t * {@link describeRefusal}. A probe that fails for another reason proves nothing either way and leaves the read\n\t * as it was.\n\t *\n\t * A refusal the provider pins on the credential itself (see {@link isCredentialRefusal}) is published for the\n\t * connection instead, without a probe.\n\t *\n\t * A token that passed within the last minute is not probed again, so a scope that keeps refusing it costs one\n\t * probe a minute, and a revocation right after a probe can take up to a minute to surface: until then its\n\t * refusals stay scoped, and may be named. A probe that proved nothing is not remembered, so the next read probes\n\t * again, and a credential no probe can confirm (e.g. a Bitbucket Data Center project access token) is probed on\n\t * every read that has scoped refusals.\n\t */\n\tprotected async confirmScopedAuthFailures(\n\t\tsession: ProviderAuthenticationSession,\n\t\tmetadata: CollectionMetadata | undefined,\n\t): Promise<void> {\n\t\tif (!hasOnlyScopedAuthFailures(metadata)) return;\n\n\t\t// Published for the connection, as a refusal outside any scope would be, while the scopes that answered keep\n\t\t// their results. That already asks for a reconnect, so there is nothing left for a probe to settle.\n\t\tif (this.markCredentialRefusals(metadata)) return;\n\t\tif ((await this.confirmCredential(session)) !== 'confirmed') return;\n\n\t\tthis.nameRefusalCauses(session, metadata);\n\t}\n\n\t/**\n\t * Settles what a batch read's authentication refusals mean before the call is judged, as\n\t * {@link confirmScopedAuthFailures} settles an account-wide read's: each refused slot comes back carrying the scope\n\t * failure it was recorded as, against the scope its target names (see {@link batchTargetScope}), and a read\n\t * publishes that instead of the bare reason.\n\t *\n\t * When any target answered, that proved the credential, so each refusal is its own target's. When every target\n\t * failed, the call fails as a whole, as it always has, unless every failure is a refusal of a credential that checks\n\t * out. Every target refusing is also what a dead credential looks like, so the credential is checked once first,\n\t * with {@link validateCredential}. A check that proves nothing leaves the refusals scoped and unnamed, as it leaves\n\t * an account-wide read's. A provider with no check cannot tell, and a refusal it pins on the credential (see\n\t * {@link isCredentialRefusal}) needs none, so either still fails the call.\n\t *\n\t * `keepOtherFailures` is for the etag cheap check, whose failed targets fall through to a full read one by one:\n\t * there, only a refused credential fails the call, always on one of the refusals, and every other failure stays\n\t * its own slot's. A batch with refusals and other failures but no answer then has its credential checked, as when\n\t * every target refused, and a provider with no check leaves its refusals scoped and unnamed.\n\t */\n\tprotected async settleBatchRefusals<T>(\n\t\tsession: ProviderAuthenticationSession,\n\t\ttargets: readonly BatchTarget[],\n\t\tslots: PromiseSettledResult<T>[],\n\t\toptions?: { keepOtherFailures?: boolean },\n\t): Promise<BatchSlot<T>[]> {\n\t\tconst keepOtherFailures = options?.keepOtherFailures ?? false;\n\t\tconst failures = slots.map((slot, i) =>\n\t\t\tslot.status === 'rejected' && slot.reason instanceof AuthenticationError\n\t\t\t\t? toCollectionScopeFailure(batchTargetScope(this.id, targets[i]), slot.reason)\n\t\t\t\t: undefined,\n\t\t);\n\t\tconst refused = failures.filter(f => f != null);\n\t\tif (!refused.length) {\n\t\t\tif (!keepOtherFailures) {\n\t\t\t\tthrowIfAllSettledFailed(slots);\n\t\t\t}\n\t\t\treturn slots;\n\t\t}\n\n\t\tconst metadata: CollectionMetadata = { completeness: 'partial', failures: refused };\n\t\tconst credentialRefused = this.markCredentialRefusals(metadata);\n\t\tif (slots.some(slot => slot.status === 'fulfilled')) {\n\t\t\tthis.nameRefusalCauses(session, metadata);\n\t\t} else if (credentialRefused) {\n\t\t\tthrowIfAllSettledFailed(keepOtherFailures ? slots.filter((_, i) => failures[i] != null) : slots);\n\t\t} else if (refused.length < slots.length && !keepOtherFailures) {\n\t\t\t// Fails on a target that failed for another reason: failing on a refusal would run the credential's own\n\t\t\t// recovery (a session expiry or a disconnect strike) for what may be only its target's refusal.\n\t\t\tthrowIfAllSettledFailed(slots.filter((_, i) => failures[i] == null));\n\t\t} else {\n\t\t\tconst confirmation = await this.confirmCredential(session);\n\t\t\tif (confirmation == null) {\n\t\t\t\tif (refused.length === slots.length) {\n\t\t\t\t\tthrowIfAllSettledFailed(slots);\n\t\t\t\t}\n\t\t\t} else if (confirmation === 'confirmed') {\n\t\t\t\tthis.nameRefusalCauses(session, metadata);\n\t\t\t}\n\t\t}\n\n\t\treturn slots.map((slot, i) => {\n\t\t\tconst failure = failures[i];\n\t\t\treturn slot.status === 'rejected' && failure != null ? { ...slot, failure: failure } : slot;\n\t\t});\n\t}\n\n\t/**\n\t * Shows one notice, and spends at most one strike, for a batch read whose targets failed with a server error or a\n\t * timeout. Our own clients leave both to the batch (see `reportRequestFailure`) instead of spending them per\n\t * target. `strike` is true only when every target failed and the call's own failure spent none: a call that\n\t * answered some targets showed the host is up, and budgets strikes the same way as the rest of its failures.\n\t */\n\tprotected reportDeferredRequestFailures(\n\t\tslots: readonly PromiseSettledResult<unknown>[] | undefined,\n\t\tstrike: boolean,\n\t): void {\n\t\tlet notify: (() => void) | undefined;\n\t\tfor (const slot of slots ?? []) {\n\t\t\tif (slot.status !== 'rejected') continue;\n\n\t\t\tnotify = getDeferredRequestFailure(slot.reason);\n\t\t\tif (notify != null) break;\n\t\t}\n\n\t\tif (notify == null) return;\n\n\t\tif (strike) {\n\t\t\tthis.trackRequestException();\n\t\t}\n\t\tnotify();\n\t}\n\n\t/**\n\t * The failure handling of an etag cheap check that failed as a whole. Only a refused credential runs\n\t * {@link handleProviderException}, for its session expiry, re-authentication and strike. Anything else is only\n\t * logged, spending no strike and showing no notice: every target the check held then falls through to a full\n\t * read, which spends and notifies if the host really is failing, or is dropped, as rate-limited or without a\n\t * connection, with the check's warning. A rate limit costs nothing in `handleProviderException` either.\n\t */\n\tprotected handleEtagCheckException(\n\t\tsyncReqUsecase: SyncReqUsecase,\n\t\tex: Error,\n\t\toptions: { scope: ScopedLogger | undefined; connectionId: string | undefined },\n\t): void {\n\t\tif (ex instanceof AuthenticationError) {\n\t\t\tthis.handleProviderException(syncReqUsecase, ex, options);\n\t\t\treturn;\n\t\t}\n\t\tif (isCancellationError(ex)) return;\n\n\t\tif (options.scope != null) {\n\t\t\toptions.scope.error(ex);\n\t\t} else {\n\t\t\tLogger.error(ex);\n\t\t}\n\t}\n\n\t/** Marks the scoped refusals the provider pins on the credential (see {@link isCredentialRefusal}). */\n\tprivate markCredentialRefusals(metadata: CollectionMetadata | undefined): boolean {\n\t\tconst isCredentialRefusal = this.isCredentialRefusal?.bind(this);\n\t\treturn isCredentialRefusal != null && markCredentialRefusals(metadata, isCredentialRefusal);\n\t}\n\n\t/**\n\t * Checks the credential with {@link validateCredential}, rethrowing the provider's refusal when it is refused.\n\t * `undefined` when the provider has no check, and `'unproven'` when the check proved nothing either way.\n\t */\n\tprivate async confirmCredential(\n\t\tsession: ProviderAuthenticationSession,\n\t): Promise<'confirmed' | 'unproven' | undefined> {\n\t\tconst validateCredential = this.validateCredential?.bind(this);\n\t\tif (validateCredential == null) return undefined;\n\n\t\t// Keyed by the address as well as the token: a self-managed instance serves every installation on its host,\n\t\t// and one installation accepting a token says nothing about another.\n\t\tconst key = [session.domain, session.baseUrl ?? '', session.accessToken].join('\\n');\n\t\ttry {\n\t\t\tawait this._validatedCredentials.getOrCreate(key, () => validateCredential(session));\n\t\t} catch (ex) {\n\t\t\t// A 403 answered a credential that authenticated and was only denied the check's own request, so it proves\n\t\t\t// nothing about the scopes' refusals either.\n\t\t\tif (ex instanceof AuthenticationError && ex.reason !== AuthenticationErrorReason.Forbidden) throw ex;\n\n\t\t\treturn 'unproven';\n\t\t}\n\t\treturn 'confirmed';\n\t}\n\n\t/** Names each scoped refusal's cause (see {@link describeRefusal}); only once the credential is proven. */\n\tprivate nameRefusalCauses(session: ProviderAuthenticationSession, metadata: CollectionMetadata | undefined): void {\n\t\tconst describeRefusal = this.describeRefusal?.bind(this);\n\t\tif (describeRefusal == null) return;\n\n\t\tattributeScopedAuthFailures(metadata, (refusal, scope) => describeRefusal(session, refusal, scope));\n\t}\n\n\t/** Returns whether it spent a strike toward disconnecting (see {@link trackRequestException}). */\n\tprotected handleProviderException(\n\t\tsyncReqUsecase: SyncReqUsecase,\n\t\tex: Error,\n\t\toptions?: { scope?: ScopedLogger | undefined; silent?: boolean; connectionId?: string },\n\t): boolean {\n\t\tif (isCancellationError(ex)) return false;\n\n\t\t// A refused credential may be one a probe passed moments ago: stop vouching for it.\n\t\tif (ex instanceof AuthenticationError) {\n\t\t\tthis._validatedCredentials.clear();\n\t\t}\n\n\t\tif (options?.scope != null) {\n\t\t\toptions.scope.error(ex);\n\t\t} else {\n\t\t\tLogger.error(ex);\n\t\t}\n\n\t\t// A per-connection (multi-account) read resolved its session through `resolveReadSession`'s\n\t\t// `connectionId` branch, which deliberately never touches the cached primary `_session`. So the\n\t\t// primary-session recovery below cannot apply to it: expiring `_session` would mark a session this\n\t\t// read never used, while the rejected connection kept its stored token and re-sent it on every\n\t\t// later read — a token the provider has already refused (expired scopes, a revoked grant, an\n\t\t// uninstalled app) is not self-healing, so the read failed identically until the user reconnected\n\t\t// by hand. Record the rejection against the connection instead; `resolveReadSession` consumes it\n\t\t// and forces the cloud `/refresh` on the next read of that connection. When there is nothing to\n\t\t// recover, fall through to the shared failure budget, which disconnects after\n\t\t// `requestExceptionLimit` and surfaces the reconnect prompt.\n\t\tif (ex instanceof AuthenticationError && options?.connectionId) {\n\t\t\tif (this._rejectedTokens.recordRejection(options.connectionId)) return false;\n\n\t\t\tthis.trackRequestException(options);\n\t\t\treturn true;\n\t\t}\n\n\t\tif (ex instanceof AuthenticationError && this._session?.cloud && !this.hasSessionSyncRequests()) {\n\t\t\tthis.requestSessionSyncForUsecase(syncReqUsecase);\n\t\t\t// Expired now, so a read in progress stops using it, and again once any transition in flight settles, in\n\t\t\t// case it publishes the same token anew (compared by token: a re-read is a new object).\n\t\t\tconst refused = this._session.accessToken;\n\t\t\tthis.expireSessionIfToken(refused);\n\t\t\tvoid this.runSessionTransition(() => {\n\t\t\t\tthis.expireSessionIfToken(refused);\n\t\t\t\treturn Promise.resolve();\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\n\t\tif (!(ex instanceof AuthenticationError) && !(ex instanceof RequestClientError)) return false;\n\n\t\tthis.trackRequestException(options);\n\t\treturn true;\n\t}\n\n\tprivate expireSessionIfToken(accessToken: string): void {\n\t\tif (this._session?.accessToken !== accessToken || this.connectionExpired === true) return;\n\n\t\tthis._session = { ...this._session, expiresAt: new Date(Date.now() - 1) };\n\t}\n\n\t/** Counts completed cloud re-syncs, so a refresh queued behind one reuses its outcome rather than running another. */\n\tprivate _resyncs = 0;\n\tprivate _lastResyncFailure: Error | undefined;\n\tprivate missingExpirityReported = false;\n\t/**\n\t * Resolves the refresh's failure while the session it could not replace is still unusable, so a read can report\n\t * that instead of reading with an expired token or as a missing connection.\n\t */\n\t@gate()\n\tprotected async refreshSessionIfExpired(): Promise<Error | undefined> {\n\t\tif (this._session?.expiresAt != null && this._session.expiresAt < new Date()) {\n\t\t\t// The current session is expired, so get the latest from the cloud and refresh if needed. Queued, so it\n\t\t\t// sees what any transition in flight settles: a re-sync that already replaced the session, or ran meanwhile\n\t\t\t// and could not, is not repeated, and a disconnect stands.\n\t\t\tconst resyncs = this._resyncs;\n\t\t\treturn this.runSessionTransition(async () => {\n\t\t\t\tlet failure: Error | undefined;\n\t\t\t\tif (this._resyncs !== resyncs) {\n\t\t\t\t\tfailure = this._lastResyncFailure;\n\t\t\t\t} else if (this._session != null && this.connectionExpired === true) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfailure = await this.syncCloudConnectionLocked('connected', true);\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tfailure = toError(ex);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Only a session still there and still expired is this read's failure, including one a provider\n\t\t\t\t// refusal expired again after the re-sync replaced it.\n\t\t\t\tif (this._session == null || this.connectionExpired !== true) return undefined;\n\n\t\t\t\treturn failure ?? new Error(`The ${this.name} session could not be refreshed`);\n\t\t\t});\n\t\t} else if (\n\t\t\tthis._session?.expiresAt == null &&\n\t\t\tthis.id !== GitCloudHostIntegrationId.GitHub &&\n\t\t\t!this.missingExpirityReported\n\t\t) {\n\t\t\tthis.ctx.hooks?.session?.onRefreshSkipped?.({\n\t\t\t\tid: this.id,\n\t\t\t\treason: 'missing-expiry',\n\t\t\t\tcloud: this._session?.cloud,\n\t\t\t});\n\t\t\tthis.missingExpirityReported = true;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t@trace()\n\ttrackRequestException(options?: { silent?: boolean }): void {\n\t\tthis.requestExceptionCount++;\n\n\t\tif (this.requestExceptionCount >= IntegrationBase.requestExceptionLimit && this._session !== null) {\n\t\t\tif (!options?.silent) {\n\t\t\t\tthis.ctx.hooks?.ui?.onDisconnectedAfterTooManyFailures?.(this.name);\n\t\t\t}\n\t\t\tvoid this.disconnect({ currentSessionOnly: true });\n\t\t}\n\t}\n\n\t@gate()\n\t@trace({ exit: true })\n\tasync isConnected(): Promise<boolean> {\n\t\t// Not `getSession()`: an expired session is still a connection, and refreshing it here would put a GK API round\n\t\t// trip on every connection check and report a throttled refresh as no connection (gitkraken/kepler#3546).\n\t\tconst session =\n\t\t\tthis._session === undefined\n\t\t\t\t? await this.ensureSession({ createIfNeeded: false, source: 'integrations' })\n\t\t\t\t: this._session;\n\t\treturn session != null && this.isSessionForIntegrationHost(session);\n\t}\n\n\t@gate()\n\tprivate async ensureSession(\n\t\toptions:\n\t\t\t| {\n\t\t\t\t\tcreateIfNeeded?: boolean;\n\t\t\t\t\tforceNewSession?: boolean;\n\t\t\t\t\tsync?: never;\n\t\t\t\t\trefetch?: never;\n\t\t\t\t\tsource?: Sources;\n\t\t\t }\n\t\t\t| {\n\t\t\t\t\tcreateIfNeeded?: never;\n\t\t\t\t\tforceNewSession?: never;\n\t\t\t\t\tsync: boolean;\n\t\t\t\t\trefetch?: boolean;\n\t\t\t\t\tsource?: Sources;\n\t\t\t },\n\t): Promise<ProviderAuthenticationSession | undefined> {\n\t\tif (options.createIfNeeded || options.forceNewSession) {\n\t\t\t// The sign-in runs outside the queue: it waits on the user, and its cloud sync re-enters this integration\n\t\t\t// (`syncCloudConnection`, `isConnected`), which would wait for it. What it stored is then resolved in a\n\t\t\t// transition, so a disconnect during the sign-in still stands.\n\t\t\tif (!(await this.signIn(options))) return this._session ?? undefined;\n\n\t\t\treturn this.runSessionTransition(() => this.resolveSessionLocked({ createIfNeeded: false }));\n\t\t}\n\n\t\treturn this.runSessionTransition(() => this.resolveSessionLocked(options));\n\t}\n\n\t/**\n\t * Asks the auth provider for a session interactively, storing what it gets; whether one came back. A declined\n\t * consent or any other failure is not one, and never throws: it is resolved, like any read, from storage.\n\t */\n\tprivate async signIn(options: {\n\t\tcreateIfNeeded?: boolean;\n\t\tforceNewSession?: boolean;\n\t\tsource?: Sources;\n\t}): Promise<boolean> {\n\t\tif (this.ctx.config.isIntegrationsEnabled?.() === false) return false;\n\n\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\ttry {\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\treturn (await authProvider.getSession(this.authProviderDescriptor, options)) != null;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t/** Resolves and publishes the session. Only ever runs inside a transition (see {@link runSessionTransition}). */\n\tprivate async resolveSessionLocked(options: {\n\t\tcreateIfNeeded?: boolean;\n\t\tsync?: boolean;\n\t\trefetch?: boolean;\n\t\tsource?: Sources;\n\t}): Promise<ProviderAuthenticationSession | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst { createIfNeeded, source, sync, refetch } = options;\n\t\t// A forced re-sync keeps the session it is replacing; serving it would skip the refetch the caller forced.\n\t\tif (this._session != null && !refetch) {\n\t\t\tif (this.isSessionForIntegrationHost(this._session)) return this._session;\n\n\t\t\tthis._session = null;\n\t\t}\n\t\tif (this.ctx.config.isIntegrationsEnabled?.() === false) return undefined;\n\n\t\tif (createIfNeeded || sync) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t} else if (this.ctx.storage.getWorkspace(this.connectedKey) === false) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tlet session: ProviderAuthenticationSession | undefined | null;\n\t\ttry {\n\t\t\tconst authProvider = await this.authenticationService.get(this.authProvider.id);\n\t\t\tsession = await authProvider.getSession(\n\t\t\t\tthis.authProviderDescriptor,\n\t\t\t\tsync\n\t\t\t\t\t? { sync: sync, refetch: refetch, source: source }\n\t\t\t\t\t: { createIfNeeded: createIfNeeded, source: source },\n\t\t\t);\n\n\t\t\tif (session?.expiresAt != null && session.expiresAt < new Date()) {\n\t\t\t\tsession = null;\n\t\t\t}\n\t\t\tif (session != null && !this.isSessionForIntegrationHost(session)) {\n\t\t\t\tsession = undefined;\n\t\t\t}\n\t\t} catch (ex) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\n\t\t\t// Only interactive paths can prompt for consent, so a sync failure must reach the rethrow below.\n\t\t\tif (!sync && ex instanceof Error && ex.message.includes('User did not consent')) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\n\t\t\t// On a forced re-sync, propagate so syncCloudConnection can tell a failure from a definitive empty\n\t\t\t// result (#5569); other callers keep swallowing to null so reads never throw.\n\t\t\tif (sync) {\n\t\t\t\t// Throwing skips the reset below, and a lingering count blocks the next non-forced sync.\n\t\t\t\tthis.smoothifyRequestExceptionCount();\n\t\t\t\tthrow ex;\n\t\t\t}\n\n\t\t\tsession = null;\n\t\t}\n\n\t\tif (session === undefined && !createIfNeeded && !sync) {\n\t\t\tawait this.ctx.storage.deleteWorkspace(this.connectedKey);\n\t\t}\n\n\t\tthis._session = session ?? null;\n\t\tthis.smoothifyRequestExceptionCount();\n\n\t\tif (session != null) {\n\t\t\tawait this.ctx.storage.storeWorkspace(this.connectedKey, true);\n\n\t\t\tqueueMicrotask(() => {\n\t\t\t\tthis._onDidChange.fire();\n\t\t\t\tthis.didChangeConnection?.fire({ integration: this, key: this.key, reason: 'connected' });\n\t\t\t\t// Fired detached, so there is no caller left to catch anything: every implementor is async, and\n\t\t\t\t// a rejection would surface as a process-level unhandled rejection in the host. It is a\n\t\t\t\t// best-effort warm-up, so swallow the failure with a warning instead. Never awaited by the\n\t\t\t\t// transition: its reads can disconnect, which queues a transition of their own.\n\t\t\t\tvoid (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait this.providerOnConnect?.();\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tscope?.warn(\n\t\t\t\t\t\t\t`Failed to run providerOnConnect for ${this.key}: ${ex instanceof Error ? ex.message : String(ex)}`,\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}\n\n\t\treturn session ?? undefined;\n\t}\n\n\tgetIgnoreSSLErrors(): boolean | 'force' {\n\t\treturn this.authenticationService.ignoreSSLErrors(this);\n\t}\n\n\tasync searchMyIssues(\n\t\tresource?: ResourceDescriptor,\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined>;\n\tasync searchMyIssues(\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined>;\n\t@trace()\n\tasync searchMyIssues(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IssueShape[] | undefined> {\n\t\treturn (await this.searchMyIssuesResult(resources, cancellation, connectionId))?.value;\n\t}\n\n\t/**\n\t * Result-returning core of {@link searchMyIssues}. Recovers thrown errors into `{ error }` so callers\n\t * (e.g. the ProviderBackend account-wide issues read) can surface a per-provider warning instead of a\n\t * silent empty result. Returns the normalized {@link IssueShape} (there is no raw account-wide issue read).\n\t */\n\tasync searchMyIssuesResult(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t): Promise<IntegrationResult<IssueShape[] | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\t// `connectionId` targets a specific account (multi-account); omitted reads the primary.\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null || isReadSessionFailure(session)) return session && { error: session.error };\n\n\t\tconst start = performance.now();\n\t\ttry {\n\t\t\tconst issues = await this.searchProviderMyIssues(\n\t\t\t\tsession,\n\t\t\t\tresources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tthis.resetRequestExceptionCount('searchMyIssues');\n\t\t\treturn { value: issues, duration: performance.now() - start };\n\t\t} catch (ex) {\n\t\t\tthis.handleProviderException('searchMyIssues', ex, { scope: scope, connectionId: connectionId });\n\t\t\treturn { error: toError(ex), duration: performance.now() - start };\n\t\t}\n\t}\n\n\tprotected abstract searchProviderMyIssues(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t): Promise<IssueShape[] | undefined>;\n\n\t/**\n\t * Paging/truncation-aware variant of {@link searchProviderMyIssues}. The default wraps the normalized read\n\t * as a complete single page; providers with native cursors or fan-out metadata override it.\n\t */\n\tprotected async searchProviderMyIssuesWithTruncation(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresources?: ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\t_options?: SearchMyIssuesOptions,\n\t): Promise<AccountWideIssuesResult | undefined> {\n\t\t// The default read has no assignee scoping to broaden, so `_options` is inert here; a provider whose\n\t\t// account-wide read is user-scoped (GitHub/GitLab/Azure) overrides this and honors `includeAllAssignees`.\n\t\tconst values = await this.searchProviderMyIssues(session, resources, cancellation);\n\t\tif (values == null) return undefined;\n\t\treturn { values: values, truncated: false };\n\t}\n\n\t/**\n\t * Result-returning, truncation-aware account-wide issue read. Recovers thrown errors into `{ error }` and\n\t * carries the `truncated` flag so the ProviderBackend facade can report an incomplete read honestly.\n\t */\n\tasync searchMyIssuesWithTruncationResult(\n\t\tresources?: ResourceDescriptor | ResourceDescriptor[],\n\t\tcancellation?: AbortSignal,\n\t\tconnectionId?: string,\n\t\toptions?: SearchMyIssuesOptions,\n\t): Promise<IntegrationResult<AccountWideIssuesResult | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null || isReadSessionFailure(session)) return session && { error: session.error };\n\n\t\ttry {\n\t\t\tconst result = await this.searchProviderMyIssuesWithTruncation(\n\t\t\t\tsession,\n\t\t\t\tresources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined,\n\t\t\t\tcancellation,\n\t\t\t\toptions,\n\t\t\t);\n\t\t\tawait this.confirmScopedAuthFailures(session, result?.metadata);\n\t\t\tthis.resetRequestExceptionCount('searchMyIssues');\n\t\t\treturn { value: result };\n\t\t} catch (ex) {\n\t\t\tthis.handleProviderException('searchMyIssues', ex, { scope: scope, connectionId: connectionId });\n\t\t\treturn { error: toError(ex) };\n\t\t}\n\t}\n\n\t@trace()\n\tasync getLinkedIssueOrPullRequest(\n\t\tresource: T,\n\t\tlink: { id: string; key: string },\n\t\toptions?: { expiryOverride?: boolean | number; type?: IssueOrPullRequestType; throwOnError?: boolean },\n\t): Promise<IssueOrPullRequest | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tif ((await this.prepareSessionLookup()) != null) return undefined;\n\n\t\tconst { throwOnError, ...cacheOptions } = options ?? {};\n\n\t\tconst issueOrPR = this.ctx.cache.getIssueOrPullRequest(\n\t\t\tlink.key,\n\t\t\toptions?.type,\n\t\t\tresource,\n\t\t\tthis,\n\t\t\tcacheable => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.getProviderLinkedIssueOrPullRequest(\n\t\t\t\t\t\t\tthis._session!,\n\t\t\t\t\t\t\tresource,\n\t\t\t\t\t\t\tlink,\n\t\t\t\t\t\t\toptions?.type,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getIssueOrPullRequest');\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t// A failed lookup is not an answer — and a missed issue/PR never expires from the cache.\n\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\tthis.handleProviderException('getIssueOrPullRequest', ex, { scope: scope });\n\t\t\t\t\t\tif (throwOnError) throw ex;\n\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\tcacheOptions,\n\t\t);\n\t\treturn issueOrPR;\n\t}\n\n\tprotected abstract getProviderLinkedIssueOrPullRequest(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tlink: { id: string; key: string },\n\t\ttype: undefined | IssueOrPullRequestType,\n\t): Promise<IssueOrPullRequest | undefined>;\n\n\tasync getIssue(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { connectionId?: string; expiryOverride?: boolean | number },\n\t): Promise<Issue | undefined> {\n\t\treturn (await this.getIssueResult(resource, id, options))?.value;\n\t}\n\n\tgetIssueResult(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { connectionId?: string; expiryOverride?: boolean | number },\n\t): Promise<IntegrationResult<Issue | undefined>> {\n\t\treturn this.getIssueResultCore(resource, id, options, session => this.getProviderIssue(session, resource, id));\n\t}\n\n\t@trace()\n\tprotected async getIssueResultCore(\n\t\tresource: ResourceDescriptor,\n\t\tid: string,\n\t\toptions: { connectionId?: string; expiryOverride?: boolean | number } | undefined,\n\t\tgetProviderIssue: (session: ProviderAuthenticationSession) => Promise<Issue | undefined>,\n\t): Promise<IntegrationResult<Issue | undefined>> {\n\t\tconst scope = getScopedLogger();\n\t\tconst { connectionId: requestedConnectionId, expiryOverride } = options ?? {};\n\t\tconst connectionId = requestedConnectionId || undefined;\n\t\tconst session = await this.resolveReadSession(connectionId, scope);\n\t\tif (session == null || isReadSessionFailure(session)) return session && { error: session.error };\n\n\t\ttry {\n\t\t\tconst issue = await getCachedIssue({\n\t\t\t\tcache: this.ctx.cache,\n\t\t\t\tid: id,\n\t\t\t\tresource: resource,\n\t\t\t\tintegration: this,\n\t\t\t\tload: async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst issue = await getProviderIssue(session);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getIssue');\n\t\t\t\t\t\treturn issue;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\t\t\t\tthis.handleProviderException('getIssue', toError(ex), {\n\t\t\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tcacheOptions: {\n\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\texpiryOverride: expiryOverride,\n\t\t\t\t\tetag: `${this.id}:${this.maybeConnected ?? false}:${this.getSessionFingerprint(session)}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn { value: issue };\n\t\t} catch (ex) {\n\t\t\treturn { error: toError(ex) };\n\t\t}\n\t}\n\n\tprotected abstract getProviderIssue(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tid: string,\n\t): Promise<Issue | undefined>;\n\n\tasync getCurrentAccount(options?: {\n\t\tavatarSize?: number;\n\t\tconnectionId?: string;\n\t\texpiryOverride?: boolean | number;\n\t}): Promise<Account | undefined> {\n\t\tconst scope = getScopedLogger();\n\t\tconst { connectionId: requestedConnectionId, expiryOverride, ...opts } = options ?? {};\n\t\tconst connectionId = requestedConnectionId || undefined;\n\t\tconst session = await this.resolveReadSessionOrThrow(connectionId, scope);\n\t\tif (session == null) return undefined;\n\n\t\tconst sessionFingerprint = this.getSessionFingerprint(session);\n\n\t\tconst currentAccount = await this.ctx.cache.getCurrentAccount(\n\t\t\tthis,\n\n\t\t\t(cacheable: any) => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst account = await this.getProviderCurrentAccount?.(session, opts);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getCurrentAccount');\n\t\t\t\t\t\treturn account;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\tif (isCancellationError(ex) && !isProviderUnreachableError(ex)) {\n\t\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tthis.handleProviderException('getCurrentAccount', ex, {\n\t\t\t\t\t\t\tscope: scope,\n\t\t\t\t\t\t\tconnectionId: connectionId,\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Invalidate the cache on error, except for auth errors\n\t\t\t\t\t\tif (!(ex instanceof AuthenticationError)) {\n\t\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Re-throw to the caller\n\t\t\t\t\t\tthrow ex;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\t{\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\texpiryOverride: expiryOverride,\n\t\t\t\texpireOnError: false,\n\t\t\t\tetag: `${this.id}:${this.maybeConnected ?? false}:${sessionFingerprint}`,\n\t\t\t},\n\t\t);\n\t\treturn currentAccount;\n\t}\n\n\tprotected getProviderCurrentAccount?(\n\t\tsession: ProviderAuthenticationSession,\n\t\toptions?: { avatarSize?: number },\n\t): Promise<Account | undefined>;\n\n\t/** Whether this integration can answer {@link getCurrentAccount} at all — Jira Data Center cannot. */\n\tget supportsCurrentAccount(): boolean {\n\t\treturn this.getProviderCurrentAccount != null;\n\t}\n\n\t/**\n\t * Resolves the account for a specific session/token — including connections other than the current\n\t * primary (multi-account) — using this integration's provider API base URL and auth type. Returns\n\t * undefined when the provider doesn't support account lookup. Uncached (callers cache per connection).\n\t */\n\tgetProviderAccountForSession(session: ProviderAuthenticationSession): Promise<Account | undefined> {\n\t\treturn this.getProviderCurrentAccount?.(session) ?? Promise.resolve(undefined);\n\t}\n\n\t/**\n\t * Resolves the workspace a specific session's token was granted for, separate from the person, asking the\n\t * provider every time so a renamed workspace is picked up. Undefined when the provider has no such workspace.\n\t */\n\tgetProviderResourceForSession(_session: ProviderAuthenticationSession): Promise<ConnectionResource | undefined> {\n\t\treturn Promise.resolve(undefined);\n\t}\n\n\t@trace()\n\tasync getPullRequest(\n\t\tresource: T,\n\t\tid: string,\n\t\toptions?: { expiryOverride?: boolean | number; throwOnError?: boolean },\n\t): Promise<PullRequest | undefined> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst refreshFailure = await this.prepareSessionLookup();\n\t\tif (refreshFailure === 'unconnected') return undefined;\n\t\tif (refreshFailure != null) {\n\t\t\tif (options?.throwOnError) throw refreshFailure;\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst pr = await this.ctx.cache.getPullRequest(\n\t\t\tid,\n\t\t\tresource,\n\t\t\tthis,\n\t\t\tcacheable => ({\n\t\t\t\tvalue: (async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = await this.getProviderPullRequest?.(this._session!, resource, id);\n\t\t\t\t\t\tthis.resetRequestExceptionCount('getPullRequest');\n\t\t\t\t\t\treturn result;\n\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t// A failed lookup is not an answer — and the by-id bucket never expires a miss.\n\t\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t\t\tthis.handleProviderException('getPullRequest', ex, { scope: scope });\n\t\t\t\t\t\tif (options?.throwOnError) throw ex;\n\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\t\t\t\t})(),\n\t\t\t}),\n\t\t\t{ expiryOverride: options?.expiryOverride },\n\t\t);\n\t\treturn pr;\n\t}\n\n\tprotected getProviderPullRequest?(\n\t\tsession: ProviderAuthenticationSession,\n\t\tresource: T,\n\t\tid: string,\n\t): Promise<PullRequest | undefined>;\n}\n"],"mappings":"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"}
|
|
@@ -54,12 +54,13 @@ export declare class AzureDevOpsApi implements Disposable {
|
|
|
54
54
|
getIssue(provider: Provider, token: TokenWithInfo, project: AzureProjectDescriptor, id: string, options: {
|
|
55
55
|
baseUrl: string;
|
|
56
56
|
/**
|
|
57
|
-
* The current user as the organization's work items identify them, set as the row's `viewer
|
|
58
|
-
* the
|
|
57
|
+
* The current user as the organization's work items identify them, set as the row's `viewer`, or `null` when
|
|
58
|
+
* it couldn't be resolved, which marks the row `viewerUnresolved`. Asked only once the work item was read,
|
|
59
|
+
* alongside its state and fields; never rejects.
|
|
59
60
|
*/
|
|
60
61
|
viewer?: () => Promise<{
|
|
61
62
|
id: string;
|
|
62
|
-
} |
|
|
63
|
+
} | null>;
|
|
63
64
|
}): Promise<Issue | undefined>;
|
|
64
65
|
getAccountForCommit(provider: Provider, token: TokenWithInfo, owner: string, repo: string, rev: string, baseUrl: string, _options?: {
|
|
65
66
|
avatarSize?: number;
|
|
@@ -113,14 +114,15 @@ export declare class AzureDevOpsApi implements Disposable {
|
|
|
113
114
|
* the count of the same query; the caller refreshes its project discovery before a search so that means a
|
|
114
115
|
* project created during the pagination.
|
|
115
116
|
*
|
|
116
|
-
* `viewer` resolves the current user as the collection's work items identify them, set as each row's `viewer
|
|
117
|
-
* asked once for a page that read
|
|
117
|
+
* `viewer` resolves the current user as the collection's work items identify them, set as each row's `viewer`, or
|
|
118
|
+
* `null` when it couldn't be resolved, which marks the rows `viewerUnresolved`. It's asked once for a page that read
|
|
119
|
+
* any work item, and never rejects.
|
|
118
120
|
*/
|
|
119
121
|
searchWorkItemsPage(provider: Provider, token: TokenWithInfo, collection: string, options: WorkItemSearchOptions & {
|
|
120
122
|
resolveProject: (name: string) => AzureProjectDescriptor | undefined;
|
|
121
123
|
viewer?: () => Promise<{
|
|
122
124
|
id: string;
|
|
123
|
-
} |
|
|
125
|
+
} | null>;
|
|
124
126
|
cursor?: string;
|
|
125
127
|
pageSize?: number;
|
|
126
128
|
}, cancellation?: AbortSignal): Promise<ProviderIssueSearchPage>;
|
|
@@ -355,8 +355,9 @@ var AzureDevOpsApi = class {
|
|
|
355
355
|
* the count of the same query; the caller refreshes its project discovery before a search so that means a
|
|
356
356
|
* project created during the pagination.
|
|
357
357
|
*
|
|
358
|
-
* `viewer` resolves the current user as the collection's work items identify them, set as each row's `viewer
|
|
359
|
-
* asked once for a page that read
|
|
358
|
+
* `viewer` resolves the current user as the collection's work items identify them, set as each row's `viewer`, or
|
|
359
|
+
* `null` when it couldn't be resolved, which marks the rows `viewerUnresolved`. It's asked once for a page that read
|
|
360
|
+
* any work item, and never rejects.
|
|
360
361
|
*/
|
|
361
362
|
async searchWorkItemsPage(provider, token, collection, options, cancellation) {
|
|
362
363
|
const scope = getScopedLogger();
|