@xmoxmo/bncr 0.2.2 → 0.2.4
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 +1 -1
- package/index.ts +191 -19
- package/package.json +3 -3
- package/src/channel.ts +1758 -172
- package/src/core/status.ts +10 -10
- package/src/core/types.ts +18 -1
- package/src/messaging/outbound/build-send-action.ts +115 -0
- package/src/messaging/outbound/send.ts +2 -0
package/src/core/status.ts
CHANGED
|
@@ -75,20 +75,12 @@ export function buildIntegratedDiagnostics(input: RuntimeStatusInput): BncrDiagn
|
|
|
75
75
|
export function buildStatusHeadlineFromRuntime(input: RuntimeStatusInput): string {
|
|
76
76
|
const diag = buildIntegratedDiagnostics(input);
|
|
77
77
|
const h = diag.health;
|
|
78
|
-
const r = diag.regression;
|
|
79
|
-
|
|
80
78
|
const parts = [
|
|
81
|
-
|
|
79
|
+
input.connected ? 'linked' : 'status',
|
|
82
80
|
`p:${h.pending}`,
|
|
83
81
|
`d:${h.deadLetter}`,
|
|
84
82
|
`c:${h.activeConnections}`,
|
|
85
83
|
];
|
|
86
|
-
|
|
87
|
-
if (!r.ok) {
|
|
88
|
-
if (r.invalidOutboxSessionKeys > 0) parts.push(`invalid:${r.invalidOutboxSessionKeys}`);
|
|
89
|
-
if (r.legacyAccountResidue > 0) parts.push(`legacy:${r.legacyAccountResidue}`);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
84
|
return parts.join(' ');
|
|
93
85
|
}
|
|
94
86
|
|
|
@@ -128,6 +120,7 @@ export function buildStatusMetaFromRuntime(input: RuntimeStatusInput) {
|
|
|
128
120
|
}
|
|
129
121
|
|
|
130
122
|
export function buildAccountRuntimeSnapshot(input: RuntimeStatusInput) {
|
|
123
|
+
const meta = buildStatusMetaFromRuntime(input);
|
|
131
124
|
return {
|
|
132
125
|
accountId: input.accountId,
|
|
133
126
|
running: input.running ?? true,
|
|
@@ -138,7 +131,14 @@ export function buildAccountRuntimeSnapshot(input: RuntimeStatusInput) {
|
|
|
138
131
|
lastOutboundAt: input.lastOutboundAt || null,
|
|
139
132
|
mode: input.connected ? 'linked' : 'configured',
|
|
140
133
|
lastError: input.lastError ?? null,
|
|
141
|
-
|
|
134
|
+
pending: input.pending,
|
|
135
|
+
deadLetter: input.deadLetter,
|
|
136
|
+
lastSessionKey: input.lastSession?.sessionKey || null,
|
|
137
|
+
lastSessionScope: input.lastSession?.scope || null,
|
|
138
|
+
lastSessionAt: input.lastSession?.updatedAt || null,
|
|
139
|
+
lastActivityAt: input.lastActivityAt || null,
|
|
140
|
+
diagnostics: meta.diagnostics,
|
|
141
|
+
meta,
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
|
package/src/core/types.ts
CHANGED
|
@@ -26,7 +26,21 @@ export type OutboxEntry = {
|
|
|
26
26
|
accountId: string;
|
|
27
27
|
sessionKey: string;
|
|
28
28
|
route: BncrRoute;
|
|
29
|
-
payload: Record<string, unknown
|
|
29
|
+
payload: Record<string, unknown> & {
|
|
30
|
+
_meta?: {
|
|
31
|
+
kind?: 'message' | 'file-transfer';
|
|
32
|
+
retryCount?: number;
|
|
33
|
+
nextAttemptAt?: number;
|
|
34
|
+
mediaUrl?: string;
|
|
35
|
+
mediaLocalRoots?: string[];
|
|
36
|
+
text?: string;
|
|
37
|
+
asVoice?: boolean;
|
|
38
|
+
audioAsVoice?: boolean;
|
|
39
|
+
replyToId?: string;
|
|
40
|
+
finalEvent?: string;
|
|
41
|
+
[key: string]: unknown;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
30
44
|
createdAt: number;
|
|
31
45
|
retryCount: number;
|
|
32
46
|
nextAttemptAt: number;
|
|
@@ -35,6 +49,9 @@ export type OutboxEntry = {
|
|
|
35
49
|
lastPushAt?: number;
|
|
36
50
|
lastPushConnId?: string;
|
|
37
51
|
lastPushClientId?: string;
|
|
52
|
+
routeAttemptConnIds?: string[];
|
|
53
|
+
routeAttemptRound?: number;
|
|
54
|
+
fastReroutePending?: boolean;
|
|
38
55
|
};
|
|
39
56
|
|
|
40
57
|
export type BncrDiagnosticsSummary = {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
type MinimalBncrSendInput = {
|
|
4
|
+
channel?: string;
|
|
5
|
+
action?: string;
|
|
6
|
+
idempotencyKey?: string;
|
|
7
|
+
accountId?: string;
|
|
8
|
+
to?: string;
|
|
9
|
+
message?: string;
|
|
10
|
+
caption?: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
media?: string;
|
|
13
|
+
filePath?: string;
|
|
14
|
+
mediaUrl?: string;
|
|
15
|
+
asVoice?: boolean;
|
|
16
|
+
audioAsVoice?: boolean;
|
|
17
|
+
params?: Record<string, unknown>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type BuiltBncrMessageAction = {
|
|
21
|
+
channel: string;
|
|
22
|
+
action: string;
|
|
23
|
+
idempotencyKey: string;
|
|
24
|
+
accountId?: string;
|
|
25
|
+
params: Record<string, unknown>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function asString(v: unknown, fallback = ''): string {
|
|
29
|
+
if (typeof v === 'string') return v;
|
|
30
|
+
if (v == null) return fallback;
|
|
31
|
+
return String(v);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
35
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function pickFirstString(...values: unknown[]): string | undefined {
|
|
39
|
+
for (const value of values) {
|
|
40
|
+
if (typeof value !== 'string') continue;
|
|
41
|
+
if (value.length === 0) return '';
|
|
42
|
+
return value;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function pickFirstBoolean(...values: unknown[]): boolean | undefined {
|
|
48
|
+
for (const value of values) {
|
|
49
|
+
if (typeof value === 'boolean') return value;
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildBncrMessageAction(input: MinimalBncrSendInput): BuiltBncrMessageAction {
|
|
55
|
+
const paramsObj = isPlainObject(input.params) ? input.params : {};
|
|
56
|
+
|
|
57
|
+
const channel = asString(input.channel || 'bncr').trim() || 'bncr';
|
|
58
|
+
const action = asString(input.action || 'send').trim() || 'send';
|
|
59
|
+
const idempotencyKey =
|
|
60
|
+
asString(input.idempotencyKey || '').trim() || `bncr-${randomUUID()}`;
|
|
61
|
+
const accountId =
|
|
62
|
+
asString(pickFirstString(paramsObj.accountId, input.accountId) || '').trim() || undefined;
|
|
63
|
+
|
|
64
|
+
const to = asString(pickFirstString(paramsObj.to, input.to) || '').trim();
|
|
65
|
+
if (!to) throw new Error('bncr send requires to');
|
|
66
|
+
|
|
67
|
+
const mediaPath = pickFirstString(
|
|
68
|
+
paramsObj.media,
|
|
69
|
+
paramsObj.path,
|
|
70
|
+
paramsObj.filePath,
|
|
71
|
+
paramsObj.mediaUrl,
|
|
72
|
+
input.media,
|
|
73
|
+
input.path,
|
|
74
|
+
input.filePath,
|
|
75
|
+
input.mediaUrl,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const message = pickFirstString(paramsObj.message, input.message) ?? '';
|
|
79
|
+
const explicitCaption = pickFirstString(paramsObj.caption, input.caption) ?? '';
|
|
80
|
+
const asVoice = pickFirstBoolean(paramsObj.asVoice, input.asVoice);
|
|
81
|
+
const audioAsVoice = pickFirstBoolean(paramsObj.audioAsVoice, input.audioAsVoice);
|
|
82
|
+
|
|
83
|
+
if ((asVoice === true || audioAsVoice === true) && !mediaPath) {
|
|
84
|
+
throw new Error('bncr voice send requires media path');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const normalizedParams: Record<string, unknown> = {
|
|
88
|
+
...paramsObj,
|
|
89
|
+
to,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
if (mediaPath) {
|
|
93
|
+
normalizedParams.path = mediaPath;
|
|
94
|
+
const finalCaption = explicitCaption || message;
|
|
95
|
+
if (finalCaption) normalizedParams.caption = finalCaption;
|
|
96
|
+
delete normalizedParams.message;
|
|
97
|
+
} else {
|
|
98
|
+
const finalMessage = message || explicitCaption;
|
|
99
|
+
if (!finalMessage.trim()) throw new Error('bncr send requires message or media');
|
|
100
|
+
normalizedParams.message = finalMessage;
|
|
101
|
+
delete normalizedParams.caption;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (asVoice === true) normalizedParams.asVoice = true;
|
|
105
|
+
if (audioAsVoice === true) normalizedParams.audioAsVoice = true;
|
|
106
|
+
if (accountId) normalizedParams.accountId = accountId;
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
channel,
|
|
110
|
+
action,
|
|
111
|
+
idempotencyKey,
|
|
112
|
+
...(accountId ? { accountId } : {}),
|
|
113
|
+
params: normalizedParams,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -51,6 +51,7 @@ export async function sendBncrMedia(params: {
|
|
|
51
51
|
to: string;
|
|
52
52
|
text?: string;
|
|
53
53
|
mediaUrl?: string;
|
|
54
|
+
mediaUrls?: string[];
|
|
54
55
|
asVoice?: boolean;
|
|
55
56
|
audioAsVoice?: boolean;
|
|
56
57
|
replyToId?: string;
|
|
@@ -86,6 +87,7 @@ export async function sendBncrMedia(params: {
|
|
|
86
87
|
payload: {
|
|
87
88
|
text: params.text || '',
|
|
88
89
|
mediaUrl: params.mediaUrl || '',
|
|
90
|
+
mediaUrls: params.mediaUrls?.length ? params.mediaUrls : undefined,
|
|
89
91
|
asVoice: params.asVoice === true ? true : undefined,
|
|
90
92
|
audioAsVoice: params.audioAsVoice === true ? true : undefined,
|
|
91
93
|
replyToId: params.replyToId,
|