@instantkom/cli 3.169.0 → 3.171.0
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/api-client.js +5 -0
- package/dist/commands/send.js +14 -3
- package/dist/send/fallback.js +34 -11
- package/npm-shrinkwrap.json +2 -2
- package/oclif.manifest.json +2747 -2747
- package/package.json +1 -1
package/dist/api-client.js
CHANGED
|
@@ -42,6 +42,11 @@ export class ApiClient {
|
|
|
42
42
|
if (!response.ok)
|
|
43
43
|
return false;
|
|
44
44
|
const data = await this.parseResponse(response);
|
|
45
|
+
// A 2xx without an accessToken (empty/malformed body) must not count as a
|
|
46
|
+
// successful refresh -- otherwise ensureJwtToken hands back a stale/undefined
|
|
47
|
+
// token and the next request sends `Authorization: Bearer undefined` (#5264c).
|
|
48
|
+
if (!data.accessToken)
|
|
49
|
+
return false;
|
|
45
50
|
this.jwtAccessToken = data.accessToken;
|
|
46
51
|
if (data.refreshToken)
|
|
47
52
|
this.jwtRefreshToken = data.refreshToken;
|
package/dist/commands/send.js
CHANGED
|
@@ -51,9 +51,7 @@ export default class Send extends BaseCommand {
|
|
|
51
51
|
}
|
|
52
52
|
const scheduledAt = parseSendAt(flags['send-at']);
|
|
53
53
|
const media = prepareMediaFiles(flags.media);
|
|
54
|
-
const
|
|
55
|
-
? { 'Idempotency-Key': flags['idempotency-key'] }
|
|
56
|
-
: undefined;
|
|
54
|
+
const idempotencyKey = flags['idempotency-key'];
|
|
57
55
|
if (flags['dry-run']) {
|
|
58
56
|
const payload = {
|
|
59
57
|
channelId: flags.channel,
|
|
@@ -77,9 +75,22 @@ export default class Send extends BaseCommand {
|
|
|
77
75
|
const channelAttempts = flags.channels
|
|
78
76
|
? await resolveFallbackChannels(client, flags.channels)
|
|
79
77
|
: [{ alias: String(flags.channel), channelId: flags.channel }];
|
|
78
|
+
// Only the multi-channel fallback needs per-channel idempotency keys.
|
|
79
|
+
// Reusing one key across fallback channels let the API dedupe the SMS retry
|
|
80
|
+
// against the failed WhatsApp attempt, silently defeating fallback (#5264b).
|
|
81
|
+
// A single explicit channel keeps the exact key the caller passed, so
|
|
82
|
+
// audit-by-key / retry-with-key behaviour is unchanged.
|
|
83
|
+
const isFallback = channelAttempts.length > 1;
|
|
80
84
|
const failures = [];
|
|
81
85
|
for (const attempt of channelAttempts) {
|
|
82
86
|
try {
|
|
87
|
+
const idempotencyHeaders = idempotencyKey
|
|
88
|
+
? {
|
|
89
|
+
'Idempotency-Key': isFallback
|
|
90
|
+
? `${idempotencyKey}-${attempt.channelId}`
|
|
91
|
+
: idempotencyKey,
|
|
92
|
+
}
|
|
93
|
+
: undefined;
|
|
83
94
|
const message = await this.sendViaChannel({
|
|
84
95
|
channelId: attempt.channelId,
|
|
85
96
|
client,
|
package/dist/send/fallback.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { ApiError } from '../errors/api-error.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
// Messenger aliases resolve to numeric gateway type ids (channel.gatewayTypeId,
|
|
3
|
+
// = the DB gwtype). Matching on the id is robust against the API returning human
|
|
4
|
+
// display names for gatewayType ("WhatsApp Cloud API"), which an exact string
|
|
5
|
+
// match would miss. wa/whatsapp cover both the legacy on-prem WhatsApp (13) and
|
|
6
|
+
// the modern Cloud API (17). Keep in sync with GatewayType (services/api).
|
|
7
|
+
const ALIAS_GATEWAY_TYPE_IDS = {
|
|
8
|
+
wa: [13, 17],
|
|
9
|
+
whatsapp: [13, 17],
|
|
10
|
+
sms: [2],
|
|
6
11
|
};
|
|
7
12
|
function channelsFromResponse(response) {
|
|
8
13
|
if (Array.isArray(response))
|
|
@@ -10,13 +15,31 @@ function channelsFromResponse(response) {
|
|
|
10
15
|
return response.data ?? response.items ?? [];
|
|
11
16
|
}
|
|
12
17
|
function matchesAlias(channel, alias) {
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
channel.
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const normalized = alias.toLowerCase();
|
|
19
|
+
// A numeric alias addresses a channel by its exact id. Substring matching
|
|
20
|
+
// here let '2' false-match ids 20/12/21 (#5264a).
|
|
21
|
+
if (/^\d+$/.test(normalized)) {
|
|
22
|
+
return String(channel.id) === normalized;
|
|
23
|
+
}
|
|
24
|
+
const gatewayType = (channel.gatewayType ?? '').toLowerCase();
|
|
25
|
+
// Known messenger aliases match on the numeric gateway type id, so multi-word
|
|
26
|
+
// display names ("WhatsApp Cloud API", gatewayTypeId 17) resolve correctly.
|
|
27
|
+
// The user-chosen channel name is deliberately never consulted -- a channel
|
|
28
|
+
// named "WA Backup via SMS" must not receive a WhatsApp message (#5264a).
|
|
29
|
+
const typeIds = ALIAS_GATEWAY_TYPE_IDS[normalized];
|
|
30
|
+
if (typeIds) {
|
|
31
|
+
if (channel.gatewayTypeId !== undefined) {
|
|
32
|
+
return typeIds.includes(channel.gatewayTypeId);
|
|
33
|
+
}
|
|
34
|
+
// Defensive fallback if a response omits gatewayTypeId: match the gateway
|
|
35
|
+
// display name by prefix (still never the channel name).
|
|
36
|
+
return normalized === 'sms'
|
|
37
|
+
? gatewayType === 'sms'
|
|
38
|
+
: gatewayType.startsWith('whatsapp');
|
|
39
|
+
}
|
|
40
|
+
// Arbitrary alias: exact case-insensitive match on the gateway display name
|
|
41
|
+
// (never a substring, never the channel name).
|
|
42
|
+
return gatewayType === normalized;
|
|
20
43
|
}
|
|
21
44
|
export async function resolveFallbackChannels(client, aliasesCsv) {
|
|
22
45
|
const aliases = aliasesCsv
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@instantkom/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.171.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@instantkom/cli",
|
|
9
|
-
"version": "3.
|
|
9
|
+
"version": "3.171.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@oclif/core": "^4",
|