@serviceme/devtools-core 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.d.mts +2 -592
- package/dist/auth.d.ts +2 -592
- package/dist/auth.js +175 -17
- package/dist/auth.js.map +1 -1
- package/dist/auth.mjs +175 -17
- package/dist/auth.mjs.map +1 -1
- package/dist/device.js +26 -0
- package/dist/device.js.map +1 -1
- package/dist/device.mjs +26 -0
- package/dist/device.mjs.map +1 -1
- package/dist/index-87-_JvQi.d.mts +626 -0
- package/dist/index-87-_JvQi.d.ts +626 -0
- package/dist/index.d.mts +24 -31
- package/dist/index.d.ts +24 -31
- package/dist/index.js +311 -68
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +317 -74
- package/dist/index.mjs.map +1 -1
- package/dist/toolbox.js +25 -0
- package/dist/toolbox.js.map +1 -1
- package/dist/toolbox.mjs +25 -0
- package/dist/toolbox.mjs.map +1 -1
- package/package.json +2 -2
package/dist/auth.js
CHANGED
|
@@ -181,6 +181,18 @@ var AccessControl = class {
|
|
|
181
181
|
}
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
+
// src/logger.ts
|
|
185
|
+
var noopLogger = {
|
|
186
|
+
debug() {
|
|
187
|
+
},
|
|
188
|
+
info() {
|
|
189
|
+
},
|
|
190
|
+
warn() {
|
|
191
|
+
},
|
|
192
|
+
error() {
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
184
196
|
// src/auth/AuthStateManager.ts
|
|
185
197
|
var import_node_events = require("events");
|
|
186
198
|
var AuthStateManager = class {
|
|
@@ -343,6 +355,7 @@ var AuthCore = class {
|
|
|
343
355
|
this.state = opts.stateManager ?? new AuthStateManager();
|
|
344
356
|
this.tokenStore = opts.tokenStore;
|
|
345
357
|
this.accessControl = opts.accessControl;
|
|
358
|
+
this.logger = opts.logger ?? noopLogger;
|
|
346
359
|
}
|
|
347
360
|
/** Snapshot of every account, the active provider, and the last error. */
|
|
348
361
|
status() {
|
|
@@ -364,16 +377,28 @@ var AuthCore = class {
|
|
|
364
377
|
*/
|
|
365
378
|
async login(provider, ui, shouldContinue) {
|
|
366
379
|
const providerImpl = this.registry.get(provider);
|
|
380
|
+
this.logger.info("[AuthCore] Starting device-flow login", { provider });
|
|
367
381
|
try {
|
|
368
382
|
const initial = await providerImpl.requestDeviceFlow();
|
|
369
383
|
await ui(initial);
|
|
384
|
+
if (!initial.deviceCode) {
|
|
385
|
+
this.logger.error("[AuthCore] Provider did not return deviceCode", provider);
|
|
386
|
+
throw new Error("Auth provider did not return deviceCode for device flow completion");
|
|
387
|
+
}
|
|
370
388
|
const session = await providerImpl.completeDeviceFlow(
|
|
371
|
-
initial.
|
|
372
|
-
shouldContinue
|
|
389
|
+
initial.deviceCode,
|
|
390
|
+
shouldContinue,
|
|
391
|
+
initial.pollIntervalMs
|
|
373
392
|
);
|
|
374
|
-
|
|
393
|
+
const result = await this.persistSession(providerImpl, session);
|
|
394
|
+
this.logger.info("[AuthCore] Device-flow login completed", { provider });
|
|
395
|
+
return result;
|
|
375
396
|
} catch (err) {
|
|
376
|
-
|
|
397
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
398
|
+
this.logger.error("[AuthCore] Device-flow login failed", message, {
|
|
399
|
+
provider
|
|
400
|
+
});
|
|
401
|
+
this.state.recordError(message);
|
|
377
402
|
throw err;
|
|
378
403
|
}
|
|
379
404
|
}
|
|
@@ -398,7 +423,10 @@ var AuthCore = class {
|
|
|
398
423
|
if (!provider) return null;
|
|
399
424
|
const account = this.state.getActiveAccount();
|
|
400
425
|
if (!account) return null;
|
|
401
|
-
const envelope = await this.tokenStore.get({
|
|
426
|
+
const envelope = await this.tokenStore.get({
|
|
427
|
+
provider,
|
|
428
|
+
accountId: account.id
|
|
429
|
+
});
|
|
402
430
|
if (!envelope) return null;
|
|
403
431
|
return { provider, account, token: envelope.token };
|
|
404
432
|
}
|
|
@@ -408,9 +436,15 @@ var AuthCore = class {
|
|
|
408
436
|
* every account.
|
|
409
437
|
*/
|
|
410
438
|
async logout(provider) {
|
|
439
|
+
this.logger.info("[AuthCore] Logout requested", {
|
|
440
|
+
provider: provider ?? "all"
|
|
441
|
+
});
|
|
411
442
|
if (!provider) {
|
|
412
443
|
for (const account of this.state.listAccounts()) {
|
|
413
|
-
await this.tokenStore.delete({
|
|
444
|
+
await this.tokenStore.delete({
|
|
445
|
+
provider: account.provider,
|
|
446
|
+
accountId: account.id
|
|
447
|
+
});
|
|
414
448
|
this.state.removeAccount(account.provider, account.id);
|
|
415
449
|
}
|
|
416
450
|
this.state.clearAll();
|
|
@@ -485,6 +519,10 @@ var AuthCore = class {
|
|
|
485
519
|
avatarUrl: meta.avatarUrl,
|
|
486
520
|
expiresAt: meta.expiresAt ?? expiresAt
|
|
487
521
|
};
|
|
522
|
+
this.logger.debug("[AuthCore] Persisting session", {
|
|
523
|
+
provider: meta.provider,
|
|
524
|
+
accountId: meta.id
|
|
525
|
+
});
|
|
488
526
|
await this.tokenStore.set({ provider: meta.provider, accountId: meta.id }, session.token, {
|
|
489
527
|
expiresAt
|
|
490
528
|
});
|
|
@@ -587,11 +625,13 @@ var GitHubAuthProvider = class {
|
|
|
587
625
|
userUrl: config.userUrl ?? DEFAULT_USER_URL,
|
|
588
626
|
scope: config.scope ?? DEFAULT_SCOPE,
|
|
589
627
|
minPollIntervalMs: config.minPollIntervalMs ?? 1e3,
|
|
590
|
-
maxPollIntervalMs: config.maxPollIntervalMs ??
|
|
628
|
+
maxPollIntervalMs: config.maxPollIntervalMs ?? 6e4,
|
|
591
629
|
maxWaitMs: config.maxWaitMs,
|
|
592
630
|
fetchImpl: config.fetchImpl ?? fetch,
|
|
593
631
|
deviceCodeRetryBaseDelayMs: config.deviceCodeRetryBaseDelayMs ?? DEFAULT_DEVICE_CODE_RETRY_BASE_DELAY_MS
|
|
594
632
|
};
|
|
633
|
+
this.logger = config.logger ?? noopLogger;
|
|
634
|
+
this.sleepImpl = config.sleepImpl ?? sleep;
|
|
595
635
|
}
|
|
596
636
|
async requestDeviceFlow(opts) {
|
|
597
637
|
const body = JSON.stringify({
|
|
@@ -600,6 +640,10 @@ var GitHubAuthProvider = class {
|
|
|
600
640
|
});
|
|
601
641
|
let lastNetworkError;
|
|
602
642
|
for (let attempt = 1; attempt <= DEVICE_CODE_MAX_ATTEMPTS; attempt++) {
|
|
643
|
+
this.logger.debug("[GitHubAuthProvider] Requesting device code", {
|
|
644
|
+
attempt,
|
|
645
|
+
maxAttempts: DEVICE_CODE_MAX_ATTEMPTS
|
|
646
|
+
});
|
|
603
647
|
let resp;
|
|
604
648
|
try {
|
|
605
649
|
resp = await this.cfg.fetchImpl(this.cfg.deviceCodeUrl, {
|
|
@@ -613,45 +657,99 @@ var GitHubAuthProvider = class {
|
|
|
613
657
|
});
|
|
614
658
|
} catch (error) {
|
|
615
659
|
if (!isTransientNetworkError(error) || attempt === DEVICE_CODE_MAX_ATTEMPTS) {
|
|
660
|
+
this.logger.error(
|
|
661
|
+
"[GitHubAuthProvider] Device-code request failed (non-retryable)",
|
|
662
|
+
error instanceof Error ? error.message : String(error),
|
|
663
|
+
{ attempt }
|
|
664
|
+
);
|
|
616
665
|
throw error;
|
|
617
666
|
}
|
|
667
|
+
const delayMs = this.cfg.deviceCodeRetryBaseDelayMs * 2 ** (attempt - 1);
|
|
668
|
+
this.logger.warn(
|
|
669
|
+
"[GitHubAuthProvider] Transient network error requesting device code, retrying",
|
|
670
|
+
{
|
|
671
|
+
attempt,
|
|
672
|
+
delayMs,
|
|
673
|
+
error: error instanceof Error ? error.message : String(error)
|
|
674
|
+
}
|
|
675
|
+
);
|
|
618
676
|
lastNetworkError = error;
|
|
619
|
-
await
|
|
677
|
+
await this.sleepImpl(delayMs);
|
|
620
678
|
continue;
|
|
621
679
|
}
|
|
622
680
|
if (!resp.ok) {
|
|
681
|
+
this.logger.error(
|
|
682
|
+
"[GitHubAuthProvider] Device-code request rejected by GitHub",
|
|
683
|
+
`HTTP ${resp.status}`
|
|
684
|
+
);
|
|
623
685
|
throw new Error(`GitHub device-code request failed: HTTP ${resp.status}`);
|
|
624
686
|
}
|
|
625
687
|
const data = await resp.json();
|
|
626
688
|
if (!data.device_code || !data.user_code || !data.verification_uri) {
|
|
689
|
+
this.logger.error(
|
|
690
|
+
"[GitHubAuthProvider] Device-code response missing required fields",
|
|
691
|
+
JSON.stringify(Object.keys(data))
|
|
692
|
+
);
|
|
627
693
|
throw new Error("GitHub device-code response missing required fields");
|
|
628
694
|
}
|
|
695
|
+
this.logger.info("[GitHubAuthProvider] Device code obtained", {
|
|
696
|
+
userCode: data.user_code,
|
|
697
|
+
verificationUri: data.verification_uri,
|
|
698
|
+
expiresInSec: data.expires_in,
|
|
699
|
+
pollIntervalSec: data.interval
|
|
700
|
+
});
|
|
629
701
|
return {
|
|
630
702
|
provider: this.providerId,
|
|
703
|
+
deviceCode: data.device_code,
|
|
631
704
|
userCode: data.user_code,
|
|
632
705
|
verificationUrl: data.verification_uri,
|
|
633
706
|
expiresAt: Date.now() + data.expires_in * 1e3,
|
|
707
|
+
pollIntervalMs: data.interval * 1e3,
|
|
634
708
|
message: `Open ${data.verification_uri} and enter ${data.user_code}`
|
|
635
709
|
};
|
|
636
710
|
}
|
|
637
711
|
throw lastNetworkError ?? new Error("GitHub device-code request failed");
|
|
638
712
|
}
|
|
639
|
-
async completeDeviceFlow(deviceCode, shouldContinue) {
|
|
713
|
+
async completeDeviceFlow(deviceCode, shouldContinue, initialPollIntervalMs) {
|
|
640
714
|
const start = Date.now();
|
|
641
|
-
let pollIntervalMs = this.cfg.minPollIntervalMs;
|
|
715
|
+
let pollIntervalMs = Math.max(this.cfg.minPollIntervalMs, initialPollIntervalMs ?? 0);
|
|
642
716
|
let consecutiveSlowDown = 0;
|
|
717
|
+
let pollCount = 0;
|
|
718
|
+
this.logger.info("[GitHubAuthProvider] Starting device-flow polling", {
|
|
719
|
+
initialPollIntervalMs: pollIntervalMs,
|
|
720
|
+
maxWaitMs: this.cfg.maxWaitMs
|
|
721
|
+
});
|
|
643
722
|
while (true) {
|
|
644
723
|
if (shouldContinue && !shouldContinue()) {
|
|
724
|
+
this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
|
|
725
|
+
elapsedMs: Date.now() - start,
|
|
726
|
+
pollCount
|
|
727
|
+
});
|
|
645
728
|
throw new Error("GitHub device flow cancelled by caller");
|
|
646
729
|
}
|
|
647
730
|
const elapsed = Date.now() - start;
|
|
648
731
|
if (this.cfg.maxWaitMs !== void 0 && elapsed > this.cfg.maxWaitMs) {
|
|
732
|
+
this.logger.warn("[GitHubAuthProvider] Device flow exceeded max wait time", {
|
|
733
|
+
elapsedMs: elapsed,
|
|
734
|
+
maxWaitMs: this.cfg.maxWaitMs,
|
|
735
|
+
pollCount
|
|
736
|
+
});
|
|
649
737
|
throw new Error("GitHub device flow exceeded max wait time");
|
|
650
738
|
}
|
|
651
|
-
await
|
|
739
|
+
await this.sleepImpl(pollIntervalMs);
|
|
652
740
|
if (shouldContinue && !shouldContinue()) {
|
|
741
|
+
this.logger.info("[GitHubAuthProvider] Device flow cancelled by caller", {
|
|
742
|
+
elapsedMs: Date.now() - start,
|
|
743
|
+
pollCount
|
|
744
|
+
});
|
|
653
745
|
throw new Error("GitHub device flow cancelled by caller");
|
|
654
746
|
}
|
|
747
|
+
pollCount++;
|
|
748
|
+
this.logger.debug("[GitHubAuthProvider] Polling for authorization", {
|
|
749
|
+
pollCount,
|
|
750
|
+
elapsedMs: Date.now() - start,
|
|
751
|
+
pollIntervalMs
|
|
752
|
+
});
|
|
655
753
|
let resp;
|
|
656
754
|
try {
|
|
657
755
|
resp = await this.cfg.fetchImpl(this.cfg.tokenUrl, {
|
|
@@ -669,19 +767,59 @@ var GitHubAuthProvider = class {
|
|
|
669
767
|
});
|
|
670
768
|
} catch (error) {
|
|
671
769
|
if (!isTransientNetworkError(error)) {
|
|
770
|
+
this.logger.error(
|
|
771
|
+
"[GitHubAuthProvider] Non-retryable error while polling token endpoint",
|
|
772
|
+
error instanceof Error ? error.message : String(error),
|
|
773
|
+
{ pollCount }
|
|
774
|
+
);
|
|
672
775
|
throw error;
|
|
673
776
|
}
|
|
674
|
-
|
|
777
|
+
const nextPollIntervalMs = Math.min(
|
|
778
|
+
Math.round(pollIntervalMs * 1.5),
|
|
779
|
+
this.cfg.maxPollIntervalMs
|
|
780
|
+
);
|
|
781
|
+
this.logger.warn(
|
|
782
|
+
"[GitHubAuthProvider] Transient network error while polling, backing off",
|
|
783
|
+
{
|
|
784
|
+
pollCount,
|
|
785
|
+
error: error instanceof Error ? error.message : String(error),
|
|
786
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
787
|
+
nextPollIntervalMs
|
|
788
|
+
}
|
|
789
|
+
);
|
|
790
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
675
791
|
continue;
|
|
676
792
|
}
|
|
677
793
|
if (!resp.ok) {
|
|
678
|
-
|
|
794
|
+
const nextPollIntervalMs = Math.min(
|
|
795
|
+
Math.round(pollIntervalMs * 1.5),
|
|
796
|
+
this.cfg.maxPollIntervalMs
|
|
797
|
+
);
|
|
798
|
+
this.logger.warn(
|
|
799
|
+
"[GitHubAuthProvider] Token endpoint returned non-OK status, backing off",
|
|
800
|
+
{
|
|
801
|
+
pollCount,
|
|
802
|
+
status: resp.status,
|
|
803
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
804
|
+
nextPollIntervalMs
|
|
805
|
+
}
|
|
806
|
+
);
|
|
807
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
679
808
|
continue;
|
|
680
809
|
}
|
|
681
810
|
const data = await resp.json();
|
|
682
811
|
if (data.access_token) {
|
|
812
|
+
this.logger.info("[GitHubAuthProvider] Authorization granted, fetching user profile", {
|
|
813
|
+
pollCount,
|
|
814
|
+
elapsedMs: Date.now() - start
|
|
815
|
+
});
|
|
683
816
|
const rawUser = await this.fetchGitHubUser(data.access_token);
|
|
684
817
|
const email = await this.resolveEmail(data.access_token, rawUser);
|
|
818
|
+
this.logger.info("[GitHubAuthProvider] Device-flow login completed", {
|
|
819
|
+
pollCount,
|
|
820
|
+
elapsedMs: Date.now() - start,
|
|
821
|
+
login: rawUser.login
|
|
822
|
+
});
|
|
685
823
|
return {
|
|
686
824
|
token: data.access_token,
|
|
687
825
|
refreshToken: data.refresh_token,
|
|
@@ -696,22 +834,42 @@ var GitHubAuthProvider = class {
|
|
|
696
834
|
};
|
|
697
835
|
}
|
|
698
836
|
if (data.error === "authorization_pending") {
|
|
837
|
+
this.logger.debug("[GitHubAuthProvider] Authorization still pending", {
|
|
838
|
+
pollCount,
|
|
839
|
+
elapsedMs: Date.now() - start
|
|
840
|
+
});
|
|
699
841
|
continue;
|
|
700
842
|
}
|
|
701
843
|
if (data.error === "slow_down") {
|
|
702
844
|
consecutiveSlowDown++;
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
845
|
+
const nextPollIntervalMs = Math.min(pollIntervalMs + 5e3, this.cfg.maxPollIntervalMs);
|
|
846
|
+
this.logger.warn("[GitHubAuthProvider] GitHub requested slower polling (slow_down)", {
|
|
847
|
+
pollCount,
|
|
848
|
+
consecutiveSlowDown,
|
|
849
|
+
previousPollIntervalMs: pollIntervalMs,
|
|
850
|
+
nextPollIntervalMs
|
|
851
|
+
});
|
|
852
|
+
pollIntervalMs = nextPollIntervalMs;
|
|
707
853
|
continue;
|
|
708
854
|
}
|
|
709
855
|
if (data.error === "access_denied") {
|
|
856
|
+
this.logger.warn("[GitHubAuthProvider] User denied authorization", {
|
|
857
|
+
pollCount
|
|
858
|
+
});
|
|
710
859
|
throw new Error("User denied authorization");
|
|
711
860
|
}
|
|
712
861
|
if (data.error === "expired_token") {
|
|
862
|
+
this.logger.warn(
|
|
863
|
+
"[GitHubAuthProvider] Device code expired before authorization completed",
|
|
864
|
+
{ pollCount, elapsedMs: Date.now() - start }
|
|
865
|
+
);
|
|
713
866
|
throw new Error("GitHub device code expired \u2014 restart the flow");
|
|
714
867
|
}
|
|
868
|
+
this.logger.error(
|
|
869
|
+
"[GitHubAuthProvider] Unexpected device-flow error from token endpoint",
|
|
870
|
+
data.error ?? "unknown",
|
|
871
|
+
{ pollCount }
|
|
872
|
+
);
|
|
715
873
|
throw new Error(`GitHub device flow error: ${data.error ?? "unknown"}`);
|
|
716
874
|
}
|
|
717
875
|
}
|