@proxy-checkout/cli 0.1.0-prx-128.109.1 → 0.1.0-prx-128.110.1
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/cjs/auth.d.cts +2 -0
- package/dist/cjs/auth.d.ts +2 -0
- package/dist/cjs/auth.js +30 -6
- package/dist/cjs/cli.d.cts +1 -1
- package/dist/cjs/cli.d.ts +1 -1
- package/dist/cjs/cli.js +23 -13
- package/dist/cjs/config.d.cts +5 -0
- package/dist/cjs/config.d.ts +5 -0
- package/dist/cjs/config.js +47 -2
- package/dist/cjs/http-client.js +9 -2
- package/dist/cjs/webhooks.d.cts +9 -9
- package/dist/cjs/webhooks.d.ts +9 -9
- package/dist/cjs/webhooks.js +26 -22
- package/dist/esm/auth.d.ts +2 -0
- package/dist/esm/auth.js +30 -6
- package/dist/esm/cli.d.ts +1 -1
- package/dist/esm/cli.js +23 -13
- package/dist/esm/config.d.ts +5 -0
- package/dist/esm/config.js +47 -2
- package/dist/esm/http-client.js +9 -2
- package/dist/esm/webhooks.d.ts +9 -9
- package/dist/esm/webhooks.js +26 -22
- package/package.json +1 -1
package/dist/cjs/auth.d.cts
CHANGED
|
@@ -26,6 +26,7 @@ export declare function login(input: {
|
|
|
26
26
|
}): Promise<CliProfile>;
|
|
27
27
|
export declare function status(input: {
|
|
28
28
|
apiBaseUrl: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
29
30
|
token: string;
|
|
30
31
|
}): Promise<CredentialExchangeResponse["profile"]>;
|
|
31
32
|
export declare function logout(input: {
|
|
@@ -33,6 +34,7 @@ export declare function logout(input: {
|
|
|
33
34
|
configStore: CliConfigStore;
|
|
34
35
|
profileName: string;
|
|
35
36
|
removeLocalProfile: boolean;
|
|
37
|
+
signal?: AbortSignal;
|
|
36
38
|
token: string;
|
|
37
39
|
}): Promise<void>;
|
|
38
40
|
export {};
|
package/dist/cjs/auth.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export declare function login(input: {
|
|
|
26
26
|
}): Promise<CliProfile>;
|
|
27
27
|
export declare function status(input: {
|
|
28
28
|
apiBaseUrl: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
29
30
|
token: string;
|
|
30
31
|
}): Promise<CredentialExchangeResponse["profile"]>;
|
|
31
32
|
export declare function logout(input: {
|
|
@@ -33,6 +34,7 @@ export declare function logout(input: {
|
|
|
33
34
|
configStore: CliConfigStore;
|
|
34
35
|
profileName: string;
|
|
35
36
|
removeLocalProfile: boolean;
|
|
37
|
+
signal?: AbortSignal;
|
|
36
38
|
token: string;
|
|
37
39
|
}): Promise<void>;
|
|
38
40
|
export {};
|
package/dist/cjs/auth.js
CHANGED
|
@@ -12,6 +12,7 @@ const deviceScope = "proxy.cli.local-webhook-development";
|
|
|
12
12
|
const deviceGrantType = "urn:ietf:params:oauth:grant-type:device_code";
|
|
13
13
|
async function login(input) {
|
|
14
14
|
const signal = input.signal ?? new AbortController().signal;
|
|
15
|
+
const previousCredential = await input.configStore.readStoredCredential(input.profileName);
|
|
15
16
|
const client = new http_client_js_1.ProxyApiClient(input.apiBaseUrl);
|
|
16
17
|
const codeResult = await client.oauthRequest("/api/auth/device/code", {
|
|
17
18
|
client_id: clientId,
|
|
@@ -55,17 +56,39 @@ async function login(input) {
|
|
|
55
56
|
await revokeExchangedCredential(input.apiBaseUrl, exchanged.credential, profile, mismatch);
|
|
56
57
|
throw mismatch;
|
|
57
58
|
}
|
|
59
|
+
let saved;
|
|
58
60
|
try {
|
|
59
|
-
|
|
60
|
-
if (saved.credentialStore === "file") {
|
|
61
|
-
input.output.notice("Warning: native credential storage is unavailable; the CLI credential is in an owner-only (0600) file fallback.");
|
|
62
|
-
}
|
|
63
|
-
return saved;
|
|
61
|
+
saved = await input.configStore.saveProfile(input.profileName, profile, exchanged.credential);
|
|
64
62
|
}
|
|
65
63
|
catch (error) {
|
|
66
64
|
await revokeExchangedCredential(input.apiBaseUrl, exchanged.credential, profile, error);
|
|
67
65
|
throw error;
|
|
68
66
|
}
|
|
67
|
+
if (saved.credentialStore === "file") {
|
|
68
|
+
input.output.notice("Warning: native credential storage is unavailable; the CLI credential is in an owner-only (0600) file fallback.");
|
|
69
|
+
}
|
|
70
|
+
await revokeSupersededCredential(previousCredential, exchanged.credential, input.output);
|
|
71
|
+
return saved;
|
|
72
|
+
}
|
|
73
|
+
async function revokeSupersededCredential(previous, newCredential, output) {
|
|
74
|
+
if (!previous || previous.token === newCredential)
|
|
75
|
+
return;
|
|
76
|
+
if (!previous.token) {
|
|
77
|
+
output.notice(`Previous CLI credential ${previous.profile.credentialId} was not available locally and could not be automatically revoked; verify its state in the Proxy Portal.`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
await new http_client_js_1.ProxyApiClient(previous.profile.apiBaseUrl, previous.token).request("/cli/auth/logout", { method: "POST" });
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
if (error instanceof errors_js_1.CliError && error.exitCode === errors_js_1.cliExitCodes.authentication)
|
|
85
|
+
return;
|
|
86
|
+
throw new errors_js_1.CliError(`The new profile is saved, but previous credential ${previous.profile.credentialId} could not be revoked automatically; revoke it in the Proxy Portal`, "cli_previous_credential_cleanup_failed", errors_js_1.cliExitCodes.network, {
|
|
87
|
+
cleanup_error_class: error instanceof Error ? error.name : typeof error,
|
|
88
|
+
cleanup_error_code: error instanceof errors_js_1.CliError ? error.code : undefined,
|
|
89
|
+
credential_id: previous.profile.credentialId,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
69
92
|
}
|
|
70
93
|
async function revokeExchangedCredential(apiBaseUrl, credential, profile, originalError) {
|
|
71
94
|
try {
|
|
@@ -83,12 +106,13 @@ async function revokeExchangedCredential(apiBaseUrl, credential, profile, origin
|
|
|
83
106
|
}
|
|
84
107
|
}
|
|
85
108
|
async function status(input) {
|
|
86
|
-
const response = await new http_client_js_1.ProxyApiClient(input.apiBaseUrl, input.token).request("/cli/auth/status");
|
|
109
|
+
const response = await new http_client_js_1.ProxyApiClient(input.apiBaseUrl, input.token).request("/cli/auth/status", { signal: input.signal });
|
|
87
110
|
return response.profile;
|
|
88
111
|
}
|
|
89
112
|
async function logout(input) {
|
|
90
113
|
await new http_client_js_1.ProxyApiClient(input.apiBaseUrl, input.token).request("/cli/auth/logout", {
|
|
91
114
|
method: "POST",
|
|
115
|
+
signal: input.signal,
|
|
92
116
|
});
|
|
93
117
|
if (input.removeLocalProfile) {
|
|
94
118
|
await input.configStore.removeProfile(input.profileName);
|
package/dist/cjs/cli.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliConfigStore } from "./config.js";
|
|
2
2
|
import { type CliIo } from "./output.js";
|
|
3
|
-
export declare const cliVersion = "0.1.0-prx-128.
|
|
3
|
+
export declare const cliVersion = "0.1.0-prx-128.110.1";
|
|
4
4
|
export interface RunCliOptions {
|
|
5
5
|
configStore?: CliConfigStore;
|
|
6
6
|
environment?: NodeJS.ProcessEnv;
|
package/dist/cjs/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliConfigStore } from "./config.js";
|
|
2
2
|
import { type CliIo } from "./output.js";
|
|
3
|
-
export declare const cliVersion = "0.1.0-prx-128.
|
|
3
|
+
export declare const cliVersion = "0.1.0-prx-128.110.1";
|
|
4
4
|
export interface RunCliOptions {
|
|
5
5
|
configStore?: CliConfigStore;
|
|
6
6
|
environment?: NodeJS.ProcessEnv;
|
package/dist/cjs/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ const config_js_1 = require("./config.js");
|
|
|
9
9
|
const errors_js_1 = require("./errors.js");
|
|
10
10
|
const output_js_1 = require("./output.js");
|
|
11
11
|
const webhooks_js_1 = require("./webhooks.js");
|
|
12
|
-
exports.cliVersion = "0.1.0-prx-128.
|
|
12
|
+
exports.cliVersion = "0.1.0-prx-128.110.1";
|
|
13
13
|
async function runCli(argv, options = {}) {
|
|
14
14
|
const io = options.io ?? {
|
|
15
15
|
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
@@ -109,7 +109,7 @@ async function executeCommand(input) {
|
|
|
109
109
|
profileName: input.profileName,
|
|
110
110
|
});
|
|
111
111
|
if (input.command.path === "auth status") {
|
|
112
|
-
const remoteProfile = await (0, auth_js_1.status)(resolved);
|
|
112
|
+
const remoteProfile = await (0, auth_js_1.status)({ ...resolved, signal: input.signal });
|
|
113
113
|
assertRemoteTarget(remoteProfile, input);
|
|
114
114
|
input.output.result({
|
|
115
115
|
profile: {
|
|
@@ -122,54 +122,55 @@ async function executeCommand(input) {
|
|
|
122
122
|
}
|
|
123
123
|
if (input.command.path === "auth logout") {
|
|
124
124
|
if (input.expectedMerchantId || input.expectedMode) {
|
|
125
|
-
assertRemoteTarget(await (0, auth_js_1.status)(resolved), input);
|
|
125
|
+
assertRemoteTarget(await (0, auth_js_1.status)({ ...resolved, signal: input.signal }), input);
|
|
126
126
|
}
|
|
127
127
|
await (0, auth_js_1.logout)({
|
|
128
128
|
apiBaseUrl: resolved.apiBaseUrl,
|
|
129
129
|
configStore: input.configStore,
|
|
130
130
|
profileName: resolved.profileName,
|
|
131
131
|
removeLocalProfile: resolved.source !== "environment",
|
|
132
|
+
signal: input.signal,
|
|
132
133
|
token: resolved.token,
|
|
133
134
|
});
|
|
134
135
|
input.output.result({ profile: resolved.profileName, revoked: true }, `Logged out profile ${resolved.profileName}.`);
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
137
138
|
if (input.expectedMerchantId || input.expectedMode) {
|
|
138
|
-
assertRemoteTarget(await (0, auth_js_1.status)(resolved), input);
|
|
139
|
+
assertRemoteTarget(await (0, auth_js_1.status)({ ...resolved, signal: input.signal }), input);
|
|
139
140
|
}
|
|
140
141
|
const webhooks = new webhooks_js_1.ProxyWebhooksClient(resolved.apiBaseUrl, resolved.token);
|
|
141
142
|
if (input.command.path === "webhooks endpoints list") {
|
|
142
|
-
const endpoints = await webhooks.listEndpoints();
|
|
143
|
+
const endpoints = await webhooks.listEndpoints(input.signal);
|
|
143
144
|
input.output.result({ webhook_endpoints: endpoints }, formatEndpointList(endpoints));
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
146
147
|
if (input.command.path === "webhooks listeners create") {
|
|
147
|
-
const listener = await webhooks.createListener(readRequiredString(input.options, "--endpoint"));
|
|
148
|
+
const listener = await webhooks.createListener(readRequiredString(input.options, "--endpoint"), input.signal);
|
|
148
149
|
input.output.result({ listener }, `Created listener ${listener.id}; lease expires ${listener.lease_expires_at}.`);
|
|
149
150
|
return;
|
|
150
151
|
}
|
|
151
152
|
if (input.command.path === "webhooks listeners status") {
|
|
152
|
-
const listener = await webhooks.getListener(readRequiredString(input.options, "--listener"));
|
|
153
|
+
const listener = await webhooks.getListener(readRequiredString(input.options, "--listener"), input.signal);
|
|
153
154
|
input.output.result({ listener }, `Listener ${listener.id} is ${listener.status}; lease expires ${listener.lease_expires_at}.`);
|
|
154
155
|
return;
|
|
155
156
|
}
|
|
156
157
|
if (input.command.path === "webhooks listeners heartbeat") {
|
|
157
|
-
const listener = await webhooks.heartbeat(readRequiredString(input.options, "--listener"));
|
|
158
|
+
const listener = await webhooks.heartbeat(readRequiredString(input.options, "--listener"), input.signal);
|
|
158
159
|
input.output.result({ listener }, `Extended listener ${listener.id} through ${listener.lease_expires_at}.`);
|
|
159
160
|
return;
|
|
160
161
|
}
|
|
161
162
|
if (input.command.path === "webhooks listeners close") {
|
|
162
|
-
const listener = await webhooks.close(readRequiredString(input.options, "--listener"));
|
|
163
|
+
const listener = await webhooks.close(readRequiredString(input.options, "--listener"), input.signal);
|
|
163
164
|
input.output.result({ listener }, `Closed listener ${listener.id}.`);
|
|
164
165
|
return;
|
|
165
166
|
}
|
|
166
167
|
if (input.command.path === "webhooks deliveries list") {
|
|
167
|
-
const deliveries = await webhooks.listDeliveries(readRequiredString(input.options, "--listener"), readOptionalInteger(input.options, "--limit"));
|
|
168
|
+
const deliveries = await webhooks.listDeliveries(readRequiredString(input.options, "--listener"), readOptionalInteger(input.options, "--limit"), input.signal);
|
|
168
169
|
input.output.result({ deliveries }, `${deliveries.length} local webhook deliveries.`);
|
|
169
170
|
return;
|
|
170
171
|
}
|
|
171
172
|
if (input.command.path === "webhooks deliveries replay") {
|
|
172
|
-
const replay = await webhooks.replay(readRequiredString(input.options, "--listener"), readRequiredString(input.options, "--delivery"), readRequiredString(input.options, "--reason"));
|
|
173
|
+
const replay = await webhooks.replay(readRequiredString(input.options, "--listener"), readRequiredString(input.options, "--delivery"), readRequiredString(input.options, "--reason"), input.signal);
|
|
173
174
|
input.output.result(replay, "Replay scheduled.");
|
|
174
175
|
return;
|
|
175
176
|
}
|
|
@@ -191,13 +192,14 @@ async function executeCommand(input) {
|
|
|
191
192
|
return;
|
|
192
193
|
}
|
|
193
194
|
if (input.command.path === "listen") {
|
|
194
|
-
const endpoints = await webhooks.listEndpoints();
|
|
195
|
+
const endpoints = await webhooks.listEndpoints(input.signal);
|
|
195
196
|
const sourceEndpointId = await selectEndpoint({
|
|
196
197
|
endpoints,
|
|
197
198
|
io: input.io,
|
|
198
199
|
noInput: input.noInput,
|
|
199
200
|
output: input.output,
|
|
200
201
|
requested: readOptionalString(input.options, "--endpoint"),
|
|
202
|
+
signal: input.signal,
|
|
201
203
|
});
|
|
202
204
|
const maxEvents = readOptionalInteger(input.options, "--max-events") ?? 0;
|
|
203
205
|
if (maxEvents < 0)
|
|
@@ -348,12 +350,20 @@ async function selectEndpoint(input) {
|
|
|
348
350
|
output: input.io.stderr,
|
|
349
351
|
});
|
|
350
352
|
try {
|
|
351
|
-
const answer = await prompt.question("Select a source endpoint number: "
|
|
353
|
+
const answer = await prompt.question("Select a source endpoint number: ", {
|
|
354
|
+
signal: input.signal,
|
|
355
|
+
});
|
|
352
356
|
const selected = input.endpoints[Number(answer) - 1];
|
|
353
357
|
if (!selected)
|
|
354
358
|
throw (0, errors_js_1.usageError)("Invalid endpoint selection");
|
|
355
359
|
return selected.id;
|
|
356
360
|
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
if (input.signal.aborted) {
|
|
363
|
+
throw new errors_js_1.CliError("CLI command interrupted", "cli_command_interrupted", errors_js_1.cliExitCodes.interrupted);
|
|
364
|
+
}
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
357
367
|
finally {
|
|
358
368
|
prompt.close();
|
|
359
369
|
}
|
package/dist/cjs/config.d.cts
CHANGED
|
@@ -17,6 +17,10 @@ export interface ResolvedCredential {
|
|
|
17
17
|
source: "environment" | "file" | "keychain" | "secret-service";
|
|
18
18
|
token: string;
|
|
19
19
|
}
|
|
20
|
+
export interface StoredProfileCredential {
|
|
21
|
+
profile: CliProfile;
|
|
22
|
+
token?: string;
|
|
23
|
+
}
|
|
20
24
|
export interface ConfigEnvironment {
|
|
21
25
|
HOME?: string;
|
|
22
26
|
PATH?: string;
|
|
@@ -40,6 +44,7 @@ export declare class CliConfigStore {
|
|
|
40
44
|
apiBaseUrl(requested?: string, profile?: CliProfile): string;
|
|
41
45
|
readProfile(profileName: string): Promise<CliProfile | undefined>;
|
|
42
46
|
saveProfile(profileName: string, profile: Omit<CliProfile, "credentialStore">, token: string): Promise<CliProfile>;
|
|
47
|
+
readStoredCredential(profileName: string): Promise<StoredProfileCredential | undefined>;
|
|
43
48
|
resolveCredential(input: {
|
|
44
49
|
apiBaseUrl?: string;
|
|
45
50
|
profileName?: string;
|
package/dist/cjs/config.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export interface ResolvedCredential {
|
|
|
17
17
|
source: "environment" | "file" | "keychain" | "secret-service";
|
|
18
18
|
token: string;
|
|
19
19
|
}
|
|
20
|
+
export interface StoredProfileCredential {
|
|
21
|
+
profile: CliProfile;
|
|
22
|
+
token?: string;
|
|
23
|
+
}
|
|
20
24
|
export interface ConfigEnvironment {
|
|
21
25
|
HOME?: string;
|
|
22
26
|
PATH?: string;
|
|
@@ -40,6 +44,7 @@ export declare class CliConfigStore {
|
|
|
40
44
|
apiBaseUrl(requested?: string, profile?: CliProfile): string;
|
|
41
45
|
readProfile(profileName: string): Promise<CliProfile | undefined>;
|
|
42
46
|
saveProfile(profileName: string, profile: Omit<CliProfile, "credentialStore">, token: string): Promise<CliProfile>;
|
|
47
|
+
readStoredCredential(profileName: string): Promise<StoredProfileCredential | undefined>;
|
|
43
48
|
resolveCredential(input: {
|
|
44
49
|
apiBaseUrl?: string;
|
|
45
50
|
profileName?: string;
|
package/dist/cjs/config.js
CHANGED
|
@@ -96,19 +96,58 @@ class CliConfigStore {
|
|
|
96
96
|
}
|
|
97
97
|
async saveProfile(profileName, profile, token) {
|
|
98
98
|
validateToken(token);
|
|
99
|
-
const credentialStore = await this.storeToken(profileName, token);
|
|
100
99
|
const configuration = await this.readConfiguration();
|
|
100
|
+
const previousProfile = configuration.profiles[profileName];
|
|
101
|
+
const previousToken = previousProfile
|
|
102
|
+
? await this.readToken(profileName, previousProfile.credentialStore)
|
|
103
|
+
: undefined;
|
|
104
|
+
const credentialStore = await this.storeToken(profileName, token);
|
|
101
105
|
const storedProfile = { ...profile, credentialStore };
|
|
102
106
|
configuration.profiles[profileName] = storedProfile;
|
|
103
107
|
try {
|
|
104
108
|
await writePrivateJson(this.configFile, configuration);
|
|
105
109
|
}
|
|
106
110
|
catch (error) {
|
|
107
|
-
|
|
111
|
+
try {
|
|
112
|
+
if (previousProfile?.credentialStore === credentialStore && previousToken !== undefined) {
|
|
113
|
+
await this.storeToken(profileName, previousToken);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
await this.deleteToken(profileName, credentialStore);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (rollbackError) {
|
|
120
|
+
throw credentialRollbackError(error, rollbackError);
|
|
121
|
+
}
|
|
108
122
|
throw error;
|
|
109
123
|
}
|
|
124
|
+
if (previousProfile && previousProfile.credentialStore !== credentialStore) {
|
|
125
|
+
try {
|
|
126
|
+
await this.deleteToken(profileName, previousProfile.credentialStore);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
configuration.profiles[profileName] = previousProfile;
|
|
130
|
+
try {
|
|
131
|
+
await writePrivateJson(this.configFile, configuration);
|
|
132
|
+
await this.deleteToken(profileName, credentialStore);
|
|
133
|
+
}
|
|
134
|
+
catch (rollbackError) {
|
|
135
|
+
throw credentialRollbackError(error, rollbackError);
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
110
140
|
return storedProfile;
|
|
111
141
|
}
|
|
142
|
+
async readStoredCredential(profileName) {
|
|
143
|
+
const profile = await this.readProfile(profileName);
|
|
144
|
+
if (!profile)
|
|
145
|
+
return undefined;
|
|
146
|
+
const token = await this.readToken(profileName, profile.credentialStore);
|
|
147
|
+
if (token !== undefined)
|
|
148
|
+
validateToken(token);
|
|
149
|
+
return { profile, ...(token === undefined ? {} : { token }) };
|
|
150
|
+
}
|
|
112
151
|
async resolveCredential(input) {
|
|
113
152
|
const profileName = this.profileName(input.profileName);
|
|
114
153
|
const profile = await this.readProfile(profileName);
|
|
@@ -340,6 +379,12 @@ function validateToken(token) {
|
|
|
340
379
|
function credentialStoreError(store, stderr) {
|
|
341
380
|
return new errors_js_1.CliError(`${store} could not access the Proxy CLI credential`, "cli_credential_store_failed", errors_js_1.cliExitCodes.internal, { diagnostic: stderr.trim().slice(0, 200) });
|
|
342
381
|
}
|
|
382
|
+
function credentialRollbackError(originalError, rollbackError) {
|
|
383
|
+
return new errors_js_1.CliError("Proxy CLI could not restore credential storage after profile replacement failed", "cli_credential_store_rollback_failed", errors_js_1.cliExitCodes.internal, {
|
|
384
|
+
original_error_class: originalError instanceof Error ? originalError.name : typeof originalError,
|
|
385
|
+
rollback_error_class: rollbackError instanceof Error ? rollbackError.name : typeof rollbackError,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
343
388
|
function isRecord(value) {
|
|
344
389
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
345
390
|
}
|
package/dist/cjs/http-client.js
CHANGED
|
@@ -14,6 +14,7 @@ class ProxyApiClient {
|
|
|
14
14
|
}
|
|
15
15
|
async request(path, options = {}) {
|
|
16
16
|
let response;
|
|
17
|
+
let body;
|
|
17
18
|
try {
|
|
18
19
|
response = await this.fetchImpl(new URL(path, `${this.baseUrl}/`), {
|
|
19
20
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
@@ -26,13 +27,15 @@ class ProxyApiClient {
|
|
|
26
27
|
...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
|
|
27
28
|
},
|
|
28
29
|
});
|
|
30
|
+
body = await readJson(response);
|
|
29
31
|
}
|
|
30
32
|
catch (error) {
|
|
33
|
+
if (error instanceof errors_js_1.CliError)
|
|
34
|
+
throw error;
|
|
31
35
|
if (options.signal?.aborted)
|
|
32
36
|
throw commandInterrupted();
|
|
33
37
|
throw new errors_js_1.CliError("Could not reach the Proxy API", "cli_network_error", errors_js_1.cliExitCodes.network, { error_class: error instanceof Error ? error.name : typeof error });
|
|
34
38
|
}
|
|
35
|
-
const body = await readJson(response);
|
|
36
39
|
if (!response.ok) {
|
|
37
40
|
const errorBody = isRecord(body) ? body : {};
|
|
38
41
|
const code = errorBody.error?.code ?? `http_${response.status}`;
|
|
@@ -46,6 +49,7 @@ class ProxyApiClient {
|
|
|
46
49
|
}
|
|
47
50
|
async oauthRequest(path, body, options = {}) {
|
|
48
51
|
let response;
|
|
52
|
+
let responseBody;
|
|
49
53
|
try {
|
|
50
54
|
response = await this.fetchImpl(new URL(path, `${this.baseUrl}/`), {
|
|
51
55
|
body: JSON.stringify(body),
|
|
@@ -54,14 +58,17 @@ class ProxyApiClient {
|
|
|
54
58
|
redirect: "manual",
|
|
55
59
|
signal: requestSignal(options.signal),
|
|
56
60
|
});
|
|
61
|
+
responseBody = await readJson(response);
|
|
57
62
|
}
|
|
58
63
|
catch (error) {
|
|
64
|
+
if (error instanceof errors_js_1.CliError)
|
|
65
|
+
throw error;
|
|
59
66
|
if (options.signal?.aborted)
|
|
60
67
|
throw commandInterrupted();
|
|
61
68
|
throw new errors_js_1.CliError("Could not reach the Proxy authentication service", "cli_network_error", errors_js_1.cliExitCodes.network, { error_class: error instanceof Error ? error.name : typeof error });
|
|
62
69
|
}
|
|
63
70
|
return {
|
|
64
|
-
body:
|
|
71
|
+
body: responseBody,
|
|
65
72
|
ok: response.ok,
|
|
66
73
|
status: response.status,
|
|
67
74
|
};
|
package/dist/cjs/webhooks.d.cts
CHANGED
|
@@ -28,23 +28,23 @@ interface DeliveryClaim {
|
|
|
28
28
|
export declare class ProxyWebhooksClient {
|
|
29
29
|
private readonly api;
|
|
30
30
|
constructor(apiBaseUrl: string, token: string, fetchImpl?: typeof fetch);
|
|
31
|
-
listEndpoints(): Promise<WebhookEndpointRecord[]>;
|
|
32
|
-
createListener(sourceEndpointId: string): Promise<WebhookListenerRecord>;
|
|
33
|
-
getListener(listenerId: string): Promise<WebhookListenerRecord>;
|
|
34
|
-
heartbeat(listenerId: string): Promise<WebhookListenerRecord>;
|
|
35
|
-
close(listenerId: string): Promise<WebhookListenerRecord>;
|
|
36
|
-
listDeliveries(listenerId: string, limit?: number): Promise<unknown[]>;
|
|
37
|
-
replay(listenerId: string, deliveryId: string, reason: string): Promise<unknown>;
|
|
31
|
+
listEndpoints(signal?: AbortSignal): Promise<WebhookEndpointRecord[]>;
|
|
32
|
+
createListener(sourceEndpointId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
33
|
+
getListener(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
34
|
+
heartbeat(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
35
|
+
close(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
36
|
+
listDeliveries(listenerId: string, limit?: number, signal?: AbortSignal): Promise<unknown[]>;
|
|
37
|
+
replay(listenerId: string, deliveryId: string, reason: string, signal?: AbortSignal): Promise<unknown>;
|
|
38
38
|
claim(listenerId: string, selection?: {
|
|
39
39
|
deliveryId?: string;
|
|
40
40
|
replayOutboxEventId?: string;
|
|
41
|
-
}): Promise<DeliveryClaim | null>;
|
|
41
|
+
}, signal?: AbortSignal): Promise<DeliveryClaim | null>;
|
|
42
42
|
complete(attemptId: string, input: {
|
|
43
43
|
durationMs: number;
|
|
44
44
|
errorClass: "network_error" | "timeout" | null;
|
|
45
45
|
responseStatusCode: number | null;
|
|
46
46
|
}): Promise<unknown>;
|
|
47
|
-
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string): Promise<{
|
|
47
|
+
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
48
48
|
expires_at: string;
|
|
49
49
|
id: string;
|
|
50
50
|
inbound_url: string;
|
package/dist/cjs/webhooks.d.ts
CHANGED
|
@@ -28,23 +28,23 @@ interface DeliveryClaim {
|
|
|
28
28
|
export declare class ProxyWebhooksClient {
|
|
29
29
|
private readonly api;
|
|
30
30
|
constructor(apiBaseUrl: string, token: string, fetchImpl?: typeof fetch);
|
|
31
|
-
listEndpoints(): Promise<WebhookEndpointRecord[]>;
|
|
32
|
-
createListener(sourceEndpointId: string): Promise<WebhookListenerRecord>;
|
|
33
|
-
getListener(listenerId: string): Promise<WebhookListenerRecord>;
|
|
34
|
-
heartbeat(listenerId: string): Promise<WebhookListenerRecord>;
|
|
35
|
-
close(listenerId: string): Promise<WebhookListenerRecord>;
|
|
36
|
-
listDeliveries(listenerId: string, limit?: number): Promise<unknown[]>;
|
|
37
|
-
replay(listenerId: string, deliveryId: string, reason: string): Promise<unknown>;
|
|
31
|
+
listEndpoints(signal?: AbortSignal): Promise<WebhookEndpointRecord[]>;
|
|
32
|
+
createListener(sourceEndpointId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
33
|
+
getListener(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
34
|
+
heartbeat(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
35
|
+
close(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
36
|
+
listDeliveries(listenerId: string, limit?: number, signal?: AbortSignal): Promise<unknown[]>;
|
|
37
|
+
replay(listenerId: string, deliveryId: string, reason: string, signal?: AbortSignal): Promise<unknown>;
|
|
38
38
|
claim(listenerId: string, selection?: {
|
|
39
39
|
deliveryId?: string;
|
|
40
40
|
replayOutboxEventId?: string;
|
|
41
|
-
}): Promise<DeliveryClaim | null>;
|
|
41
|
+
}, signal?: AbortSignal): Promise<DeliveryClaim | null>;
|
|
42
42
|
complete(attemptId: string, input: {
|
|
43
43
|
durationMs: number;
|
|
44
44
|
errorClass: "network_error" | "timeout" | null;
|
|
45
45
|
responseStatusCode: number | null;
|
|
46
46
|
}): Promise<unknown>;
|
|
47
|
-
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string): Promise<{
|
|
47
|
+
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
48
48
|
expires_at: string;
|
|
49
49
|
id: string;
|
|
50
50
|
inbound_url: string;
|
package/dist/cjs/webhooks.js
CHANGED
|
@@ -25,32 +25,33 @@ class ProxyWebhooksClient {
|
|
|
25
25
|
constructor(apiBaseUrl, token, fetchImpl) {
|
|
26
26
|
this.api = new http_client_js_1.ProxyApiClient(apiBaseUrl, token, fetchImpl);
|
|
27
27
|
}
|
|
28
|
-
async listEndpoints() {
|
|
29
|
-
return (await this.api.request("/cli/webhook-endpoints")).webhook_endpoints;
|
|
28
|
+
async listEndpoints(signal) {
|
|
29
|
+
return (await this.api.request("/cli/webhook-endpoints", { signal })).webhook_endpoints;
|
|
30
30
|
}
|
|
31
|
-
async createListener(sourceEndpointId) {
|
|
31
|
+
async createListener(sourceEndpointId, signal) {
|
|
32
32
|
return (await this.api.request("/cli/webhook-listeners", {
|
|
33
33
|
body: { source_webhook_endpoint_id: sourceEndpointId },
|
|
34
34
|
method: "POST",
|
|
35
|
+
signal,
|
|
35
36
|
})).listener;
|
|
36
37
|
}
|
|
37
|
-
async getListener(listenerId) {
|
|
38
|
-
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}
|
|
38
|
+
async getListener(listenerId, signal) {
|
|
39
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}`, { signal })).listener;
|
|
39
40
|
}
|
|
40
|
-
async heartbeat(listenerId) {
|
|
41
|
-
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/heartbeat`, { method: "POST" })).listener;
|
|
41
|
+
async heartbeat(listenerId, signal) {
|
|
42
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/heartbeat`, { method: "POST", signal })).listener;
|
|
42
43
|
}
|
|
43
|
-
async close(listenerId) {
|
|
44
|
-
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/close`, { method: "POST" })).listener;
|
|
44
|
+
async close(listenerId, signal) {
|
|
45
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/close`, { method: "POST", signal })).listener;
|
|
45
46
|
}
|
|
46
|
-
async listDeliveries(listenerId, limit) {
|
|
47
|
-
const query = limit ? `?${new URLSearchParams({ limit: String(limit) })}` : "";
|
|
48
|
-
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/deliveries${query}
|
|
47
|
+
async listDeliveries(listenerId, limit, signal) {
|
|
48
|
+
const query = limit !== undefined ? `?${new URLSearchParams({ limit: String(limit) })}` : "";
|
|
49
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/deliveries${query}`, { signal })).deliveries;
|
|
49
50
|
}
|
|
50
|
-
async replay(listenerId, deliveryId, reason) {
|
|
51
|
-
return this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/deliveries/${encodeURIComponent(deliveryId)}/replay`, { body: { reason }, method: "POST" });
|
|
51
|
+
async replay(listenerId, deliveryId, reason, signal) {
|
|
52
|
+
return this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/deliveries/${encodeURIComponent(deliveryId)}/replay`, { body: { reason }, method: "POST", signal });
|
|
52
53
|
}
|
|
53
|
-
async claim(listenerId, selection = {}) {
|
|
54
|
+
async claim(listenerId, selection = {}, signal) {
|
|
54
55
|
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/claims`, {
|
|
55
56
|
method: "POST",
|
|
56
57
|
body: {
|
|
@@ -59,6 +60,7 @@ class ProxyWebhooksClient {
|
|
|
59
60
|
? { replay_outbox_event_id: selection.replayOutboxEventId }
|
|
60
61
|
: {}),
|
|
61
62
|
},
|
|
63
|
+
signal,
|
|
62
64
|
})).claim;
|
|
63
65
|
}
|
|
64
66
|
async complete(attemptId, input) {
|
|
@@ -71,26 +73,28 @@ class ProxyWebhooksClient {
|
|
|
71
73
|
},
|
|
72
74
|
});
|
|
73
75
|
}
|
|
74
|
-
async createStripeIngress(listenerId, webhookSecret, pspConfigId) {
|
|
76
|
+
async createStripeIngress(listenerId, webhookSecret, pspConfigId, signal) {
|
|
75
77
|
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress`, {
|
|
76
78
|
method: "POST",
|
|
77
79
|
body: {
|
|
78
80
|
...(pspConfigId ? { psp_config_id: pspConfigId } : {}),
|
|
79
81
|
webhook_secret: webhookSecret,
|
|
80
82
|
},
|
|
83
|
+
signal,
|
|
81
84
|
})).stripe_ingress;
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
exports.ProxyWebhooksClient = ProxyWebhooksClient;
|
|
85
88
|
async function runListener(input) {
|
|
86
89
|
const destination = validateLoopbackDestination(input.forwardTo);
|
|
87
|
-
const listener = await input.client.createListener(input.sourceEndpointId);
|
|
90
|
+
const listener = await input.client.createListener(input.sourceEndpointId, input.signal);
|
|
88
91
|
let stripeProcess;
|
|
89
92
|
let forwarded = 0;
|
|
90
93
|
let heartbeatFailure;
|
|
91
94
|
const heartbeatTimer = setInterval(() => {
|
|
92
|
-
void input.client.heartbeat(listener.id).catch((error) => {
|
|
93
|
-
|
|
95
|
+
void input.client.heartbeat(listener.id, input.signal).catch((error) => {
|
|
96
|
+
if (!input.signal.aborted)
|
|
97
|
+
heartbeatFailure = error;
|
|
94
98
|
});
|
|
95
99
|
}, heartbeatIntervalMs);
|
|
96
100
|
input.output.event("listener.started", { forward_to: destination.toString(), listener }, `Listening for Proxy test webhooks on ${listener.id}; forwarding to ${destination}`);
|
|
@@ -99,7 +103,7 @@ async function runListener(input) {
|
|
|
99
103
|
throw listenerInterrupted();
|
|
100
104
|
if (input.stripe) {
|
|
101
105
|
const webhookSecret = await captureStripeWebhookSecret(input.signal);
|
|
102
|
-
const ingress = await input.client.createStripeIngress(listener.id, webhookSecret, input.pspConfigId);
|
|
106
|
+
const ingress = await input.client.createStripeIngress(listener.id, webhookSecret, input.pspConfigId, input.signal);
|
|
103
107
|
stripeProcess = startStripeListener(ingress.inbound_url, input.output);
|
|
104
108
|
input.output.event("stripe.started", {
|
|
105
109
|
expires_at: ingress.expires_at,
|
|
@@ -116,7 +120,7 @@ async function runListener(input) {
|
|
|
116
120
|
if (stripeProcess && (stripeProcess.exitCode !== null || stripeProcess.signalCode)) {
|
|
117
121
|
throw new errors_js_1.CliError("Stripe CLI listener exited unexpectedly", "stripe_cli_exited", errors_js_1.cliExitCodes.dependency, { exit_code: stripeProcess.exitCode, signal: stripeProcess.signalCode });
|
|
118
122
|
}
|
|
119
|
-
const claim = await input.client.claim(listener.id);
|
|
123
|
+
const claim = await input.client.claim(listener.id, {}, input.signal);
|
|
120
124
|
if (!claim) {
|
|
121
125
|
await abortableDelay(claimPollIntervalMs, input.signal);
|
|
122
126
|
continue;
|
|
@@ -160,7 +164,7 @@ async function forwardSelectedDelivery(input) {
|
|
|
160
164
|
const claim = await input.client.claim(input.listenerId, {
|
|
161
165
|
...(input.deliveryId ? { deliveryId: input.deliveryId } : {}),
|
|
162
166
|
...(input.replayOutboxEventId ? { replayOutboxEventId: input.replayOutboxEventId } : {}),
|
|
163
|
-
});
|
|
167
|
+
}, input.signal);
|
|
164
168
|
if (!claim) {
|
|
165
169
|
throw new errors_js_1.CliError("Selected delivery or replay is not currently claimable", "cli_delivery_not_claimable", errors_js_1.cliExitCodes.conflict);
|
|
166
170
|
}
|
package/dist/esm/auth.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export declare function login(input: {
|
|
|
26
26
|
}): Promise<CliProfile>;
|
|
27
27
|
export declare function status(input: {
|
|
28
28
|
apiBaseUrl: string;
|
|
29
|
+
signal?: AbortSignal;
|
|
29
30
|
token: string;
|
|
30
31
|
}): Promise<CredentialExchangeResponse["profile"]>;
|
|
31
32
|
export declare function logout(input: {
|
|
@@ -33,6 +34,7 @@ export declare function logout(input: {
|
|
|
33
34
|
configStore: CliConfigStore;
|
|
34
35
|
profileName: string;
|
|
35
36
|
removeLocalProfile: boolean;
|
|
37
|
+
signal?: AbortSignal;
|
|
36
38
|
token: string;
|
|
37
39
|
}): Promise<void>;
|
|
38
40
|
export {};
|
package/dist/esm/auth.js
CHANGED
|
@@ -7,6 +7,7 @@ const deviceScope = "proxy.cli.local-webhook-development";
|
|
|
7
7
|
const deviceGrantType = "urn:ietf:params:oauth:grant-type:device_code";
|
|
8
8
|
export async function login(input) {
|
|
9
9
|
const signal = input.signal ?? new AbortController().signal;
|
|
10
|
+
const previousCredential = await input.configStore.readStoredCredential(input.profileName);
|
|
10
11
|
const client = new ProxyApiClient(input.apiBaseUrl);
|
|
11
12
|
const codeResult = await client.oauthRequest("/api/auth/device/code", {
|
|
12
13
|
client_id: clientId,
|
|
@@ -50,17 +51,39 @@ export async function login(input) {
|
|
|
50
51
|
await revokeExchangedCredential(input.apiBaseUrl, exchanged.credential, profile, mismatch);
|
|
51
52
|
throw mismatch;
|
|
52
53
|
}
|
|
54
|
+
let saved;
|
|
53
55
|
try {
|
|
54
|
-
|
|
55
|
-
if (saved.credentialStore === "file") {
|
|
56
|
-
input.output.notice("Warning: native credential storage is unavailable; the CLI credential is in an owner-only (0600) file fallback.");
|
|
57
|
-
}
|
|
58
|
-
return saved;
|
|
56
|
+
saved = await input.configStore.saveProfile(input.profileName, profile, exchanged.credential);
|
|
59
57
|
}
|
|
60
58
|
catch (error) {
|
|
61
59
|
await revokeExchangedCredential(input.apiBaseUrl, exchanged.credential, profile, error);
|
|
62
60
|
throw error;
|
|
63
61
|
}
|
|
62
|
+
if (saved.credentialStore === "file") {
|
|
63
|
+
input.output.notice("Warning: native credential storage is unavailable; the CLI credential is in an owner-only (0600) file fallback.");
|
|
64
|
+
}
|
|
65
|
+
await revokeSupersededCredential(previousCredential, exchanged.credential, input.output);
|
|
66
|
+
return saved;
|
|
67
|
+
}
|
|
68
|
+
async function revokeSupersededCredential(previous, newCredential, output) {
|
|
69
|
+
if (!previous || previous.token === newCredential)
|
|
70
|
+
return;
|
|
71
|
+
if (!previous.token) {
|
|
72
|
+
output.notice(`Previous CLI credential ${previous.profile.credentialId} was not available locally and could not be automatically revoked; verify its state in the Proxy Portal.`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
await new ProxyApiClient(previous.profile.apiBaseUrl, previous.token).request("/cli/auth/logout", { method: "POST" });
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof CliError && error.exitCode === cliExitCodes.authentication)
|
|
80
|
+
return;
|
|
81
|
+
throw new CliError(`The new profile is saved, but previous credential ${previous.profile.credentialId} could not be revoked automatically; revoke it in the Proxy Portal`, "cli_previous_credential_cleanup_failed", cliExitCodes.network, {
|
|
82
|
+
cleanup_error_class: error instanceof Error ? error.name : typeof error,
|
|
83
|
+
cleanup_error_code: error instanceof CliError ? error.code : undefined,
|
|
84
|
+
credential_id: previous.profile.credentialId,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
64
87
|
}
|
|
65
88
|
async function revokeExchangedCredential(apiBaseUrl, credential, profile, originalError) {
|
|
66
89
|
try {
|
|
@@ -78,12 +101,13 @@ async function revokeExchangedCredential(apiBaseUrl, credential, profile, origin
|
|
|
78
101
|
}
|
|
79
102
|
}
|
|
80
103
|
export async function status(input) {
|
|
81
|
-
const response = await new ProxyApiClient(input.apiBaseUrl, input.token).request("/cli/auth/status");
|
|
104
|
+
const response = await new ProxyApiClient(input.apiBaseUrl, input.token).request("/cli/auth/status", { signal: input.signal });
|
|
82
105
|
return response.profile;
|
|
83
106
|
}
|
|
84
107
|
export async function logout(input) {
|
|
85
108
|
await new ProxyApiClient(input.apiBaseUrl, input.token).request("/cli/auth/logout", {
|
|
86
109
|
method: "POST",
|
|
110
|
+
signal: input.signal,
|
|
87
111
|
});
|
|
88
112
|
if (input.removeLocalProfile) {
|
|
89
113
|
await input.configStore.removeProfile(input.profileName);
|
package/dist/esm/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliConfigStore } from "./config.js";
|
|
2
2
|
import { type CliIo } from "./output.js";
|
|
3
|
-
export declare const cliVersion = "0.1.0-prx-128.
|
|
3
|
+
export declare const cliVersion = "0.1.0-prx-128.110.1";
|
|
4
4
|
export interface RunCliOptions {
|
|
5
5
|
configStore?: CliConfigStore;
|
|
6
6
|
environment?: NodeJS.ProcessEnv;
|
package/dist/esm/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { CliConfigStore } from "./config.js";
|
|
|
5
5
|
import { asCliError, CliError, cliExitCodes, usageError } from "./errors.js";
|
|
6
6
|
import { CliOutput } from "./output.js";
|
|
7
7
|
import { forwardSelectedDelivery, ProxyWebhooksClient, runListener, } from "./webhooks.js";
|
|
8
|
-
export const cliVersion = "0.1.0-prx-128.
|
|
8
|
+
export const cliVersion = "0.1.0-prx-128.110.1";
|
|
9
9
|
export async function runCli(argv, options = {}) {
|
|
10
10
|
const io = options.io ?? {
|
|
11
11
|
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
@@ -105,7 +105,7 @@ async function executeCommand(input) {
|
|
|
105
105
|
profileName: input.profileName,
|
|
106
106
|
});
|
|
107
107
|
if (input.command.path === "auth status") {
|
|
108
|
-
const remoteProfile = await status(resolved);
|
|
108
|
+
const remoteProfile = await status({ ...resolved, signal: input.signal });
|
|
109
109
|
assertRemoteTarget(remoteProfile, input);
|
|
110
110
|
input.output.result({
|
|
111
111
|
profile: {
|
|
@@ -118,54 +118,55 @@ async function executeCommand(input) {
|
|
|
118
118
|
}
|
|
119
119
|
if (input.command.path === "auth logout") {
|
|
120
120
|
if (input.expectedMerchantId || input.expectedMode) {
|
|
121
|
-
assertRemoteTarget(await status(resolved), input);
|
|
121
|
+
assertRemoteTarget(await status({ ...resolved, signal: input.signal }), input);
|
|
122
122
|
}
|
|
123
123
|
await logout({
|
|
124
124
|
apiBaseUrl: resolved.apiBaseUrl,
|
|
125
125
|
configStore: input.configStore,
|
|
126
126
|
profileName: resolved.profileName,
|
|
127
127
|
removeLocalProfile: resolved.source !== "environment",
|
|
128
|
+
signal: input.signal,
|
|
128
129
|
token: resolved.token,
|
|
129
130
|
});
|
|
130
131
|
input.output.result({ profile: resolved.profileName, revoked: true }, `Logged out profile ${resolved.profileName}.`);
|
|
131
132
|
return;
|
|
132
133
|
}
|
|
133
134
|
if (input.expectedMerchantId || input.expectedMode) {
|
|
134
|
-
assertRemoteTarget(await status(resolved), input);
|
|
135
|
+
assertRemoteTarget(await status({ ...resolved, signal: input.signal }), input);
|
|
135
136
|
}
|
|
136
137
|
const webhooks = new ProxyWebhooksClient(resolved.apiBaseUrl, resolved.token);
|
|
137
138
|
if (input.command.path === "webhooks endpoints list") {
|
|
138
|
-
const endpoints = await webhooks.listEndpoints();
|
|
139
|
+
const endpoints = await webhooks.listEndpoints(input.signal);
|
|
139
140
|
input.output.result({ webhook_endpoints: endpoints }, formatEndpointList(endpoints));
|
|
140
141
|
return;
|
|
141
142
|
}
|
|
142
143
|
if (input.command.path === "webhooks listeners create") {
|
|
143
|
-
const listener = await webhooks.createListener(readRequiredString(input.options, "--endpoint"));
|
|
144
|
+
const listener = await webhooks.createListener(readRequiredString(input.options, "--endpoint"), input.signal);
|
|
144
145
|
input.output.result({ listener }, `Created listener ${listener.id}; lease expires ${listener.lease_expires_at}.`);
|
|
145
146
|
return;
|
|
146
147
|
}
|
|
147
148
|
if (input.command.path === "webhooks listeners status") {
|
|
148
|
-
const listener = await webhooks.getListener(readRequiredString(input.options, "--listener"));
|
|
149
|
+
const listener = await webhooks.getListener(readRequiredString(input.options, "--listener"), input.signal);
|
|
149
150
|
input.output.result({ listener }, `Listener ${listener.id} is ${listener.status}; lease expires ${listener.lease_expires_at}.`);
|
|
150
151
|
return;
|
|
151
152
|
}
|
|
152
153
|
if (input.command.path === "webhooks listeners heartbeat") {
|
|
153
|
-
const listener = await webhooks.heartbeat(readRequiredString(input.options, "--listener"));
|
|
154
|
+
const listener = await webhooks.heartbeat(readRequiredString(input.options, "--listener"), input.signal);
|
|
154
155
|
input.output.result({ listener }, `Extended listener ${listener.id} through ${listener.lease_expires_at}.`);
|
|
155
156
|
return;
|
|
156
157
|
}
|
|
157
158
|
if (input.command.path === "webhooks listeners close") {
|
|
158
|
-
const listener = await webhooks.close(readRequiredString(input.options, "--listener"));
|
|
159
|
+
const listener = await webhooks.close(readRequiredString(input.options, "--listener"), input.signal);
|
|
159
160
|
input.output.result({ listener }, `Closed listener ${listener.id}.`);
|
|
160
161
|
return;
|
|
161
162
|
}
|
|
162
163
|
if (input.command.path === "webhooks deliveries list") {
|
|
163
|
-
const deliveries = await webhooks.listDeliveries(readRequiredString(input.options, "--listener"), readOptionalInteger(input.options, "--limit"));
|
|
164
|
+
const deliveries = await webhooks.listDeliveries(readRequiredString(input.options, "--listener"), readOptionalInteger(input.options, "--limit"), input.signal);
|
|
164
165
|
input.output.result({ deliveries }, `${deliveries.length} local webhook deliveries.`);
|
|
165
166
|
return;
|
|
166
167
|
}
|
|
167
168
|
if (input.command.path === "webhooks deliveries replay") {
|
|
168
|
-
const replay = await webhooks.replay(readRequiredString(input.options, "--listener"), readRequiredString(input.options, "--delivery"), readRequiredString(input.options, "--reason"));
|
|
169
|
+
const replay = await webhooks.replay(readRequiredString(input.options, "--listener"), readRequiredString(input.options, "--delivery"), readRequiredString(input.options, "--reason"), input.signal);
|
|
169
170
|
input.output.result(replay, "Replay scheduled.");
|
|
170
171
|
return;
|
|
171
172
|
}
|
|
@@ -187,13 +188,14 @@ async function executeCommand(input) {
|
|
|
187
188
|
return;
|
|
188
189
|
}
|
|
189
190
|
if (input.command.path === "listen") {
|
|
190
|
-
const endpoints = await webhooks.listEndpoints();
|
|
191
|
+
const endpoints = await webhooks.listEndpoints(input.signal);
|
|
191
192
|
const sourceEndpointId = await selectEndpoint({
|
|
192
193
|
endpoints,
|
|
193
194
|
io: input.io,
|
|
194
195
|
noInput: input.noInput,
|
|
195
196
|
output: input.output,
|
|
196
197
|
requested: readOptionalString(input.options, "--endpoint"),
|
|
198
|
+
signal: input.signal,
|
|
197
199
|
});
|
|
198
200
|
const maxEvents = readOptionalInteger(input.options, "--max-events") ?? 0;
|
|
199
201
|
if (maxEvents < 0)
|
|
@@ -344,12 +346,20 @@ async function selectEndpoint(input) {
|
|
|
344
346
|
output: input.io.stderr,
|
|
345
347
|
});
|
|
346
348
|
try {
|
|
347
|
-
const answer = await prompt.question("Select a source endpoint number: "
|
|
349
|
+
const answer = await prompt.question("Select a source endpoint number: ", {
|
|
350
|
+
signal: input.signal,
|
|
351
|
+
});
|
|
348
352
|
const selected = input.endpoints[Number(answer) - 1];
|
|
349
353
|
if (!selected)
|
|
350
354
|
throw usageError("Invalid endpoint selection");
|
|
351
355
|
return selected.id;
|
|
352
356
|
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (input.signal.aborted) {
|
|
359
|
+
throw new CliError("CLI command interrupted", "cli_command_interrupted", cliExitCodes.interrupted);
|
|
360
|
+
}
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
353
363
|
finally {
|
|
354
364
|
prompt.close();
|
|
355
365
|
}
|
package/dist/esm/config.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export interface ResolvedCredential {
|
|
|
17
17
|
source: "environment" | "file" | "keychain" | "secret-service";
|
|
18
18
|
token: string;
|
|
19
19
|
}
|
|
20
|
+
export interface StoredProfileCredential {
|
|
21
|
+
profile: CliProfile;
|
|
22
|
+
token?: string;
|
|
23
|
+
}
|
|
20
24
|
export interface ConfigEnvironment {
|
|
21
25
|
HOME?: string;
|
|
22
26
|
PATH?: string;
|
|
@@ -40,6 +44,7 @@ export declare class CliConfigStore {
|
|
|
40
44
|
apiBaseUrl(requested?: string, profile?: CliProfile): string;
|
|
41
45
|
readProfile(profileName: string): Promise<CliProfile | undefined>;
|
|
42
46
|
saveProfile(profileName: string, profile: Omit<CliProfile, "credentialStore">, token: string): Promise<CliProfile>;
|
|
47
|
+
readStoredCredential(profileName: string): Promise<StoredProfileCredential | undefined>;
|
|
43
48
|
resolveCredential(input: {
|
|
44
49
|
apiBaseUrl?: string;
|
|
45
50
|
profileName?: string;
|
package/dist/esm/config.js
CHANGED
|
@@ -91,19 +91,58 @@ export class CliConfigStore {
|
|
|
91
91
|
}
|
|
92
92
|
async saveProfile(profileName, profile, token) {
|
|
93
93
|
validateToken(token);
|
|
94
|
-
const credentialStore = await this.storeToken(profileName, token);
|
|
95
94
|
const configuration = await this.readConfiguration();
|
|
95
|
+
const previousProfile = configuration.profiles[profileName];
|
|
96
|
+
const previousToken = previousProfile
|
|
97
|
+
? await this.readToken(profileName, previousProfile.credentialStore)
|
|
98
|
+
: undefined;
|
|
99
|
+
const credentialStore = await this.storeToken(profileName, token);
|
|
96
100
|
const storedProfile = { ...profile, credentialStore };
|
|
97
101
|
configuration.profiles[profileName] = storedProfile;
|
|
98
102
|
try {
|
|
99
103
|
await writePrivateJson(this.configFile, configuration);
|
|
100
104
|
}
|
|
101
105
|
catch (error) {
|
|
102
|
-
|
|
106
|
+
try {
|
|
107
|
+
if (previousProfile?.credentialStore === credentialStore && previousToken !== undefined) {
|
|
108
|
+
await this.storeToken(profileName, previousToken);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
await this.deleteToken(profileName, credentialStore);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (rollbackError) {
|
|
115
|
+
throw credentialRollbackError(error, rollbackError);
|
|
116
|
+
}
|
|
103
117
|
throw error;
|
|
104
118
|
}
|
|
119
|
+
if (previousProfile && previousProfile.credentialStore !== credentialStore) {
|
|
120
|
+
try {
|
|
121
|
+
await this.deleteToken(profileName, previousProfile.credentialStore);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
configuration.profiles[profileName] = previousProfile;
|
|
125
|
+
try {
|
|
126
|
+
await writePrivateJson(this.configFile, configuration);
|
|
127
|
+
await this.deleteToken(profileName, credentialStore);
|
|
128
|
+
}
|
|
129
|
+
catch (rollbackError) {
|
|
130
|
+
throw credentialRollbackError(error, rollbackError);
|
|
131
|
+
}
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
105
135
|
return storedProfile;
|
|
106
136
|
}
|
|
137
|
+
async readStoredCredential(profileName) {
|
|
138
|
+
const profile = await this.readProfile(profileName);
|
|
139
|
+
if (!profile)
|
|
140
|
+
return undefined;
|
|
141
|
+
const token = await this.readToken(profileName, profile.credentialStore);
|
|
142
|
+
if (token !== undefined)
|
|
143
|
+
validateToken(token);
|
|
144
|
+
return { profile, ...(token === undefined ? {} : { token }) };
|
|
145
|
+
}
|
|
107
146
|
async resolveCredential(input) {
|
|
108
147
|
const profileName = this.profileName(input.profileName);
|
|
109
148
|
const profile = await this.readProfile(profileName);
|
|
@@ -334,6 +373,12 @@ function validateToken(token) {
|
|
|
334
373
|
function credentialStoreError(store, stderr) {
|
|
335
374
|
return new CliError(`${store} could not access the Proxy CLI credential`, "cli_credential_store_failed", cliExitCodes.internal, { diagnostic: stderr.trim().slice(0, 200) });
|
|
336
375
|
}
|
|
376
|
+
function credentialRollbackError(originalError, rollbackError) {
|
|
377
|
+
return new CliError("Proxy CLI could not restore credential storage after profile replacement failed", "cli_credential_store_rollback_failed", cliExitCodes.internal, {
|
|
378
|
+
original_error_class: originalError instanceof Error ? originalError.name : typeof originalError,
|
|
379
|
+
rollback_error_class: rollbackError instanceof Error ? rollbackError.name : typeof rollbackError,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
337
382
|
function isRecord(value) {
|
|
338
383
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
339
384
|
}
|
package/dist/esm/http-client.js
CHANGED
|
@@ -11,6 +11,7 @@ export class ProxyApiClient {
|
|
|
11
11
|
}
|
|
12
12
|
async request(path, options = {}) {
|
|
13
13
|
let response;
|
|
14
|
+
let body;
|
|
14
15
|
try {
|
|
15
16
|
response = await this.fetchImpl(new URL(path, `${this.baseUrl}/`), {
|
|
16
17
|
body: options.body === undefined ? undefined : JSON.stringify(options.body),
|
|
@@ -23,13 +24,15 @@ export class ProxyApiClient {
|
|
|
23
24
|
...(this.token ? { authorization: `Bearer ${this.token}` } : {}),
|
|
24
25
|
},
|
|
25
26
|
});
|
|
27
|
+
body = await readJson(response);
|
|
26
28
|
}
|
|
27
29
|
catch (error) {
|
|
30
|
+
if (error instanceof CliError)
|
|
31
|
+
throw error;
|
|
28
32
|
if (options.signal?.aborted)
|
|
29
33
|
throw commandInterrupted();
|
|
30
34
|
throw new CliError("Could not reach the Proxy API", "cli_network_error", cliExitCodes.network, { error_class: error instanceof Error ? error.name : typeof error });
|
|
31
35
|
}
|
|
32
|
-
const body = await readJson(response);
|
|
33
36
|
if (!response.ok) {
|
|
34
37
|
const errorBody = isRecord(body) ? body : {};
|
|
35
38
|
const code = errorBody.error?.code ?? `http_${response.status}`;
|
|
@@ -43,6 +46,7 @@ export class ProxyApiClient {
|
|
|
43
46
|
}
|
|
44
47
|
async oauthRequest(path, body, options = {}) {
|
|
45
48
|
let response;
|
|
49
|
+
let responseBody;
|
|
46
50
|
try {
|
|
47
51
|
response = await this.fetchImpl(new URL(path, `${this.baseUrl}/`), {
|
|
48
52
|
body: JSON.stringify(body),
|
|
@@ -51,14 +55,17 @@ export class ProxyApiClient {
|
|
|
51
55
|
redirect: "manual",
|
|
52
56
|
signal: requestSignal(options.signal),
|
|
53
57
|
});
|
|
58
|
+
responseBody = await readJson(response);
|
|
54
59
|
}
|
|
55
60
|
catch (error) {
|
|
61
|
+
if (error instanceof CliError)
|
|
62
|
+
throw error;
|
|
56
63
|
if (options.signal?.aborted)
|
|
57
64
|
throw commandInterrupted();
|
|
58
65
|
throw new CliError("Could not reach the Proxy authentication service", "cli_network_error", cliExitCodes.network, { error_class: error instanceof Error ? error.name : typeof error });
|
|
59
66
|
}
|
|
60
67
|
return {
|
|
61
|
-
body:
|
|
68
|
+
body: responseBody,
|
|
62
69
|
ok: response.ok,
|
|
63
70
|
status: response.status,
|
|
64
71
|
};
|
package/dist/esm/webhooks.d.ts
CHANGED
|
@@ -28,23 +28,23 @@ interface DeliveryClaim {
|
|
|
28
28
|
export declare class ProxyWebhooksClient {
|
|
29
29
|
private readonly api;
|
|
30
30
|
constructor(apiBaseUrl: string, token: string, fetchImpl?: typeof fetch);
|
|
31
|
-
listEndpoints(): Promise<WebhookEndpointRecord[]>;
|
|
32
|
-
createListener(sourceEndpointId: string): Promise<WebhookListenerRecord>;
|
|
33
|
-
getListener(listenerId: string): Promise<WebhookListenerRecord>;
|
|
34
|
-
heartbeat(listenerId: string): Promise<WebhookListenerRecord>;
|
|
35
|
-
close(listenerId: string): Promise<WebhookListenerRecord>;
|
|
36
|
-
listDeliveries(listenerId: string, limit?: number): Promise<unknown[]>;
|
|
37
|
-
replay(listenerId: string, deliveryId: string, reason: string): Promise<unknown>;
|
|
31
|
+
listEndpoints(signal?: AbortSignal): Promise<WebhookEndpointRecord[]>;
|
|
32
|
+
createListener(sourceEndpointId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
33
|
+
getListener(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
34
|
+
heartbeat(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
35
|
+
close(listenerId: string, signal?: AbortSignal): Promise<WebhookListenerRecord>;
|
|
36
|
+
listDeliveries(listenerId: string, limit?: number, signal?: AbortSignal): Promise<unknown[]>;
|
|
37
|
+
replay(listenerId: string, deliveryId: string, reason: string, signal?: AbortSignal): Promise<unknown>;
|
|
38
38
|
claim(listenerId: string, selection?: {
|
|
39
39
|
deliveryId?: string;
|
|
40
40
|
replayOutboxEventId?: string;
|
|
41
|
-
}): Promise<DeliveryClaim | null>;
|
|
41
|
+
}, signal?: AbortSignal): Promise<DeliveryClaim | null>;
|
|
42
42
|
complete(attemptId: string, input: {
|
|
43
43
|
durationMs: number;
|
|
44
44
|
errorClass: "network_error" | "timeout" | null;
|
|
45
45
|
responseStatusCode: number | null;
|
|
46
46
|
}): Promise<unknown>;
|
|
47
|
-
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string): Promise<{
|
|
47
|
+
createStripeIngress(listenerId: string, webhookSecret: string, pspConfigId?: string, signal?: AbortSignal): Promise<{
|
|
48
48
|
expires_at: string;
|
|
49
49
|
id: string;
|
|
50
50
|
inbound_url: string;
|
package/dist/esm/webhooks.js
CHANGED
|
@@ -20,32 +20,33 @@ export class ProxyWebhooksClient {
|
|
|
20
20
|
constructor(apiBaseUrl, token, fetchImpl) {
|
|
21
21
|
this.api = new ProxyApiClient(apiBaseUrl, token, fetchImpl);
|
|
22
22
|
}
|
|
23
|
-
async listEndpoints() {
|
|
24
|
-
return (await this.api.request("/cli/webhook-endpoints")).webhook_endpoints;
|
|
23
|
+
async listEndpoints(signal) {
|
|
24
|
+
return (await this.api.request("/cli/webhook-endpoints", { signal })).webhook_endpoints;
|
|
25
25
|
}
|
|
26
|
-
async createListener(sourceEndpointId) {
|
|
26
|
+
async createListener(sourceEndpointId, signal) {
|
|
27
27
|
return (await this.api.request("/cli/webhook-listeners", {
|
|
28
28
|
body: { source_webhook_endpoint_id: sourceEndpointId },
|
|
29
29
|
method: "POST",
|
|
30
|
+
signal,
|
|
30
31
|
})).listener;
|
|
31
32
|
}
|
|
32
|
-
async getListener(listenerId) {
|
|
33
|
-
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}
|
|
33
|
+
async getListener(listenerId, signal) {
|
|
34
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}`, { signal })).listener;
|
|
34
35
|
}
|
|
35
|
-
async heartbeat(listenerId) {
|
|
36
|
-
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/heartbeat`, { method: "POST" })).listener;
|
|
36
|
+
async heartbeat(listenerId, signal) {
|
|
37
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/heartbeat`, { method: "POST", signal })).listener;
|
|
37
38
|
}
|
|
38
|
-
async close(listenerId) {
|
|
39
|
-
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/close`, { method: "POST" })).listener;
|
|
39
|
+
async close(listenerId, signal) {
|
|
40
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/close`, { method: "POST", signal })).listener;
|
|
40
41
|
}
|
|
41
|
-
async listDeliveries(listenerId, limit) {
|
|
42
|
-
const query = limit ? `?${new URLSearchParams({ limit: String(limit) })}` : "";
|
|
43
|
-
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/deliveries${query}
|
|
42
|
+
async listDeliveries(listenerId, limit, signal) {
|
|
43
|
+
const query = limit !== undefined ? `?${new URLSearchParams({ limit: String(limit) })}` : "";
|
|
44
|
+
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/deliveries${query}`, { signal })).deliveries;
|
|
44
45
|
}
|
|
45
|
-
async replay(listenerId, deliveryId, reason) {
|
|
46
|
-
return this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/deliveries/${encodeURIComponent(deliveryId)}/replay`, { body: { reason }, method: "POST" });
|
|
46
|
+
async replay(listenerId, deliveryId, reason, signal) {
|
|
47
|
+
return this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/deliveries/${encodeURIComponent(deliveryId)}/replay`, { body: { reason }, method: "POST", signal });
|
|
47
48
|
}
|
|
48
|
-
async claim(listenerId, selection = {}) {
|
|
49
|
+
async claim(listenerId, selection = {}, signal) {
|
|
49
50
|
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/claims`, {
|
|
50
51
|
method: "POST",
|
|
51
52
|
body: {
|
|
@@ -54,6 +55,7 @@ export class ProxyWebhooksClient {
|
|
|
54
55
|
? { replay_outbox_event_id: selection.replayOutboxEventId }
|
|
55
56
|
: {}),
|
|
56
57
|
},
|
|
58
|
+
signal,
|
|
57
59
|
})).claim;
|
|
58
60
|
}
|
|
59
61
|
async complete(attemptId, input) {
|
|
@@ -66,25 +68,27 @@ export class ProxyWebhooksClient {
|
|
|
66
68
|
},
|
|
67
69
|
});
|
|
68
70
|
}
|
|
69
|
-
async createStripeIngress(listenerId, webhookSecret, pspConfigId) {
|
|
71
|
+
async createStripeIngress(listenerId, webhookSecret, pspConfigId, signal) {
|
|
70
72
|
return (await this.api.request(`/cli/webhook-listeners/${encodeURIComponent(listenerId)}/stripe-ingress`, {
|
|
71
73
|
method: "POST",
|
|
72
74
|
body: {
|
|
73
75
|
...(pspConfigId ? { psp_config_id: pspConfigId } : {}),
|
|
74
76
|
webhook_secret: webhookSecret,
|
|
75
77
|
},
|
|
78
|
+
signal,
|
|
76
79
|
})).stripe_ingress;
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
export async function runListener(input) {
|
|
80
83
|
const destination = validateLoopbackDestination(input.forwardTo);
|
|
81
|
-
const listener = await input.client.createListener(input.sourceEndpointId);
|
|
84
|
+
const listener = await input.client.createListener(input.sourceEndpointId, input.signal);
|
|
82
85
|
let stripeProcess;
|
|
83
86
|
let forwarded = 0;
|
|
84
87
|
let heartbeatFailure;
|
|
85
88
|
const heartbeatTimer = setInterval(() => {
|
|
86
|
-
void input.client.heartbeat(listener.id).catch((error) => {
|
|
87
|
-
|
|
89
|
+
void input.client.heartbeat(listener.id, input.signal).catch((error) => {
|
|
90
|
+
if (!input.signal.aborted)
|
|
91
|
+
heartbeatFailure = error;
|
|
88
92
|
});
|
|
89
93
|
}, heartbeatIntervalMs);
|
|
90
94
|
input.output.event("listener.started", { forward_to: destination.toString(), listener }, `Listening for Proxy test webhooks on ${listener.id}; forwarding to ${destination}`);
|
|
@@ -93,7 +97,7 @@ export async function runListener(input) {
|
|
|
93
97
|
throw listenerInterrupted();
|
|
94
98
|
if (input.stripe) {
|
|
95
99
|
const webhookSecret = await captureStripeWebhookSecret(input.signal);
|
|
96
|
-
const ingress = await input.client.createStripeIngress(listener.id, webhookSecret, input.pspConfigId);
|
|
100
|
+
const ingress = await input.client.createStripeIngress(listener.id, webhookSecret, input.pspConfigId, input.signal);
|
|
97
101
|
stripeProcess = startStripeListener(ingress.inbound_url, input.output);
|
|
98
102
|
input.output.event("stripe.started", {
|
|
99
103
|
expires_at: ingress.expires_at,
|
|
@@ -110,7 +114,7 @@ export async function runListener(input) {
|
|
|
110
114
|
if (stripeProcess && (stripeProcess.exitCode !== null || stripeProcess.signalCode)) {
|
|
111
115
|
throw new CliError("Stripe CLI listener exited unexpectedly", "stripe_cli_exited", cliExitCodes.dependency, { exit_code: stripeProcess.exitCode, signal: stripeProcess.signalCode });
|
|
112
116
|
}
|
|
113
|
-
const claim = await input.client.claim(listener.id);
|
|
117
|
+
const claim = await input.client.claim(listener.id, {}, input.signal);
|
|
114
118
|
if (!claim) {
|
|
115
119
|
await abortableDelay(claimPollIntervalMs, input.signal);
|
|
116
120
|
continue;
|
|
@@ -154,7 +158,7 @@ export async function forwardSelectedDelivery(input) {
|
|
|
154
158
|
const claim = await input.client.claim(input.listenerId, {
|
|
155
159
|
...(input.deliveryId ? { deliveryId: input.deliveryId } : {}),
|
|
156
160
|
...(input.replayOutboxEventId ? { replayOutboxEventId: input.replayOutboxEventId } : {}),
|
|
157
|
-
});
|
|
161
|
+
}, input.signal);
|
|
158
162
|
if (!claim) {
|
|
159
163
|
throw new CliError("Selected delivery or replay is not currently claimable", "cli_delivery_not_claimable", cliExitCodes.conflict);
|
|
160
164
|
}
|