@actionway/cli 0.18.14 → 0.18.16
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/index.js +105 -10
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12573,11 +12573,22 @@ function deviceErrorDetail(value) {
|
|
|
12573
12573
|
const message = typeof error?.message === "string" ? error.message : "";
|
|
12574
12574
|
return [code, message].filter(Boolean).join(": ");
|
|
12575
12575
|
}
|
|
12576
|
-
async function startDevicePairing(gateway, codeChallenge, fetchImpl = fetch) {
|
|
12576
|
+
async function startDevicePairing(gateway, codeChallenge, fetchImpl = fetch, context = {}) {
|
|
12577
12577
|
const response = await fetchImpl(`${stripSlash(gateway)}/api/auth/cli-device/start`, {
|
|
12578
12578
|
method: "POST",
|
|
12579
|
-
headers: {
|
|
12580
|
-
|
|
12579
|
+
headers: {
|
|
12580
|
+
Accept: "application/json",
|
|
12581
|
+
"Content-Type": "application/json",
|
|
12582
|
+
"x-actionway-cli-version": getCliVersion(),
|
|
12583
|
+
...context.clientContext ? localClientContextHeaders(context.clientContext) : {}
|
|
12584
|
+
},
|
|
12585
|
+
body: JSON.stringify({
|
|
12586
|
+
code_challenge: codeChallenge,
|
|
12587
|
+
...context.installAttribution ? {
|
|
12588
|
+
install_session_id: context.installAttribution.installSessionId,
|
|
12589
|
+
install_source: context.installAttribution.installSource
|
|
12590
|
+
} : {}
|
|
12591
|
+
}),
|
|
12581
12592
|
signal: AbortSignal.timeout(DEVICE_START_TIMEOUT_MS)
|
|
12582
12593
|
});
|
|
12583
12594
|
const value = await readDeviceJson(response);
|
|
@@ -12750,6 +12761,18 @@ async function login(options = {}) {
|
|
|
12750
12761
|
const sleep = options.sleepImpl ?? defaultSleep;
|
|
12751
12762
|
const onNotice = options.onNotice ?? ((message) => process.stderr.write(`${message}
|
|
12752
12763
|
`));
|
|
12764
|
+
let failureReported = false;
|
|
12765
|
+
const reportInstallEvent = async (event) => {
|
|
12766
|
+
if (!options.installAttribution || !options.onInstallEvent) return;
|
|
12767
|
+
try {
|
|
12768
|
+
await options.onInstallEvent(event);
|
|
12769
|
+
} catch {
|
|
12770
|
+
}
|
|
12771
|
+
};
|
|
12772
|
+
const reportInstallFailure = async (failureStage2, failureReason) => {
|
|
12773
|
+
failureReported = true;
|
|
12774
|
+
await reportInstallEvent({ event: "install_failed", failureStage: failureStage2, failureReason });
|
|
12775
|
+
};
|
|
12753
12776
|
const gateway = resolveGatewayUrl(options.gateway, env);
|
|
12754
12777
|
const gatewaySource = options.gateway?.trim() ? "flag" : (env.ACTIONWAY_GATEWAY_URL ?? "").trim() ? "env" : "default";
|
|
12755
12778
|
let clientId = options.clientId?.trim() || (env.ACTIONWAY_OAUTH_CLIENT_ID ?? "").trim();
|
|
@@ -12771,6 +12794,7 @@ async function login(options = {}) {
|
|
|
12771
12794
|
throw error;
|
|
12772
12795
|
};
|
|
12773
12796
|
if (!!clientId !== !!issuer) {
|
|
12797
|
+
await reportInstallFailure("configuration", "configuration_invalid");
|
|
12774
12798
|
throw new CliError(
|
|
12775
12799
|
"E_SCHEMA",
|
|
12776
12800
|
"client_id and issuer overrides must be provided together",
|
|
@@ -12783,6 +12807,7 @@ async function login(options = {}) {
|
|
|
12783
12807
|
try {
|
|
12784
12808
|
discovered = await fetchCliOAuthConfig(gateway, fetchImpl);
|
|
12785
12809
|
} catch (error) {
|
|
12810
|
+
await reportInstallFailure("configuration", "configuration_unavailable");
|
|
12786
12811
|
const message = error instanceof Error ? error.message : String(error);
|
|
12787
12812
|
return withAuthContext(new CliError(
|
|
12788
12813
|
"E_BACKEND",
|
|
@@ -12794,9 +12819,13 @@ async function login(options = {}) {
|
|
|
12794
12819
|
issuer ||= discovered.issuer;
|
|
12795
12820
|
}
|
|
12796
12821
|
const timeoutMs = options.timeoutSeconds !== void 0 && Number.isFinite(options.timeoutSeconds) && options.timeoutSeconds > 0 ? options.timeoutSeconds * 1e3 : DEFAULT_LOGIN_TIMEOUT_MS;
|
|
12822
|
+
let failureStage = "pairing";
|
|
12797
12823
|
try {
|
|
12798
12824
|
const verifier = randomBase64Url();
|
|
12799
|
-
const pairing = await startDevicePairing(gateway, pkceChallenge(verifier), fetchImpl
|
|
12825
|
+
const pairing = await startDevicePairing(gateway, pkceChallenge(verifier), fetchImpl, {
|
|
12826
|
+
...options.installAttribution ? { installAttribution: options.installAttribution } : {},
|
|
12827
|
+
...options.clientContext ? { clientContext: options.clientContext } : {}
|
|
12828
|
+
});
|
|
12800
12829
|
onNotice(
|
|
12801
12830
|
`Sign in to Actionway on any device:
|
|
12802
12831
|
${pairing.verificationUrl}
|
|
@@ -12805,6 +12834,7 @@ async function login(options = {}) {
|
|
|
12805
12834
|
if (options.noBrowser) onNotice(`Open this URL to approve the sign-in:
|
|
12806
12835
|
${pairing.verificationUrlComplete}`);
|
|
12807
12836
|
else openBrowser(pairing.verificationUrlComplete, onNotice);
|
|
12837
|
+
failureStage = "authorization";
|
|
12808
12838
|
const deadline = Date.now() + Math.min(timeoutMs, pairing.expiresInS * 1e3);
|
|
12809
12839
|
let approvalCode = null;
|
|
12810
12840
|
for (; ; ) {
|
|
@@ -12814,6 +12844,7 @@ ${pairing.verificationUrlComplete}`);
|
|
|
12814
12844
|
break;
|
|
12815
12845
|
}
|
|
12816
12846
|
if (poll.status === "denied") {
|
|
12847
|
+
await reportInstallFailure("authorization", "authorization_denied");
|
|
12817
12848
|
throw new CliError(
|
|
12818
12849
|
"E_BACKEND",
|
|
12819
12850
|
"the sign-in request was denied in the browser",
|
|
@@ -12821,6 +12852,7 @@ ${pairing.verificationUrlComplete}`);
|
|
|
12821
12852
|
);
|
|
12822
12853
|
}
|
|
12823
12854
|
if (poll.status === "expired") {
|
|
12855
|
+
await reportInstallFailure("authorization", "authorization_expired");
|
|
12824
12856
|
throw new CliError(
|
|
12825
12857
|
"E_BACKEND",
|
|
12826
12858
|
"the sign-in code expired before it was approved",
|
|
@@ -12828,6 +12860,7 @@ ${pairing.verificationUrlComplete}`);
|
|
|
12828
12860
|
);
|
|
12829
12861
|
}
|
|
12830
12862
|
if (Date.now() >= deadline) {
|
|
12863
|
+
await reportInstallFailure("authorization", "authorization_timeout");
|
|
12831
12864
|
throw new CliError(
|
|
12832
12865
|
"E_BACKEND",
|
|
12833
12866
|
`login timed out after ${Math.round(timeoutMs / 1e3)} seconds`,
|
|
@@ -12836,6 +12869,7 @@ ${pairing.verificationUrlComplete}`);
|
|
|
12836
12869
|
}
|
|
12837
12870
|
await sleep(pairing.intervalS * 1e3);
|
|
12838
12871
|
}
|
|
12872
|
+
failureStage = "token_exchange";
|
|
12839
12873
|
const tokens = await tokenRequest(
|
|
12840
12874
|
issuer,
|
|
12841
12875
|
new URLSearchParams({
|
|
@@ -12847,6 +12881,8 @@ ${pairing.verificationUrlComplete}`);
|
|
|
12847
12881
|
}),
|
|
12848
12882
|
fetchImpl
|
|
12849
12883
|
);
|
|
12884
|
+
await reportInstallEvent({ event: "install_token_exchanged" });
|
|
12885
|
+
failureStage = "account_connection";
|
|
12850
12886
|
const account = await fetchCliSession(gateway, tokens.accessToken, fetchImpl, {
|
|
12851
12887
|
...options.installAttribution ? { installAttribution: options.installAttribution } : {},
|
|
12852
12888
|
...options.clientContext ? { clientContext: options.clientContext } : {}
|
|
@@ -12874,6 +12910,11 @@ ${pairing.verificationUrlComplete}`);
|
|
|
12874
12910
|
saveCredentials(credentials, env);
|
|
12875
12911
|
return { credentials, account };
|
|
12876
12912
|
} catch (error) {
|
|
12913
|
+
if (!failureReported) {
|
|
12914
|
+
const stage = typeof failureStage === "string" ? failureStage : "pairing";
|
|
12915
|
+
const reason = stage === "pairing" ? "pairing_unavailable" : stage === "authorization" ? "authorization_unavailable" : stage === "token_exchange" ? "token_exchange_failed" : "account_connection_failed";
|
|
12916
|
+
await reportInstallFailure(stage, reason);
|
|
12917
|
+
}
|
|
12877
12918
|
return withAuthContext(error);
|
|
12878
12919
|
}
|
|
12879
12920
|
}
|
|
@@ -13367,6 +13408,36 @@ async function captureInstallStarted(input) {
|
|
|
13367
13408
|
return false;
|
|
13368
13409
|
}
|
|
13369
13410
|
}
|
|
13411
|
+
async function captureInstallAuthEvent(input) {
|
|
13412
|
+
try {
|
|
13413
|
+
const response = await fetchWithTimeout(
|
|
13414
|
+
`${stripSlash2(input.gateway)}/api/analytics/install-auth`,
|
|
13415
|
+
{
|
|
13416
|
+
method: "POST",
|
|
13417
|
+
headers: {
|
|
13418
|
+
Accept: "application/json",
|
|
13419
|
+
"Content-Type": "application/json",
|
|
13420
|
+
"x-actionway-cli-version": getCliVersion(),
|
|
13421
|
+
...input.clientContext ? localClientContextHeaders(input.clientContext) : {}
|
|
13422
|
+
},
|
|
13423
|
+
body: JSON.stringify({
|
|
13424
|
+
event: input.event.event,
|
|
13425
|
+
installSessionId: input.installSessionId,
|
|
13426
|
+
installSource: input.installSource,
|
|
13427
|
+
...input.event.event === "install_failed" ? {
|
|
13428
|
+
failureStage: input.event.failureStage,
|
|
13429
|
+
failureReason: input.event.failureReason
|
|
13430
|
+
} : {}
|
|
13431
|
+
})
|
|
13432
|
+
},
|
|
13433
|
+
ANALYTICS_TIMEOUT_MS,
|
|
13434
|
+
input.fetchImpl ?? fetch
|
|
13435
|
+
);
|
|
13436
|
+
return response.ok;
|
|
13437
|
+
} catch {
|
|
13438
|
+
return false;
|
|
13439
|
+
}
|
|
13440
|
+
}
|
|
13370
13441
|
async function requestCliInstallSession(input) {
|
|
13371
13442
|
const url = new URL(`${stripSlash2(input.gateway)}/api/auth/cli-session`);
|
|
13372
13443
|
url.searchParams.set("install_session_id", input.installSessionId);
|
|
@@ -13470,6 +13541,15 @@ function registerInitCommand(program2, getClientContext = () => ({ runtimeOs: "o
|
|
|
13470
13541
|
...opts.browser === false ? { noBrowser: true } : {},
|
|
13471
13542
|
installAttribution: { installSessionId, installSource },
|
|
13472
13543
|
clientContext,
|
|
13544
|
+
onInstallEvent: async (event) => {
|
|
13545
|
+
await captureInstallAuthEvent({
|
|
13546
|
+
gateway: initGateway,
|
|
13547
|
+
installSessionId,
|
|
13548
|
+
installSource,
|
|
13549
|
+
event,
|
|
13550
|
+
clientContext
|
|
13551
|
+
});
|
|
13552
|
+
},
|
|
13473
13553
|
onNotice: (message) => emitEvent({ event: "auth", message })
|
|
13474
13554
|
});
|
|
13475
13555
|
account = {
|
|
@@ -13478,12 +13558,27 @@ function registerInitCommand(program2, getClientContext = () => ({ runtimeOs: "o
|
|
|
13478
13558
|
walletId: loginResult.account.walletId
|
|
13479
13559
|
};
|
|
13480
13560
|
} else {
|
|
13481
|
-
|
|
13482
|
-
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
|
|
13486
|
-
|
|
13561
|
+
try {
|
|
13562
|
+
account = await connectInstallSession({
|
|
13563
|
+
...opts.gateway ? { explicitGateway: opts.gateway } : {},
|
|
13564
|
+
installSessionId,
|
|
13565
|
+
installSource,
|
|
13566
|
+
clientContext
|
|
13567
|
+
});
|
|
13568
|
+
} catch (error) {
|
|
13569
|
+
await captureInstallAuthEvent({
|
|
13570
|
+
gateway: initGateway,
|
|
13571
|
+
installSessionId,
|
|
13572
|
+
installSource,
|
|
13573
|
+
clientContext,
|
|
13574
|
+
event: {
|
|
13575
|
+
event: "install_failed",
|
|
13576
|
+
failureStage: error instanceof CliError && error.code === "E_NOT_AUTHENTICATED" ? "token_exchange" : "account_connection",
|
|
13577
|
+
failureReason: error instanceof CliError && error.code === "E_NOT_AUTHENTICATED" ? "token_refresh_failed" : "account_connection_failed"
|
|
13578
|
+
}
|
|
13579
|
+
});
|
|
13580
|
+
throw error;
|
|
13581
|
+
}
|
|
13487
13582
|
}
|
|
13488
13583
|
const snapshot = whoamiSnapshot();
|
|
13489
13584
|
ok({
|
package/package.json
CHANGED