@gitkraken/core-gitlens 0.4.100 → 0.5.100
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 +12 -4
- package/dist/git/models/graph.d.ts +63 -33
- package/dist/git/models/graph.d.ts.map +1 -1
- package/dist/git/models/graph.js.map +1 -1
- package/dist/git/models/graphSession.d.ts +3 -84
- package/dist/git/models/graphSession.d.ts.map +1 -1
- package/dist/git/models/graphSession.js +1 -6
- package/dist/git/models/graphSession.js.map +1 -1
- package/dist/git/providers/graph.d.ts +1 -15
- package/dist/git/providers/graph.d.ts.map +1 -1
- package/dist/git/providers/pausedOperations.d.ts +2 -0
- package/dist/git/providers/pausedOperations.d.ts.map +1 -1
- package/dist/git/utils/reachability.utils.d.ts +1 -1
- package/dist/git/utils/reachability.utils.js +1 -1
- package/dist/git-cli/providers/graph.d.ts +1 -3
- package/dist/git-cli/providers/graph.d.ts.map +1 -1
- package/dist/git-cli/providers/graph.js +37 -214
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +1 -0
- package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
- package/dist/git-cli/providers/pausedOperations.js +21 -1
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/staging.d.ts.map +1 -1
- package/dist/git-cli/providers/staging.js +10 -1
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/plus/git-github/providers/github/graph.d.ts +1 -3
- package/dist/plus/git-github/providers/github/graph.d.ts.map +1 -1
- package/dist/plus/git-github/providers/github/graph.js +8 -10
- package/dist/plus/git-github/providers/github/graph.js.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts +38 -48
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts.map +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +22 -5
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -1
- package/dist/plus/integrations/authentication/rejectedTokenTracker.d.ts +39 -0
- package/dist/plus/integrations/authentication/rejectedTokenTracker.d.ts.map +1 -0
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js +53 -0
- package/dist/plus/integrations/authentication/rejectedTokenTracker.js.map +1 -0
- package/dist/plus/integrations/errors.d.ts.map +1 -1
- package/dist/plus/integrations/errors.js +6 -2
- package/dist/plus/integrations/errors.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +8 -5
- package/dist/plus/integrations/integrationService.d.ts.map +1 -1
- package/dist/plus/integrations/integrationService.js +20 -652
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +10 -17
- package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.js +57 -94
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/integration.d.ts +20 -0
- package/dist/plus/integrations/models/integration.d.ts.map +1 -1
- package/dist/plus/integrations/models/integration.js +61 -4
- package/dist/plus/integrations/models/integration.js.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.d.ts +4 -21
- package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -1
- package/dist/plus/integrations/models/issuesIntegration.js +10 -52
- package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/reads/broaden.d.ts +20 -0
- package/dist/plus/integrations/reads/broaden.d.ts.map +1 -0
- package/dist/plus/integrations/reads/broaden.js +326 -0
- package/dist/plus/integrations/reads/broaden.js.map +1 -0
- package/dist/plus/integrations/reads/context.d.ts +22 -0
- package/dist/plus/integrations/reads/context.d.ts.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.d.ts +1 -1
- package/dist/plus/integrations/reads/hierarchy.d.ts.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.js +109 -137
- package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.d.ts +25 -1
- package/dist/plus/integrations/reads/hierarchy.utils.d.ts.map +1 -1
- package/dist/plus/integrations/reads/hierarchy.utils.js +43 -0
- package/dist/plus/integrations/reads/hierarchy.utils.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts +1 -1
- package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +13 -25
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -2
- package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts +1 -1
- package/dist/plus/integrations/reads/pullRequests.d.ts.map +1 -1
- package/dist/plus/integrations/reads/pullRequests.js +1 -1
- package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.d.ts +25 -0
- package/dist/plus/integrations/reads/resolveRepository.d.ts.map +1 -0
- package/dist/plus/integrations/reads/resolveRepository.js +235 -0
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -0
- package/dist/plus/integrations/reads/sweeps.d.ts +11 -0
- package/dist/plus/integrations/reads/sweeps.d.ts.map +1 -0
- package/dist/plus/integrations/reads/sweeps.js +125 -0
- package/dist/plus/integrations/reads/sweeps.js.map +1 -0
- package/dist/utils/date.d.ts +1 -1
- package/dist/utils/date.d.ts.map +1 -1
- package/dist/utils/date.js +5 -3
- package/dist/utils/date.js.map +1 -1
- package/package.json +1 -1
- package/src/git/models/graph.ts +59 -41
- package/src/git/models/graphSession.ts +3 -106
- package/src/git/providers/graph.ts +1 -15
- package/src/git/providers/pausedOperations.ts +8 -1
- package/src/git/utils/reachability.utils.ts +1 -1
- package/src/git-cli/providers/graph.ts +37 -250
- package/src/git-cli/providers/pausedOperations.ts +29 -2
- package/src/git-cli/providers/staging.ts +8 -1
- package/src/plus/git-github/providers/github/graph.ts +8 -15
- package/src/plus/integrations/authentication/integrationAuthenticationProvider.ts +62 -40
- package/src/plus/integrations/authentication/rejectedTokenTracker.ts +56 -0
- package/src/plus/integrations/errors.ts +6 -2
- package/src/plus/integrations/integrationService.ts +27 -801
- package/src/plus/integrations/models/gitHostIntegration.ts +90 -99
- package/src/plus/integrations/models/integration.ts +68 -5
- package/src/plus/integrations/models/issuesIntegration.ts +10 -57
- package/src/plus/integrations/providers/azureDevOps.ts +1 -1
- package/src/plus/integrations/reads/broaden.ts +452 -0
- package/src/plus/integrations/reads/context.ts +24 -0
- package/src/plus/integrations/reads/hierarchy.ts +133 -153
- package/src/plus/integrations/reads/hierarchy.utils.ts +50 -1
- package/src/plus/integrations/reads/issueTracker.ts +15 -27
- package/src/plus/integrations/reads/issues.ts +4 -2
- package/src/plus/integrations/reads/pullRequests.ts +2 -9
- package/src/plus/integrations/reads/resolveRepository.ts +272 -0
- package/src/plus/integrations/reads/sweeps.ts +185 -0
- package/src/utils/date.ts +6 -3
|
@@ -32,6 +32,38 @@ export interface IntegrationAuthenticationSessionDescriptor {
|
|
|
32
32
|
[key: string]: unknown;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Options for {@link IntegrationAuthenticationProvider.getSession}. The two variants keep `sync` exclusive
|
|
37
|
+
* with the interactive flags: a sync resolves whatever the cloud already has, so it must never open a connect
|
|
38
|
+
* window.
|
|
39
|
+
*
|
|
40
|
+
* `refreshRejectedToken` marks the stored token as known-refused by the provider (an `AuthenticationError` on
|
|
41
|
+
* a previous read), which forces a cloud `/refresh` even when the token's expiry still claims it is valid —
|
|
42
|
+
* the case a purely time-based refresh cannot see. The refresh token itself never reaches the client: the GK
|
|
43
|
+
* cloud performs the exchange server-side.
|
|
44
|
+
*/
|
|
45
|
+
export type GetSessionOptions =
|
|
46
|
+
| {
|
|
47
|
+
createIfNeeded?: boolean;
|
|
48
|
+
forceNewSession?: boolean;
|
|
49
|
+
sync?: never;
|
|
50
|
+
refreshRejectedToken?: boolean;
|
|
51
|
+
source?: Sources;
|
|
52
|
+
}
|
|
53
|
+
| {
|
|
54
|
+
createIfNeeded?: never;
|
|
55
|
+
forceNewSession?: never;
|
|
56
|
+
sync: boolean;
|
|
57
|
+
refreshRejectedToken?: boolean;
|
|
58
|
+
source?: Sources;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* What {@link IntegrationAuthenticationProviderBase.getSession} passes down once it has decided the stored
|
|
63
|
+
* session can't be served as-is. `refreshIfExpired` is its own conclusion, not a caller's option.
|
|
64
|
+
*/
|
|
65
|
+
type GetNewSessionOptions = GetSessionOptions & { refreshIfExpired?: boolean };
|
|
66
|
+
|
|
35
67
|
export interface IntegrationAuthenticationProvider extends Disposable {
|
|
36
68
|
/**
|
|
37
69
|
* Clears the stored secret for one connection only (never the whole provider). Unlike
|
|
@@ -46,11 +78,13 @@ export interface IntegrationAuthenticationProvider extends Disposable {
|
|
|
46
78
|
options?: { preserveConfigured?: boolean },
|
|
47
79
|
): Promise<void>;
|
|
48
80
|
deleteAllSessions(descriptor?: IntegrationAuthenticationSessionDescriptor): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the session for `descriptor`, from storage when it is still usable and from the cloud
|
|
83
|
+
* otherwise. See {@link GetSessionOptions}.
|
|
84
|
+
*/
|
|
49
85
|
getSession(
|
|
50
86
|
descriptor: IntegrationAuthenticationSessionDescriptor,
|
|
51
|
-
options?:
|
|
52
|
-
| { createIfNeeded?: boolean; forceNewSession?: boolean; sync?: never; source?: Sources }
|
|
53
|
-
| { createIfNeeded?: never; forceNewSession?: never; sync: boolean; source?: Sources },
|
|
87
|
+
options?: GetSessionOptions,
|
|
54
88
|
): Promise<ProviderAuthenticationSession | undefined>;
|
|
55
89
|
get onDidChange(): Event<void>;
|
|
56
90
|
}
|
|
@@ -120,9 +154,7 @@ abstract class IntegrationAuthenticationProviderBase<
|
|
|
120
154
|
@trace()
|
|
121
155
|
async getSession(
|
|
122
156
|
descriptor: IntegrationAuthenticationSessionDescriptor,
|
|
123
|
-
options?:
|
|
124
|
-
| { createIfNeeded?: boolean; forceNewSession?: boolean; sync?: never; source?: Sources }
|
|
125
|
-
| { createIfNeeded?: never; forceNewSession?: never; sync: boolean; source?: Sources },
|
|
157
|
+
options?: GetSessionOptions,
|
|
126
158
|
): Promise<ProviderAuthenticationSession | undefined> {
|
|
127
159
|
let session;
|
|
128
160
|
let previousToken;
|
|
@@ -143,10 +175,15 @@ abstract class IntegrationAuthenticationProviderBase<
|
|
|
143
175
|
}
|
|
144
176
|
|
|
145
177
|
const isExpiredSession = session?.expiresAt != null && new Date(session.expiresAt).getTime() < Date.now();
|
|
146
|
-
|
|
178
|
+
// A rejected token has to re-resolve even when the stored session looks perfectly healthy: that is
|
|
179
|
+
// the whole point — the provider refused it while its expiry still says valid. Without this the
|
|
180
|
+
// stored session short-circuits here and the refresh below is never reached.
|
|
181
|
+
const refreshRejectedToken = options?.refreshRejectedToken === true && session != null;
|
|
182
|
+
if (session == null || isExpiredSession || refreshRejectedToken) {
|
|
147
183
|
session = await this.getNewSession(descriptor, {
|
|
148
184
|
...options,
|
|
149
185
|
refreshIfExpired: isExpiredSession,
|
|
186
|
+
refreshRejectedToken: refreshRejectedToken,
|
|
150
187
|
});
|
|
151
188
|
|
|
152
189
|
if (session != null) {
|
|
@@ -163,21 +200,7 @@ abstract class IntegrationAuthenticationProviderBase<
|
|
|
163
200
|
|
|
164
201
|
protected abstract getNewSession(
|
|
165
202
|
descriptor: IntegrationAuthenticationSessionDescriptor,
|
|
166
|
-
options?:
|
|
167
|
-
| {
|
|
168
|
-
createIfNeeded?: boolean;
|
|
169
|
-
forceNewSession?: boolean;
|
|
170
|
-
sync?: never;
|
|
171
|
-
refreshIfExpired?: boolean;
|
|
172
|
-
source?: Sources;
|
|
173
|
-
}
|
|
174
|
-
| {
|
|
175
|
-
createIfNeeded?: never;
|
|
176
|
-
forceNewSession?: never;
|
|
177
|
-
sync: boolean;
|
|
178
|
-
refreshIfExpired?: boolean;
|
|
179
|
-
source?: Sources;
|
|
180
|
-
},
|
|
203
|
+
options?: GetNewSessionOptions,
|
|
181
204
|
): Promise<ProviderAuthenticationSession | undefined>;
|
|
182
205
|
|
|
183
206
|
protected fireChange(): void {
|
|
@@ -202,21 +225,7 @@ export class CloudIntegrationAuthenticationProvider<
|
|
|
202
225
|
|
|
203
226
|
protected override async getNewSession(
|
|
204
227
|
descriptor: IntegrationAuthenticationSessionDescriptor,
|
|
205
|
-
options?:
|
|
206
|
-
| {
|
|
207
|
-
createIfNeeded?: boolean;
|
|
208
|
-
forceNewSession?: boolean;
|
|
209
|
-
sync?: never;
|
|
210
|
-
refreshIfExpired?: boolean;
|
|
211
|
-
source?: Sources;
|
|
212
|
-
}
|
|
213
|
-
| {
|
|
214
|
-
createIfNeeded?: never;
|
|
215
|
-
forceNewSession?: never;
|
|
216
|
-
sync: boolean;
|
|
217
|
-
refreshIfExpired?: boolean;
|
|
218
|
-
source?: Sources;
|
|
219
|
-
},
|
|
228
|
+
options?: GetNewSessionOptions,
|
|
220
229
|
): Promise<ProviderAuthenticationSession | undefined> {
|
|
221
230
|
if (options?.forceNewSession) {
|
|
222
231
|
if ((await this.disconnectCloudSession()) === 'failure') {
|
|
@@ -230,9 +239,15 @@ export class CloudIntegrationAuthenticationProvider<
|
|
|
230
239
|
// TODO: This is a stopgap to make sure we're not hammering the api on automatic calls to get the session.
|
|
231
240
|
// Ultimately we want to timestamp calls to syncCloudIntegrations and use that to determine whether we should
|
|
232
241
|
// make the call or not.
|
|
242
|
+
// `refreshRejectedToken` joins the list for the same reason `refreshIfExpired` is on it: both mean the
|
|
243
|
+
// session in hand is known-unusable, so skipping the cloud round trip would just re-serve it.
|
|
233
244
|
let session =
|
|
234
|
-
options?.refreshIfExpired ||
|
|
235
|
-
|
|
245
|
+
options?.refreshIfExpired ||
|
|
246
|
+
options?.refreshRejectedToken ||
|
|
247
|
+
options?.createIfNeeded ||
|
|
248
|
+
options?.forceNewSession ||
|
|
249
|
+
options?.sync
|
|
250
|
+
? await this.getCloudSession(descriptor, { refreshRejectedToken: options?.refreshRejectedToken })
|
|
236
251
|
: undefined;
|
|
237
252
|
|
|
238
253
|
const shouldCreateSession = options?.createIfNeeded && session == null;
|
|
@@ -269,6 +284,7 @@ export class CloudIntegrationAuthenticationProvider<
|
|
|
269
284
|
|
|
270
285
|
private async getCloudSession(
|
|
271
286
|
descriptor: IntegrationAuthenticationSessionDescriptor,
|
|
287
|
+
options?: { refreshRejectedToken?: boolean },
|
|
272
288
|
): Promise<ProviderAuthenticationSession | undefined> {
|
|
273
289
|
const loggedIn = (await this.authenticationService.ctx.account.getAccount()) != null;
|
|
274
290
|
if (!loggedIn) return undefined;
|
|
@@ -304,7 +320,13 @@ export class CloudIntegrationAuthenticationProvider<
|
|
|
304
320
|
session.expiresIn = maxSmallIntegerV8; // maximum expiration length
|
|
305
321
|
}
|
|
306
322
|
|
|
307
|
-
|
|
323
|
+
// `expiresIn < 60` is a PREDICTION that the token is about to lapse, and it is the only trigger the
|
|
324
|
+
// refresh used to have. It cannot see a token the provider has already refused while the backend
|
|
325
|
+
// still believes it is valid — a revoked grant, retired scopes, an app removed from the org — where
|
|
326
|
+
// `expiresIn` stays high and the doomed token is re-sent on every read. `refreshRejectedToken` is
|
|
327
|
+
// the observed counterpart: the caller saw the provider reject this exact token, so refresh it
|
|
328
|
+
// regardless of what its expiry claims.
|
|
329
|
+
if (session != null && (session.expiresIn < 60 || options?.refreshRejectedToken)) {
|
|
308
330
|
session = await cloudIntegrations.getConnectionSession(
|
|
309
331
|
this.authProviderId,
|
|
310
332
|
session.accessToken,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-connection recovery state for a cloud token the provider REFUSED — a revoked grant, retired scopes, an
|
|
3
|
+
* app removed from the org. A purely predictive refresh (`expiresIn < 60`) cannot see that case, because the
|
|
4
|
+
* backend still believes the token is valid, so without this the refused token is re-sent on every read and
|
|
5
|
+
* the failure only clears when the user reconnects by hand.
|
|
6
|
+
*
|
|
7
|
+
* One recovery refresh per connection, then the connection is the failure budget's problem:
|
|
8
|
+
* - {@link recordRejection} on an `AuthenticationError` — arms the refresh, or reports that the rejection is
|
|
9
|
+
* not recoverable so the caller counts it as a real failure instead.
|
|
10
|
+
* - {@link claimRefresh} before the next read resolves its session — spends the rejection, so the refresh
|
|
11
|
+
* fires once and no further read of that connection asks the cloud again.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately NOT re-armed when the refresh hands back a different token. The GK cloud mints a new access
|
|
14
|
+
* token on every `/refresh`, so a credential the provider refuses for a reason a refresh cannot fix (retired
|
|
15
|
+
* scopes being the plain case) would look "rotated" on each attempt: re-arming on that would refresh on every
|
|
16
|
+
* read forever and the failure budget — the thing that disconnects and prompts a reconnect — would never
|
|
17
|
+
* advance. What resets this instead is {@link clear}, called by every path that replaces or removes the stored
|
|
18
|
+
* tokens; a connection that is simply working never reaches here at all.
|
|
19
|
+
*/
|
|
20
|
+
export class RejectedTokenTracker {
|
|
21
|
+
private readonly _rejections = new Map<string, 'armed' | 'spent'>();
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether a read of `connectionId` must force the cloud refresh of a token known to be refused, spending
|
|
25
|
+
* the rejection so the refresh runs at most once. Spent here rather than after the read because a resolve
|
|
26
|
+
* that throws must not leave the refresh armed and ask the cloud again on the next read.
|
|
27
|
+
*/
|
|
28
|
+
claimRefresh(connectionId: string): boolean {
|
|
29
|
+
if (this._rejections.get(connectionId) !== 'armed') return false;
|
|
30
|
+
|
|
31
|
+
this._rejections.set(connectionId, 'spent');
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Arms the recovery refresh for `connectionId`. Returns `false` once this connection has had its refresh
|
|
37
|
+
* (or already has one pending), so the caller falls back to its own failure handling: a token still
|
|
38
|
+
* refused after the refresh is not self-healing, and only the failure budget ends in a reconnect prompt.
|
|
39
|
+
*/
|
|
40
|
+
recordRejection(connectionId: string): boolean {
|
|
41
|
+
if (this._rejections.has(connectionId)) return false;
|
|
42
|
+
|
|
43
|
+
this._rejections.set(connectionId, 'armed');
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Drops all state, for when the stored tokens a rejection could name are replaced or gone. */
|
|
48
|
+
clear(): void {
|
|
49
|
+
this._rejections.clear();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether any connection is holding a rejection. For assertions; the read path uses {@link claimRefresh}. */
|
|
53
|
+
get empty(): boolean {
|
|
54
|
+
return this._rejections.size === 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -97,8 +97,12 @@ async function readErrorBodyMessage(response: Response): Promise<{ message: stri
|
|
|
97
97
|
message = m;
|
|
98
98
|
} else if (typeof error === 'string') {
|
|
99
99
|
message = error;
|
|
100
|
-
} else if (error != null && typeof error === 'object'
|
|
101
|
-
|
|
100
|
+
} else if (error != null && typeof error === 'object') {
|
|
101
|
+
// Some hosts nest it as `{ error: { message } }`; narrow the same way as the outer object.
|
|
102
|
+
const nested = (error as { message?: unknown }).message;
|
|
103
|
+
if (typeof nested === 'string') {
|
|
104
|
+
message = nested;
|
|
105
|
+
}
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
} catch {
|