@whooshbang/sdk 1.0.0-rc.14 → 1.0.0-rc.18
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/README.md +52 -3
- package/dist/cli/arguments.d.ts +22 -0
- package/dist/cli/arguments.js +59 -0
- package/dist/cli/arguments.js.map +1 -0
- package/dist/cli/commands.d.ts +14 -0
- package/dist/cli/commands.js +257 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/config.d.ts +45 -0
- package/dist/cli/config.js +85 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/main.d.ts +5 -0
- package/dist/cli/main.js +142 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/oauth.d.ts +93 -0
- package/dist/cli/oauth.js +225 -0
- package/dist/cli/oauth.js.map +1 -0
- package/dist/cli/output.d.ts +19 -0
- package/dist/cli/output.js +39 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/session.d.ts +29 -0
- package/dist/cli/session.js +161 -0
- package/dist/cli/session.js.map +1 -0
- package/dist/client.d.ts +108 -1
- package/dist/client.js +266 -1
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -78,6 +78,54 @@ both the previous and the current secret so a rotation overlap has no gap. Use
|
|
|
78
78
|
`verifyWhooshBangWebhook` (or `verifyWebhookSignature`) for signature-only
|
|
79
79
|
verification of any signed body.
|
|
80
80
|
|
|
81
|
+
## Channel connections
|
|
82
|
+
|
|
83
|
+
`listConnectionProviders` reports what a project can connect to, the setup
|
|
84
|
+
modes each provider supports, and the WhooshBang-owned identities an
|
|
85
|
+
environment may select. `listChannelConnections`, `createChannelConnection`,
|
|
86
|
+
`getChannelConnection`, `updateChannelConnection`, and
|
|
87
|
+
`archiveChannelConnection` cover the per-environment lifecycle. Credential
|
|
88
|
+
material, and any reference to it, is structurally absent from every one of
|
|
89
|
+
these results — including for the shared identities WhooshBang operates.
|
|
90
|
+
|
|
91
|
+
Create takes an explicit idempotency key and returns a `pending` connection.
|
|
92
|
+
Update carries the `row_version` that was read, so a rename and a disable made
|
|
93
|
+
at the same time cannot silently undo each other. Archiving is terminal:
|
|
94
|
+
reconnecting later is a new connection, so consent a subscriber gave to the
|
|
95
|
+
archived identity is never revived under it.
|
|
96
|
+
|
|
97
|
+
`createChannelConnection` is discriminated on `mode`. `whooshbang_shared`
|
|
98
|
+
names a disclosed WhooshBang-owned identity by `shared_identity_id`.
|
|
99
|
+
`customer_byok` supplies the customer's own provider credential — for Telegram,
|
|
100
|
+
the bot token BotFather issued — and names no identity at all. The absence is
|
|
101
|
+
deliberate: an endpoint that digested a client-supplied identity would answer
|
|
102
|
+
"already claimed" for a bot the caller does not own, which would turn create
|
|
103
|
+
into an oracle for whether any given bot is connected to WhooshBang by anyone.
|
|
104
|
+
The identity is derived server-side from a provider-validated `getMe`. Pass
|
|
105
|
+
`confirm_webhook_takeover` only when the customer accepts that an existing
|
|
106
|
+
webhook on that bot will be replaced; without it, an occupied bot is refused.
|
|
107
|
+
|
|
108
|
+
`repairChannelConnection`, `rotateChannelConnectionCredential`,
|
|
109
|
+
`checkChannelConnectionHealth`, and `purgeChannelConnection` operate one
|
|
110
|
+
connection after it exists. Repair reprovisions the delivery path with the
|
|
111
|
+
credential already in custody. Rotate replaces that credential while keeping
|
|
112
|
+
the identity, so subscribers stay bound to what they consented to. Purge
|
|
113
|
+
releases the provider-side delivery path, destroys custody, and archives; it is
|
|
114
|
+
repeat-safe, so a caller that lost the first response can confirm the outcome.
|
|
115
|
+
|
|
116
|
+
All four are `connections:write`, including `checkChannelConnectionHealth`. A
|
|
117
|
+
health check makes a live provider call and advances the connection's recorded
|
|
118
|
+
health, so it is not a safe read; read `health` from `getChannelConnection`
|
|
119
|
+
when the last recorded answer is enough.
|
|
120
|
+
|
|
121
|
+
The Problem vocabulary is closed, so these refusals reuse published codes and
|
|
122
|
+
differ by `field_errors[].code`: a rejected credential is `request_invalid`
|
|
123
|
+
with `provider_credential_invalid`, an occupied webhook is `request_invalid`
|
|
124
|
+
with `webhook_takeover_required`, an identity already connected by anyone is
|
|
125
|
+
`idempotency_conflict` with `provider_identity_claimed`, an unreachable
|
|
126
|
+
provider is `provider_outcome_unknown`, and a rate-limited one is
|
|
127
|
+
`provider_retryable` with `retryAt`.
|
|
128
|
+
|
|
81
129
|
## Machine clients
|
|
82
130
|
|
|
83
131
|
`createMachineCredential` generates the C0-03 secret with Web Crypto and
|
|
@@ -116,8 +164,9 @@ then revoke a replacement. Product delivery methods continue to use project-
|
|
|
116
164
|
or machine-scoped credentials; the client does not persist either credential
|
|
117
165
|
kind.
|
|
118
166
|
|
|
119
|
-
Delivery, project creation, endpoint create/rotate,
|
|
120
|
-
acknowledgement side-effects require an explicit
|
|
167
|
+
Delivery, project creation, endpoint create/rotate, connection creation,
|
|
168
|
+
machine administration, and acknowledgement side-effects require an explicit
|
|
169
|
+
idempotency key. Credential
|
|
121
170
|
lifecycle create/rotate/revoke calls follow their one-time-reveal or intrinsic
|
|
122
171
|
repeat-safe contract instead. The client sends each call exactly once. A
|
|
123
172
|
transport failure on a side effect raises an error with
|
|
@@ -158,6 +207,6 @@ node packages/sdk-js/examples/send-interactive-message.mjs
|
|
|
158
207
|
## Compatibility
|
|
159
208
|
|
|
160
209
|
The exact runtime dependency is
|
|
161
|
-
`@whooshbang/contracts@1.0.0-rc.
|
|
210
|
+
`@whooshbang/contracts@1.0.0-rc.16`. Successful payloads are
|
|
162
211
|
validated with its packaged validators, while additive response fields remain
|
|
163
212
|
available on the returned object.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A very small argv reader.
|
|
3
|
+
*
|
|
4
|
+
* `node:util`'s `parseArgs` is here and capable, but it wants the option
|
|
5
|
+
* vocabulary declared up front and this CLI's options differ per command. The
|
|
6
|
+
* whole grammar is `wb <words…> [--flag value] [--switch]`, which is a dozen
|
|
7
|
+
* lines to read directly and no dependency to carry into a package a library
|
|
8
|
+
* consumer also installs.
|
|
9
|
+
*/
|
|
10
|
+
export interface ParsedArguments {
|
|
11
|
+
/** The command path: `["project", "create"]`. */
|
|
12
|
+
readonly words: readonly string[];
|
|
13
|
+
readonly options: ReadonlyMap<string, string>;
|
|
14
|
+
readonly switches: ReadonlySet<string>;
|
|
15
|
+
/** Values repeated across several `--flag value` pairs, e.g. `--scope`. */
|
|
16
|
+
readonly repeated: ReadonlyMap<string, readonly string[]>;
|
|
17
|
+
}
|
|
18
|
+
export declare function parseArguments(argv: readonly string[]): ParsedArguments;
|
|
19
|
+
export declare class UsageError extends Error {
|
|
20
|
+
constructor(message: string);
|
|
21
|
+
}
|
|
22
|
+
export declare function required(parsed: ParsedArguments, name: string, what: string): string;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const SWITCHES = new Set([
|
|
2
|
+
"help",
|
|
3
|
+
"json",
|
|
4
|
+
"version",
|
|
5
|
+
"credential-stdin",
|
|
6
|
+
"yes",
|
|
7
|
+
]);
|
|
8
|
+
export function parseArguments(argv) {
|
|
9
|
+
const words = [];
|
|
10
|
+
const options = new Map();
|
|
11
|
+
const switches = new Set();
|
|
12
|
+
const repeated = new Map();
|
|
13
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
14
|
+
const token = argv[index];
|
|
15
|
+
if (!token.startsWith("--")) {
|
|
16
|
+
words.push(token);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
const body = token.slice(2);
|
|
20
|
+
const equals = body.indexOf("=");
|
|
21
|
+
const name = equals === -1 ? body : body.slice(0, equals);
|
|
22
|
+
if (equals !== -1) {
|
|
23
|
+
const value = body.slice(equals + 1);
|
|
24
|
+
options.set(name, value);
|
|
25
|
+
repeated.set(name, [...(repeated.get(name) ?? []), value]);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (SWITCHES.has(name)) {
|
|
29
|
+
switches.add(name);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const next = argv[index + 1];
|
|
33
|
+
if (next === undefined || next.startsWith("--")) {
|
|
34
|
+
// A bare unknown flag is a switch rather than an error: the command
|
|
35
|
+
// decides what it does and does not understand, and it can say so in its
|
|
36
|
+
// own words.
|
|
37
|
+
switches.add(name);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
options.set(name, next);
|
|
41
|
+
repeated.set(name, [...(repeated.get(name) ?? []), next]);
|
|
42
|
+
index += 1;
|
|
43
|
+
}
|
|
44
|
+
return { options, repeated, switches, words };
|
|
45
|
+
}
|
|
46
|
+
export class UsageError extends Error {
|
|
47
|
+
constructor(message) {
|
|
48
|
+
super(message);
|
|
49
|
+
this.name = "UsageError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function required(parsed, name, what) {
|
|
53
|
+
const value = parsed.options.get(name);
|
|
54
|
+
if (value === undefined || value.length === 0) {
|
|
55
|
+
throw new UsageError(`--${name} is required: ${what}.`);
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=arguments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arguments.js","sourceRoot":"","sources":["../../src/cli/arguments.ts"],"names":[],"mappings":"AAkBA,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC;IACvB,MAAM;IACN,MAAM;IACN,SAAS;IACT,kBAAkB;IAClB,KAAK;CACN,CAAC,CAAC;AAEH,MAAM,UAAU,cAAc,CAAC,IAAuB;IACpD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE7C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAW,CAAC;QACpC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAClB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1D,IAAI,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;YAClB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACzB,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;YAC3D,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YACvB,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;QAC7B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,oEAAoE;YACpE,yEAAyE;YACzE,aAAa;YACb,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACnB,SAAS;QACX,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACxB,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1D,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAChD,CAAC;AAED,MAAM,OAAO,UAAW,SAAQ,KAAK;IACnC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,UAAU,QAAQ,CACtB,MAAuB,EACvB,IAAY,EACZ,IAAY;IAEZ,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,UAAU,CAAC,KAAK,IAAI,iBAAiB,IAAI,GAAG,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type ParsedArguments } from "./arguments.js";
|
|
2
|
+
import { type Writer } from "./output.js";
|
|
3
|
+
import { resolveOrigin } from "./session.js";
|
|
4
|
+
export interface CommandContext {
|
|
5
|
+
readonly parsed: ParsedArguments;
|
|
6
|
+
readonly writer: Writer;
|
|
7
|
+
readonly origin: string;
|
|
8
|
+
readonly openUrl: (url: string) => Promise<void>;
|
|
9
|
+
readonly readStdin: () => Promise<string>;
|
|
10
|
+
readonly credentialsFile?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveCommand(words: readonly string[]): ((context: CommandContext) => Promise<void>) | null;
|
|
13
|
+
export declare const COMMAND_NAMES: string[];
|
|
14
|
+
export { resolveOrigin };
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { required, UsageError } from "./arguments.js";
|
|
3
|
+
import { emit, emitSecret, table } from "./output.js";
|
|
4
|
+
import { authorizedClient, resolveOrigin, signIn, signOut } from "./session.js";
|
|
5
|
+
/**
|
|
6
|
+
* Every side-effecting call needs an idempotency key, and a person running a
|
|
7
|
+
* command by hand has no opinion about one. Generating it means a retried
|
|
8
|
+
* `wb message send` after a dropped connection is a replay rather than a
|
|
9
|
+
* second message — but only within the one invocation, which is why `--key`
|
|
10
|
+
* exists for anybody scripting a genuinely repeatable operation.
|
|
11
|
+
*/
|
|
12
|
+
function idempotency(parsed) {
|
|
13
|
+
return { idempotencyKey: parsed.options.get("key") ?? randomUUID() };
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `connection` unless asked otherwise, which matches what the API does for an
|
|
17
|
+
* ordinary subscriber id. Naming it on every send keeps a script auditable:
|
|
18
|
+
* somebody reading it can see which transport it meant without knowing the
|
|
19
|
+
* defaulting rule.
|
|
20
|
+
*/
|
|
21
|
+
function deliveryTarget(parsed) {
|
|
22
|
+
const value = parsed.options.get("target") ?? "connection";
|
|
23
|
+
if (value !== "connection" && value !== "simulator") {
|
|
24
|
+
throw new UsageError("--target must be connection or simulator.");
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function environmentArguments(parsed) {
|
|
29
|
+
return {
|
|
30
|
+
environmentId: required(parsed, "environment", "the environment id the operation acts in"),
|
|
31
|
+
projectId: required(parsed, "project", "the project id"),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function client(context) {
|
|
35
|
+
return authorizedClient({
|
|
36
|
+
...(context.credentialsFile === undefined
|
|
37
|
+
? {}
|
|
38
|
+
: { credentialsFile: context.credentialsFile }),
|
|
39
|
+
origin: context.origin,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const commands = {
|
|
43
|
+
async login(context) {
|
|
44
|
+
const scopes = context.parsed.repeated.get("scope");
|
|
45
|
+
const session = await signIn({
|
|
46
|
+
...(context.credentialsFile === undefined
|
|
47
|
+
? {}
|
|
48
|
+
: { credentialsFile: context.credentialsFile }),
|
|
49
|
+
openUrl: context.openUrl,
|
|
50
|
+
origin: context.origin,
|
|
51
|
+
...(scopes && scopes.length > 0 ? { scopes } : {}),
|
|
52
|
+
});
|
|
53
|
+
const api = await client(context);
|
|
54
|
+
const account = await api.getAccount();
|
|
55
|
+
emit(context.writer, { account: account.id, origin: context.origin, scopes: session.scopes }, [
|
|
56
|
+
`Signed in to ${context.origin}.`,
|
|
57
|
+
`Account ${account.id}.`,
|
|
58
|
+
`Permissions: ${session.scopes.join(", ")}`,
|
|
59
|
+
]);
|
|
60
|
+
},
|
|
61
|
+
async logout(context) {
|
|
62
|
+
const removed = await signOut(context.origin, context.credentialsFile);
|
|
63
|
+
emit(context.writer, { origin: context.origin, signedOut: removed }, [
|
|
64
|
+
removed
|
|
65
|
+
? `Signed out of ${context.origin}.`
|
|
66
|
+
: `Not signed in to ${context.origin}.`,
|
|
67
|
+
]);
|
|
68
|
+
},
|
|
69
|
+
async whoami(context) {
|
|
70
|
+
const account = await (await client(context)).getAccount();
|
|
71
|
+
emit(context.writer, account, [
|
|
72
|
+
`Account ${account.id}`,
|
|
73
|
+
...(account.name ? [account.name] : []),
|
|
74
|
+
]);
|
|
75
|
+
},
|
|
76
|
+
async "project list"(context) {
|
|
77
|
+
const projects = await (await client(context)).listProjects();
|
|
78
|
+
emit(context.writer, projects, table([
|
|
79
|
+
["ID", "SLUG", "NAME"],
|
|
80
|
+
...projects.items.map((project) => [
|
|
81
|
+
project.id,
|
|
82
|
+
project.slug,
|
|
83
|
+
project.name,
|
|
84
|
+
]),
|
|
85
|
+
]));
|
|
86
|
+
},
|
|
87
|
+
async "project create"(context) {
|
|
88
|
+
const name = context.parsed.words[2];
|
|
89
|
+
if (!name)
|
|
90
|
+
throw new UsageError("wb project create <name> [--slug <slug>]");
|
|
91
|
+
const slug = context.parsed.options.get("slug") ??
|
|
92
|
+
name
|
|
93
|
+
.toLowerCase()
|
|
94
|
+
.replace(/[^a-z0-9]+/gu, "-")
|
|
95
|
+
.replace(/^-+|-+$/gu, "");
|
|
96
|
+
const project = await (await client(context)).createProject({ name, slug }, idempotency(context.parsed));
|
|
97
|
+
emit(context.writer, project, [
|
|
98
|
+
`Created project ${project.id} (${project.slug}).`,
|
|
99
|
+
...table([
|
|
100
|
+
["ENVIRONMENT", "KIND", "DEFAULT NOTIFIER"],
|
|
101
|
+
...project.environments.map((environment) => [
|
|
102
|
+
environment.id,
|
|
103
|
+
environment.kind,
|
|
104
|
+
environment.default_notifier.id,
|
|
105
|
+
]),
|
|
106
|
+
]),
|
|
107
|
+
]);
|
|
108
|
+
},
|
|
109
|
+
async "credential create"(context) {
|
|
110
|
+
const { environmentId, projectId } = environmentArguments(context.parsed);
|
|
111
|
+
const label = required(context.parsed, "label", "what this credential is for");
|
|
112
|
+
// The contract types the scope list as a bounded tuple, so a plain
|
|
113
|
+
// string[] cannot be passed. The values are validated by the API either
|
|
114
|
+
// way; the assertion buys the caller a shape the compiler accepts.
|
|
115
|
+
const scopes = (context.parsed.repeated.get("scope") ?? [
|
|
116
|
+
"messages:write",
|
|
117
|
+
"messages:read",
|
|
118
|
+
"subscriptions:write",
|
|
119
|
+
"subscriptions:read",
|
|
120
|
+
]);
|
|
121
|
+
const created = await (await client(context)).createApiCredential(projectId, environmentId, { label, scopes });
|
|
122
|
+
if (context.writer.json) {
|
|
123
|
+
emit(context.writer, created, []);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
emitSecret(context.writer, created.secret, "The complete secret is shown once and is not retrievable later. Store it now.");
|
|
127
|
+
},
|
|
128
|
+
async "connection list"(context) {
|
|
129
|
+
const { environmentId, projectId } = environmentArguments(context.parsed);
|
|
130
|
+
const connections = await (await client(context)).listChannelConnections(projectId, environmentId);
|
|
131
|
+
emit(context.writer, connections, table([
|
|
132
|
+
["ID", "MODE", "STATUS", "NAME"],
|
|
133
|
+
...connections.items.map((connection) => [
|
|
134
|
+
connection.id,
|
|
135
|
+
connection.mode,
|
|
136
|
+
connection.status,
|
|
137
|
+
connection.display_name,
|
|
138
|
+
]),
|
|
139
|
+
]));
|
|
140
|
+
},
|
|
141
|
+
async "connection providers"(context) {
|
|
142
|
+
const { environmentId, projectId } = environmentArguments(context.parsed);
|
|
143
|
+
const providers = await (await client(context)).listConnectionProviders(projectId, environmentId);
|
|
144
|
+
emit(context.writer, providers, table([
|
|
145
|
+
["PROVIDER", "SHARED IDENTITY", "DISPLAY NAME"],
|
|
146
|
+
...providers.items.flatMap((provider) => provider.shared_identities.map((identity) => [
|
|
147
|
+
provider.provider,
|
|
148
|
+
identity.id,
|
|
149
|
+
identity.display_name,
|
|
150
|
+
])),
|
|
151
|
+
]));
|
|
152
|
+
},
|
|
153
|
+
async "connection create"(context) {
|
|
154
|
+
const { environmentId, projectId } = environmentArguments(context.parsed);
|
|
155
|
+
const mode = required(context.parsed, "mode", "whooshbang_shared, customer_managed, or customer_byok");
|
|
156
|
+
const displayName = required(context.parsed, "name", "what to call this connection");
|
|
157
|
+
let request = {
|
|
158
|
+
display_name: displayName,
|
|
159
|
+
mode,
|
|
160
|
+
};
|
|
161
|
+
if (mode === "whooshbang_shared") {
|
|
162
|
+
request["shared_identity_id"] = required(context.parsed, "shared-identity", "which shared identity to select — see `wb connection providers`");
|
|
163
|
+
}
|
|
164
|
+
else if (mode === "customer_byok") {
|
|
165
|
+
if (!context.parsed.switches.has("credential-stdin")) {
|
|
166
|
+
throw new UsageError("customer_byok needs --credential-stdin, and the token on standard input. A bot token passed as an argument is visible to every process on the machine and lands in your shell history.");
|
|
167
|
+
}
|
|
168
|
+
request = {
|
|
169
|
+
...request,
|
|
170
|
+
confirm_webhook_takeover: true,
|
|
171
|
+
credential: (await context.readStdin()).trim(),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
else if (mode === "customer_managed") {
|
|
175
|
+
const username = context.parsed.options.get("username");
|
|
176
|
+
if (username)
|
|
177
|
+
request["suggested_username"] = username;
|
|
178
|
+
}
|
|
179
|
+
const connection = await (await client(context)).createChannelConnection(projectId, environmentId, request, idempotency(context.parsed));
|
|
180
|
+
emit(context.writer, connection, [
|
|
181
|
+
`Created connection ${connection.id} (${connection.status}).`,
|
|
182
|
+
...(connection.setup?.handoff_url
|
|
183
|
+
? [`Finish setup in Telegram: ${connection.setup.handoff_url}`]
|
|
184
|
+
: []),
|
|
185
|
+
]);
|
|
186
|
+
},
|
|
187
|
+
async "connection route"(context) {
|
|
188
|
+
const { environmentId, projectId } = environmentArguments(context.parsed);
|
|
189
|
+
const routes = await (await client(context)).attachNotifierConnection(projectId, environmentId, {
|
|
190
|
+
connection_id: required(context.parsed, "connection", "the connection to route through"),
|
|
191
|
+
notifier_id: required(context.parsed, "notifier", "the notifier to route"),
|
|
192
|
+
});
|
|
193
|
+
emit(context.writer, routes, table([
|
|
194
|
+
["NOTIFIER", "CONNECTION", "DEFAULT"],
|
|
195
|
+
...routes.items.map((route) => [
|
|
196
|
+
route.notifier_id,
|
|
197
|
+
route.connection_id,
|
|
198
|
+
route.is_default ? "yes" : "no",
|
|
199
|
+
]),
|
|
200
|
+
]));
|
|
201
|
+
},
|
|
202
|
+
async "link create"(context) {
|
|
203
|
+
const { environmentId, projectId } = environmentArguments(context.parsed);
|
|
204
|
+
const connectionId = context.parsed.options.get("connection");
|
|
205
|
+
const link = await (await client(context)).createSubscriptionLinkInEnvironment(projectId, environmentId, {
|
|
206
|
+
channels: ["telegram"],
|
|
207
|
+
...(connectionId === undefined ? {} : { connection_id: connectionId }),
|
|
208
|
+
subscriber_id: required(context.parsed, "subscriber", "your own identifier for this subscriber"),
|
|
209
|
+
}, idempotency(context.parsed));
|
|
210
|
+
emit(context.writer, link, [
|
|
211
|
+
`Link ${link.id} (${link.status}).`,
|
|
212
|
+
// A link carries no authorization URL once it has been used, and an
|
|
213
|
+
// idempotent replay returns the used one. Printing "undefined" there
|
|
214
|
+
// would read as a defect rather than as the link having done its job.
|
|
215
|
+
...(link.authorization_url
|
|
216
|
+
? ["Send the subscriber here:", link.authorization_url]
|
|
217
|
+
: ["This link has already been used; issue another to invite again."]),
|
|
218
|
+
]);
|
|
219
|
+
},
|
|
220
|
+
async "message send"(context) {
|
|
221
|
+
const { environmentId, projectId } = environmentArguments(context.parsed);
|
|
222
|
+
const message = await (await client(context)).createMessageInEnvironment(projectId, environmentId, {
|
|
223
|
+
content: {
|
|
224
|
+
text: required(context.parsed, "text", "the message body"),
|
|
225
|
+
type: "text",
|
|
226
|
+
},
|
|
227
|
+
delivery_target: deliveryTarget(context.parsed),
|
|
228
|
+
to: {
|
|
229
|
+
subscriber_id: required(context.parsed, "subscriber", "who to send to"),
|
|
230
|
+
},
|
|
231
|
+
}, idempotency(context.parsed));
|
|
232
|
+
emit(context.writer, message, [
|
|
233
|
+
`Accepted ${message.id} (${message.state}).`,
|
|
234
|
+
`Read it with: wb message get ${message.id} --project ${projectId} --environment ${environmentId}`,
|
|
235
|
+
]);
|
|
236
|
+
},
|
|
237
|
+
async "message get"(context) {
|
|
238
|
+
const { environmentId, projectId } = environmentArguments(context.parsed);
|
|
239
|
+
const messageId = context.parsed.words[2];
|
|
240
|
+
if (!messageId)
|
|
241
|
+
throw new UsageError("wb message get <message-id>");
|
|
242
|
+
const message = await (await client(context)).getMessageInEnvironment(projectId, environmentId, messageId);
|
|
243
|
+
emit(context.writer, message, [
|
|
244
|
+
`${message.id} ${message.state}`,
|
|
245
|
+
`provider: ${message.delivery.highest_proven_provider_state}`,
|
|
246
|
+
`attempts: ${message.delivery.attempt_count}`,
|
|
247
|
+
]);
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
export function resolveCommand(words) {
|
|
251
|
+
const two = words.slice(0, 2).join(" ");
|
|
252
|
+
const one = words[0] ?? "";
|
|
253
|
+
return commands[two] ?? commands[one] ?? null;
|
|
254
|
+
}
|
|
255
|
+
export const COMMAND_NAMES = Object.keys(commands);
|
|
256
|
+
export { resolveOrigin };
|
|
257
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.js","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the CLI keeps what it learned about a deployment.
|
|
3
|
+
*
|
|
4
|
+
* One file, holding one record per origin. A developer who works against
|
|
5
|
+
* staging and production is the ordinary case, not an exotic one, and a single
|
|
6
|
+
* unkeyed token would silently send staging work to production the first time
|
|
7
|
+
* somebody changed `--api-url`.
|
|
8
|
+
*
|
|
9
|
+
* `$XDG_CONFIG_HOME` is honoured where it is set, and `~/.config/whooshbang`
|
|
10
|
+
* otherwise, which is where this project already keeps operator files.
|
|
11
|
+
*/
|
|
12
|
+
export interface StoredSession {
|
|
13
|
+
/** The client id dynamic registration returned. Reused on later logins. */
|
|
14
|
+
readonly clientId: string;
|
|
15
|
+
readonly accessToken: string;
|
|
16
|
+
readonly refreshToken?: string;
|
|
17
|
+
/** Epoch milliseconds, or absent when the server named no expiry. */
|
|
18
|
+
readonly expiresAt?: number;
|
|
19
|
+
readonly scopes: readonly string[];
|
|
20
|
+
}
|
|
21
|
+
export interface StoredCredentials {
|
|
22
|
+
readonly version: 1;
|
|
23
|
+
/** Keyed by API origin, exactly as the caller spelled it. */
|
|
24
|
+
readonly origins: Record<string, StoredSession>;
|
|
25
|
+
}
|
|
26
|
+
export declare function credentialsDirectory(environment?: NodeJS.ProcessEnv): string;
|
|
27
|
+
export declare function credentialsPath(environment?: NodeJS.ProcessEnv): string;
|
|
28
|
+
/**
|
|
29
|
+
* A missing or unreadable file is an empty store rather than an error: not
|
|
30
|
+
* being logged in is the ordinary first state, and a corrupted file should send
|
|
31
|
+
* somebody to `wb login` rather than to a stack trace.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readCredentials(file?: string): Promise<StoredCredentials>;
|
|
34
|
+
/**
|
|
35
|
+
* Written owner-only, in a directory created owner-only.
|
|
36
|
+
*
|
|
37
|
+
* `writeFile`'s mode is subject to the process umask, so the permissions are
|
|
38
|
+
* set explicitly afterwards rather than hoped for. The token is a bearer
|
|
39
|
+
* credential for the whole account; a group-readable home directory is common
|
|
40
|
+
* enough that leaving this to umask is not a defensible default.
|
|
41
|
+
*/
|
|
42
|
+
export declare function writeCredentials(credentials: StoredCredentials, file?: string): Promise<void>;
|
|
43
|
+
export declare function storeSession(origin: string, session: StoredSession, file?: string): Promise<void>;
|
|
44
|
+
export declare function forgetSession(origin: string, file?: string): Promise<StoredSession | null>;
|
|
45
|
+
export declare function loadSession(origin: string, file?: string): Promise<StoredSession | null>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const EMPTY = { origins: {}, version: 1 };
|
|
5
|
+
export function credentialsDirectory(environment = process.env) {
|
|
6
|
+
const xdg = environment.XDG_CONFIG_HOME?.trim();
|
|
7
|
+
const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), ".config");
|
|
8
|
+
return path.join(base, "whooshbang");
|
|
9
|
+
}
|
|
10
|
+
export function credentialsPath(environment = process.env) {
|
|
11
|
+
return path.join(credentialsDirectory(environment), "cli-credentials.json");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A missing or unreadable file is an empty store rather than an error: not
|
|
15
|
+
* being logged in is the ordinary first state, and a corrupted file should send
|
|
16
|
+
* somebody to `wb login` rather than to a stack trace.
|
|
17
|
+
*/
|
|
18
|
+
export async function readCredentials(file = credentialsPath()) {
|
|
19
|
+
let contents;
|
|
20
|
+
try {
|
|
21
|
+
contents = await readFile(file, "utf8");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return EMPTY;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const parsed = JSON.parse(contents);
|
|
28
|
+
if (typeof parsed !== "object" ||
|
|
29
|
+
parsed === null ||
|
|
30
|
+
parsed.version !== 1 ||
|
|
31
|
+
typeof parsed.origins !== "object" ||
|
|
32
|
+
parsed.origins === null) {
|
|
33
|
+
return EMPTY;
|
|
34
|
+
}
|
|
35
|
+
return parsed;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return EMPTY;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Written owner-only, in a directory created owner-only.
|
|
43
|
+
*
|
|
44
|
+
* `writeFile`'s mode is subject to the process umask, so the permissions are
|
|
45
|
+
* set explicitly afterwards rather than hoped for. The token is a bearer
|
|
46
|
+
* credential for the whole account; a group-readable home directory is common
|
|
47
|
+
* enough that leaving this to umask is not a defensible default.
|
|
48
|
+
*/
|
|
49
|
+
export async function writeCredentials(credentials, file = credentialsPath()) {
|
|
50
|
+
await mkdir(path.dirname(file), { mode: 0o700, recursive: true });
|
|
51
|
+
await chmod(path.dirname(file), 0o700).catch(() => undefined);
|
|
52
|
+
await writeFile(file, `${JSON.stringify(credentials, null, 2)}\n`, {
|
|
53
|
+
mode: 0o600,
|
|
54
|
+
});
|
|
55
|
+
await chmod(file, 0o600);
|
|
56
|
+
}
|
|
57
|
+
export async function storeSession(origin, session, file = credentialsPath()) {
|
|
58
|
+
const current = await readCredentials(file);
|
|
59
|
+
await writeCredentials({
|
|
60
|
+
origins: { ...current.origins, [origin]: session },
|
|
61
|
+
version: 1,
|
|
62
|
+
}, file);
|
|
63
|
+
}
|
|
64
|
+
export async function forgetSession(origin, file = credentialsPath()) {
|
|
65
|
+
const current = await readCredentials(file);
|
|
66
|
+
const session = current.origins[origin] ?? null;
|
|
67
|
+
if (!session)
|
|
68
|
+
return null;
|
|
69
|
+
const origins = { ...current.origins };
|
|
70
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
71
|
+
delete origins[origin];
|
|
72
|
+
if (Object.keys(origins).length === 0) {
|
|
73
|
+
// Nothing left to hold: remove the file rather than leave an empty husk
|
|
74
|
+
// that reads, to anybody auditing a home directory, like a live credential.
|
|
75
|
+
await rm(file, { force: true });
|
|
76
|
+
return session;
|
|
77
|
+
}
|
|
78
|
+
await writeCredentials({ origins, version: 1 }, file);
|
|
79
|
+
return session;
|
|
80
|
+
}
|
|
81
|
+
export async function loadSession(origin, file = credentialsPath()) {
|
|
82
|
+
const current = await readCredentials(file);
|
|
83
|
+
return current.origins[origin] ?? null;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/cli/config.ts"],"names":[],"mappings":"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"}
|