salt-agent-sdk 0.20.0 → 0.22.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/README.md +28 -3
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +72 -1
- package/dist/actions.js.map +1 -1
- package/dist/callBundle.d.ts +90 -0
- package/dist/callBundle.d.ts.map +1 -0
- package/dist/callBundle.js +199 -0
- package/dist/callBundle.js.map +1 -0
- package/dist/client.d.ts +88 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +80 -2
- package/dist/client.js.map +1 -1
- package/dist/device/client.d.ts +12 -4
- package/dist/device/client.d.ts.map +1 -1
- package/dist/device/client.js +44 -1
- package/dist/device/client.js.map +1 -1
- package/dist/device/host.d.ts +74 -3
- package/dist/device/host.d.ts.map +1 -1
- package/dist/device/host.js +262 -12
- package/dist/device/host.js.map +1 -1
- package/dist/device/http.d.ts.map +1 -1
- package/dist/device/http.js +3 -2
- package/dist/device/http.js.map +1 -1
- package/dist/device/protocol.d.ts +97 -1
- package/dist/device/protocol.d.ts.map +1 -1
- package/dist/device/protocol.js +155 -1
- package/dist/device/protocol.js.map +1 -1
- package/dist/help.d.ts +54 -0
- package/dist/help.d.ts.map +1 -0
- package/dist/help.js +62 -0
- package/dist/help.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lattice.d.ts +206 -0
- package/dist/lattice.d.ts.map +1 -0
- package/dist/lattice.js +366 -0
- package/dist/lattice.js.map +1 -0
- package/dist/webhook.d.ts +67 -3
- package/dist/webhook.d.ts.map +1 -1
- package/dist/webhook.js +72 -4
- package/dist/webhook.js.map +1 -1
- package/dist/work.d.ts +2 -0
- package/dist/work.d.ts.map +1 -1
- package/dist/work.js +4 -0
- package/dist/work.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The Calls v1 crypto binding (salt-api CALL_PROTOCOL_SPEC.md section 4), ported from salt-fe's
|
|
3
|
+
// src/utilities/callCrypto.js and the bundle builders in src/utilities/webrtc.js. Byte-compatible:
|
|
4
|
+
// a bundle this module signs and encrypts is read by salt-fe's decryptAndVerify, and the other way
|
|
5
|
+
// round (tests/call-bundle.test.js proves both against a fixture generated from salt-fe itself).
|
|
6
|
+
//
|
|
7
|
+
// The signed-then-encrypted offer/answer bundle is
|
|
8
|
+
// { v, call_id, kind, sdp, dtls_fingerprints, ts, nonce }
|
|
9
|
+
// (a renegotiation adds `sdp_type`; a trickle-ICE batch is { v, call_id, candidates, ts, nonce }).
|
|
10
|
+
// The sender SIGNS it with their own PGP private key and ENCRYPTS it to the peer's public key in one
|
|
11
|
+
// openpgp call. That closes the hole WebRTC leaves open: a signaling server substituting its own SDP
|
|
12
|
+
// to sit in the media path. Salt relays the opaque armored `payload` and never reads it.
|
|
13
|
+
//
|
|
14
|
+
// Pure crypto and string handling: no network, no WebRTC. Used by the device (it answers a watch call
|
|
15
|
+
// with a signed bundle to the caller) and by any agent that places or receives calls.
|
|
16
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
+
}
|
|
22
|
+
Object.defineProperty(o, k2, desc);
|
|
23
|
+
}) : (function(o, m, k, k2) {
|
|
24
|
+
if (k2 === undefined) k2 = k;
|
|
25
|
+
o[k2] = m[k];
|
|
26
|
+
}));
|
|
27
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
+
}) : function(o, v) {
|
|
30
|
+
o["default"] = v;
|
|
31
|
+
});
|
|
32
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
33
|
+
var ownKeys = function(o) {
|
|
34
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
35
|
+
var ar = [];
|
|
36
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
37
|
+
return ar;
|
|
38
|
+
};
|
|
39
|
+
return ownKeys(o);
|
|
40
|
+
};
|
|
41
|
+
return function (mod) {
|
|
42
|
+
if (mod && mod.__esModule) return mod;
|
|
43
|
+
var result = {};
|
|
44
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
45
|
+
__setModuleDefault(result, mod);
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
})();
|
|
49
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
|
+
exports.LEGACY_KEY_PASSPHRASE = exports.TS_SKEW_MS = exports.CALL_BUNDLE_VERSION = void 0;
|
|
51
|
+
exports.randomNonce = randomNonce;
|
|
52
|
+
exports.buildCallBundle = buildCallBundle;
|
|
53
|
+
exports.buildSignalBundle = buildSignalBundle;
|
|
54
|
+
exports.signAndEncrypt = signAndEncrypt;
|
|
55
|
+
exports.decryptAndVerify = decryptAndVerify;
|
|
56
|
+
exports.isBundleFresh = isBundleFresh;
|
|
57
|
+
exports.parseSdpFingerprints = parseSdpFingerprints;
|
|
58
|
+
exports.fingerprintsMatch = fingerprintsMatch;
|
|
59
|
+
exports.senderKeyMatchesContact = senderKeyMatchesContact;
|
|
60
|
+
const node_crypto_1 = require("node:crypto");
|
|
61
|
+
const openpgp = __importStar(require("openpgp"));
|
|
62
|
+
exports.CALL_BUNDLE_VERSION = 1;
|
|
63
|
+
/** Reject a bundle whose `ts` has drifted more than this from wall-clock. */
|
|
64
|
+
exports.TS_SKEW_MS = 5 * 60 * 1000;
|
|
65
|
+
/** A legacy server-custody private key is still armored under this constant passphrase. */
|
|
66
|
+
exports.LEGACY_KEY_PASSPHRASE = "salt";
|
|
67
|
+
// Accepts a vault-issued key (already decrypted: no passphrase) or a legacy key armored under the
|
|
68
|
+
// passphrase (salt-fe's constant "salt" unless one is given).
|
|
69
|
+
async function unlockPrivateKey(armoredPrivateKey, passphrase = exports.LEGACY_KEY_PASSPHRASE) {
|
|
70
|
+
const key = await openpgp.readPrivateKey({ armoredKey: armoredPrivateKey });
|
|
71
|
+
if (key.isDecrypted())
|
|
72
|
+
return key;
|
|
73
|
+
return openpgp.decryptKey({ privateKey: key, passphrase });
|
|
74
|
+
}
|
|
75
|
+
/** A 128-bit random nonce, hex (section 4's bundle shape). */
|
|
76
|
+
function randomNonce() {
|
|
77
|
+
return (0, node_crypto_1.randomBytes)(16).toString("hex");
|
|
78
|
+
}
|
|
79
|
+
/** Build the plaintext offer/answer bundle. `dtls_fingerprints` is always derived from `sdp` itself,
|
|
80
|
+
* never supplied: pinning only means something if the fingerprints are the ones in the SDP being sent.
|
|
81
|
+
* `ts` is unix seconds. Throws when the SDP carries no `a=fingerprint:` line. */
|
|
82
|
+
function buildCallBundle(p) {
|
|
83
|
+
const dtls_fingerprints = parseSdpFingerprints(p.sdp);
|
|
84
|
+
if (dtls_fingerprints.length === 0)
|
|
85
|
+
throw new Error("sdp carries no a=fingerprint lines to bind");
|
|
86
|
+
const bundle = {
|
|
87
|
+
v: exports.CALL_BUNDLE_VERSION,
|
|
88
|
+
call_id: p.callId,
|
|
89
|
+
kind: p.kind,
|
|
90
|
+
...(p.sdpType !== undefined ? { sdp_type: p.sdpType } : {}),
|
|
91
|
+
sdp: p.sdp,
|
|
92
|
+
dtls_fingerprints,
|
|
93
|
+
ts: p.ts ?? Math.floor(Date.now() / 1000),
|
|
94
|
+
nonce: p.nonce ?? randomNonce(),
|
|
95
|
+
};
|
|
96
|
+
return bundle;
|
|
97
|
+
}
|
|
98
|
+
/** Build the plaintext trickle-ICE bundle. */
|
|
99
|
+
function buildSignalBundle(p) {
|
|
100
|
+
return {
|
|
101
|
+
v: exports.CALL_BUNDLE_VERSION,
|
|
102
|
+
call_id: p.callId,
|
|
103
|
+
candidates: p.candidates,
|
|
104
|
+
ts: p.ts ?? Math.floor(Date.now() / 1000),
|
|
105
|
+
nonce: p.nonce ?? randomNonce(),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/** Sign `bundle` with the sender's private key and encrypt the result to the receiver's public key.
|
|
109
|
+
* Returns armored ciphertext, ready for the opaque `payload` field of POST /api/v1/calls (or /answer,
|
|
110
|
+
* /signal). `passphrase` only matters for a legacy key that is still locked (default "salt"). */
|
|
111
|
+
async function signAndEncrypt(p) {
|
|
112
|
+
const privateKey = await unlockPrivateKey(p.signingPrivateKey, p.passphrase);
|
|
113
|
+
const publicKey = await openpgp.readKey({ armoredKey: p.receiverPublicKey });
|
|
114
|
+
const message = await openpgp.createMessage({ text: JSON.stringify(p.bundle) });
|
|
115
|
+
return (await openpgp.encrypt({ message, encryptionKeys: publicKey, signingKeys: privateKey }));
|
|
116
|
+
}
|
|
117
|
+
/** Decrypt an armored bundle and check its signature against the SENDER's public key (never a key
|
|
118
|
+
* embedded in the message). Never throws for an ordinary crypto failure (corrupted payload, a signature
|
|
119
|
+
* from the wrong key): those come back as `{ bundle: null, signatureValid: false }` so callers fail
|
|
120
|
+
* closed. A bundle that decrypts but is not JSON is the same. */
|
|
121
|
+
async function decryptAndVerify(p) {
|
|
122
|
+
try {
|
|
123
|
+
const privateKey = await unlockPrivateKey(p.decryptionPrivateKey, p.passphrase);
|
|
124
|
+
const publicKey = await openpgp.readKey({ armoredKey: p.senderPublicKey });
|
|
125
|
+
const message = await openpgp.readMessage({ armoredMessage: p.armored });
|
|
126
|
+
const { data, signatures } = await openpgp.decrypt({
|
|
127
|
+
message,
|
|
128
|
+
decryptionKeys: privateKey,
|
|
129
|
+
verificationKeys: publicKey,
|
|
130
|
+
expectSigned: false, // the signatures are inspected below
|
|
131
|
+
});
|
|
132
|
+
let signatureValid = false;
|
|
133
|
+
if (signatures && signatures.length > 0) {
|
|
134
|
+
try {
|
|
135
|
+
await signatures[0].verified;
|
|
136
|
+
signatureValid = true;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
signatureValid = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
let bundle = null;
|
|
143
|
+
try {
|
|
144
|
+
bundle = JSON.parse(data);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
return { bundle: null, signatureValid: false };
|
|
148
|
+
}
|
|
149
|
+
return { bundle, signatureValid };
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return { bundle: null, signatureValid: false };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/** Section 4's replay half: `call_id` and `ts` bind the bundle to THIS call. No crypto here. `ts` is
|
|
156
|
+
* unix SECONDS and must be within 5 minutes of `now` (milliseconds, default the wall clock); when
|
|
157
|
+
* `expectedCallId` is given the bundle's `call_id` must equal it. */
|
|
158
|
+
function isBundleFresh(p) {
|
|
159
|
+
const { bundle, expectedCallId } = p;
|
|
160
|
+
const now = p.now ?? Date.now();
|
|
161
|
+
if (!bundle)
|
|
162
|
+
return false;
|
|
163
|
+
if (expectedCallId && bundle.call_id !== expectedCallId)
|
|
164
|
+
return false;
|
|
165
|
+
if (typeof bundle.ts !== "number")
|
|
166
|
+
return false;
|
|
167
|
+
return Math.abs(now - bundle.ts * 1000) <= exports.TS_SKEW_MS;
|
|
168
|
+
}
|
|
169
|
+
function normalizeFingerprint(fp) {
|
|
170
|
+
return String(fp ?? "").trim().toUpperCase().replace(/\s+/g, " ");
|
|
171
|
+
}
|
|
172
|
+
/** Every `a=fingerprint:<algo> <hex>` line of an SDP, normalised (trimmed, upper-cased, whitespace
|
|
173
|
+
* collapsed): `"SHA-256 AB:CD:..."`. Accepts CRLF or LF line ends. */
|
|
174
|
+
function parseSdpFingerprints(sdp) {
|
|
175
|
+
return Array.from(String(sdp || "").matchAll(/^a=fingerprint:(.+)$/gim)).map((m) => normalizeFingerprint(m[1]));
|
|
176
|
+
}
|
|
177
|
+
/** Section 4's pin: every fingerprint in the SDP being applied must be one the signed bundle carries
|
|
178
|
+
* (case and whitespace insensitive). False when either side has none. A mismatch means: hang up with
|
|
179
|
+
* reason `error`, do not apply the SDP. */
|
|
180
|
+
function fingerprintsMatch(signed, actualSdp) {
|
|
181
|
+
const signedSet = new Set((signed || []).map(normalizeFingerprint));
|
|
182
|
+
const actual = parseSdpFingerprints(actualSdp);
|
|
183
|
+
if (actual.length === 0 || signedSet.size === 0)
|
|
184
|
+
return false;
|
|
185
|
+
return actual.every((fp) => signedSet.has(fp));
|
|
186
|
+
}
|
|
187
|
+
/** The verified badge: only when the signing key's own fingerprint matches the stored public key. */
|
|
188
|
+
async function senderKeyMatchesContact(p) {
|
|
189
|
+
if (!p.senderPublicKey || !p.contactPublicKey)
|
|
190
|
+
return false;
|
|
191
|
+
try {
|
|
192
|
+
const [a, b] = await Promise.all([openpgp.readKey({ armoredKey: p.senderPublicKey }), openpgp.readKey({ armoredKey: p.contactPublicKey })]);
|
|
193
|
+
return a.getFingerprint().toUpperCase() === b.getFingerprint().toUpperCase();
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=callBundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callBundle.js","sourceRoot":"","sources":["../src/callBundle.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
import { type SaltId } from "./ids.js";
|
|
2
2
|
import { type IdentityClaims, type IdentitySections, type SignedCard } from "./identity.js";
|
|
3
3
|
import { type HeldRecoveryShare, type RecoveryGuardianInvitation, type RecoveryRequestForGuardian } from "./recovery.js";
|
|
4
|
+
import { type HelpReason, type HelpRequest } from "./help.js";
|
|
5
|
+
/** What `client.askForHelp` / `ctx.askForHelp` take. The question rides end-to-end encrypted in the 1:1; Salt learns only the reason. */
|
|
6
|
+
export interface AskForHelpParams {
|
|
7
|
+
/** The 1:1 with the person. Exactly two non-observer members, else the call is refused before anything is sent. */
|
|
8
|
+
chatId: SaltId;
|
|
9
|
+
/** In plain words, at most 500 characters. */
|
|
10
|
+
question: string;
|
|
11
|
+
reason: HelpReason;
|
|
12
|
+
/** The live device session this question is about; the person's phone then shows the computer's screen. */
|
|
13
|
+
deviceSessionId?: SaltId;
|
|
14
|
+
/** This agent's own account id, when the caller already knows it (saves a `whoAmI` call). */
|
|
15
|
+
selfId?: SaltId;
|
|
16
|
+
}
|
|
17
|
+
/** `GET /api/v1/help_requests` filters. */
|
|
18
|
+
export interface ListHelpRequestsParams {
|
|
19
|
+
state?: "open" | "all";
|
|
20
|
+
chatId?: SaltId;
|
|
21
|
+
/** Return rows older than this help request id. */
|
|
22
|
+
before?: SaltId;
|
|
23
|
+
}
|
|
4
24
|
export interface IdentityDisclosure {
|
|
5
25
|
id: string;
|
|
6
26
|
section_keys: string[];
|
|
@@ -314,6 +334,8 @@ export interface SendAttachmentParams {
|
|
|
314
334
|
* chatbox.jsx sendOneAttachment uses, so a captionless attachment reads
|
|
315
335
|
* identically whether it came from a person or an agent. */
|
|
316
336
|
caption?: string;
|
|
337
|
+
/** A message in this chat to link as the one being answered (`reply_to_message_id`). Not defaulted here. */
|
|
338
|
+
replyTo?: SaltId;
|
|
317
339
|
}
|
|
318
340
|
/**
|
|
319
341
|
* Basename only, control characters and path separators stripped, capped at
|
|
@@ -991,6 +1013,7 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
991
1013
|
username: string;
|
|
992
1014
|
}>, mentions?: SaltId[], opts?: {
|
|
993
1015
|
quiet?: boolean;
|
|
1016
|
+
replyTo?: SaltId;
|
|
994
1017
|
}): Promise<PostMessageResult>;
|
|
995
1018
|
/**
|
|
996
1019
|
* Sends a file into a chat, encrypted exactly the way salt-fe's
|
|
@@ -1024,7 +1047,20 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
1024
1047
|
* which hands the body straight through as ctx.text (ctx.encrypted
|
|
1025
1048
|
* false) instead of attempting PGP decrypt.
|
|
1026
1049
|
*/
|
|
1027
|
-
postPlainMessage(apiKey: string, chatId: SaltId, text: string
|
|
1050
|
+
postPlainMessage(apiKey: string, chatId: SaltId, text: string, opts?: {
|
|
1051
|
+
replyTo?: SaltId;
|
|
1052
|
+
}): Promise<unknown>;
|
|
1053
|
+
/**
|
|
1054
|
+
* Show Me: ask the person in a 1:1 for help. Posts the question as an ordinary end-to-end
|
|
1055
|
+
* encrypted message (`[[SALT-HELP reason=...]]` + the question, help.ts) and then opens the
|
|
1056
|
+
* help request on Salt (`POST /api/v1/help_requests`), which rings the person's phone and,
|
|
1057
|
+
* with `deviceSessionId`, tells the computer to start sending stills. Returns the help request.
|
|
1058
|
+
* Throws before sending anything when the chat is not an encrypted 1:1, the question is empty
|
|
1059
|
+
* or over 500 characters, or no one in it has a usable key. A 422 from Salt (a rate limit of 6
|
|
1060
|
+
* an hour per chat, or a device session that is not yours) surfaces as a SaltApiError with
|
|
1061
|
+
* the server's sentence, after the question was already posted: the message stays in the chat.
|
|
1062
|
+
*/
|
|
1063
|
+
askForHelp(apiKey: string, params: AskForHelpParams): Promise<HelpRequest>;
|
|
1028
1064
|
/**
|
|
1029
1065
|
* Open rooms (0.6x): declares what this identity wants delivered from
|
|
1030
1066
|
* a chat it isn't necessarily addressed in every message of --
|
|
@@ -1704,6 +1740,13 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
1704
1740
|
counted: boolean;
|
|
1705
1741
|
}>;
|
|
1706
1742
|
};
|
|
1743
|
+
helpRequests: {
|
|
1744
|
+
/** Newest first, at most 50; `state` defaults to `open` on the server. */
|
|
1745
|
+
list(apiKey: string, params?: ListHelpRequestsParams): Promise<HelpRequest[]>;
|
|
1746
|
+
get(apiKey: string, id: SaltId): Promise<HelpRequest>;
|
|
1747
|
+
/** Close a request you asked: `answered` (you have what you need) or `withdrawn`. Stops the computer's stills. */
|
|
1748
|
+
close(apiKey: string, id: SaltId, status: "answered" | "withdrawn"): Promise<HelpRequest>;
|
|
1749
|
+
};
|
|
1707
1750
|
apps: {
|
|
1708
1751
|
/**
|
|
1709
1752
|
* Creates an app + version 1. `forId` builds it for someone else (that
|
|
@@ -1803,6 +1846,7 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
1803
1846
|
username: string;
|
|
1804
1847
|
}>, mentions?: SaltId[], opts?: {
|
|
1805
1848
|
quiet?: boolean;
|
|
1849
|
+
replyTo?: SaltId;
|
|
1806
1850
|
}): Promise<PostMessageResult>;
|
|
1807
1851
|
/**
|
|
1808
1852
|
* Sends a file into a chat, encrypted exactly the way salt-fe's
|
|
@@ -1836,7 +1880,20 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
1836
1880
|
* which hands the body straight through as ctx.text (ctx.encrypted
|
|
1837
1881
|
* false) instead of attempting PGP decrypt.
|
|
1838
1882
|
*/
|
|
1839
|
-
postPlainMessage(apiKey: string, chatId: SaltId, text: string
|
|
1883
|
+
postPlainMessage(apiKey: string, chatId: SaltId, text: string, opts?: {
|
|
1884
|
+
replyTo?: SaltId;
|
|
1885
|
+
}): Promise<unknown>;
|
|
1886
|
+
/**
|
|
1887
|
+
* Show Me: ask the person in a 1:1 for help. Posts the question as an ordinary end-to-end
|
|
1888
|
+
* encrypted message (`[[SALT-HELP reason=...]]` + the question, help.ts) and then opens the
|
|
1889
|
+
* help request on Salt (`POST /api/v1/help_requests`), which rings the person's phone and,
|
|
1890
|
+
* with `deviceSessionId`, tells the computer to start sending stills. Returns the help request.
|
|
1891
|
+
* Throws before sending anything when the chat is not an encrypted 1:1, the question is empty
|
|
1892
|
+
* or over 500 characters, or no one in it has a usable key. A 422 from Salt (a rate limit of 6
|
|
1893
|
+
* an hour per chat, or a device session that is not yours) surfaces as a SaltApiError with
|
|
1894
|
+
* the server's sentence, after the question was already posted: the message stays in the chat.
|
|
1895
|
+
*/
|
|
1896
|
+
askForHelp(apiKey: string, params: AskForHelpParams): Promise<HelpRequest>;
|
|
1840
1897
|
/**
|
|
1841
1898
|
* Open rooms (0.6x): declares what this identity wants delivered from
|
|
1842
1899
|
* a chat it isn't necessarily addressed in every message of --
|
|
@@ -2516,6 +2573,13 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
2516
2573
|
counted: boolean;
|
|
2517
2574
|
}>;
|
|
2518
2575
|
};
|
|
2576
|
+
helpRequests: {
|
|
2577
|
+
/** Newest first, at most 50; `state` defaults to `open` on the server. */
|
|
2578
|
+
list(apiKey: string, params?: ListHelpRequestsParams): Promise<HelpRequest[]>;
|
|
2579
|
+
get(apiKey: string, id: SaltId): Promise<HelpRequest>;
|
|
2580
|
+
/** Close a request you asked: `answered` (you have what you need) or `withdrawn`. Stops the computer's stills. */
|
|
2581
|
+
close(apiKey: string, id: SaltId, status: "answered" | "withdrawn"): Promise<HelpRequest>;
|
|
2582
|
+
};
|
|
2519
2583
|
apps: {
|
|
2520
2584
|
/**
|
|
2521
2585
|
* Creates an app + version 1. `forId` builds it for someone else (that
|
|
@@ -2615,6 +2679,7 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
2615
2679
|
username: string;
|
|
2616
2680
|
}>, mentions?: SaltId[], opts?: {
|
|
2617
2681
|
quiet?: boolean;
|
|
2682
|
+
replyTo?: SaltId;
|
|
2618
2683
|
}): Promise<PostMessageResult>;
|
|
2619
2684
|
/**
|
|
2620
2685
|
* Sends a file into a chat, encrypted exactly the way salt-fe's
|
|
@@ -2648,7 +2713,20 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
2648
2713
|
* which hands the body straight through as ctx.text (ctx.encrypted
|
|
2649
2714
|
* false) instead of attempting PGP decrypt.
|
|
2650
2715
|
*/
|
|
2651
|
-
postPlainMessage(apiKey: string, chatId: SaltId, text: string
|
|
2716
|
+
postPlainMessage(apiKey: string, chatId: SaltId, text: string, opts?: {
|
|
2717
|
+
replyTo?: SaltId;
|
|
2718
|
+
}): Promise<unknown>;
|
|
2719
|
+
/**
|
|
2720
|
+
* Show Me: ask the person in a 1:1 for help. Posts the question as an ordinary end-to-end
|
|
2721
|
+
* encrypted message (`[[SALT-HELP reason=...]]` + the question, help.ts) and then opens the
|
|
2722
|
+
* help request on Salt (`POST /api/v1/help_requests`), which rings the person's phone and,
|
|
2723
|
+
* with `deviceSessionId`, tells the computer to start sending stills. Returns the help request.
|
|
2724
|
+
* Throws before sending anything when the chat is not an encrypted 1:1, the question is empty
|
|
2725
|
+
* or over 500 characters, or no one in it has a usable key. A 422 from Salt (a rate limit of 6
|
|
2726
|
+
* an hour per chat, or a device session that is not yours) surfaces as a SaltApiError with
|
|
2727
|
+
* the server's sentence, after the question was already posted: the message stays in the chat.
|
|
2728
|
+
*/
|
|
2729
|
+
askForHelp(apiKey: string, params: AskForHelpParams): Promise<HelpRequest>;
|
|
2652
2730
|
/**
|
|
2653
2731
|
* Open rooms (0.6x): declares what this identity wants delivered from
|
|
2654
2732
|
* a chat it isn't necessarily addressed in every message of --
|
|
@@ -3328,6 +3406,13 @@ export declare function createSaltClient(options: SaltClientOptions): {
|
|
|
3328
3406
|
counted: boolean;
|
|
3329
3407
|
}>;
|
|
3330
3408
|
};
|
|
3409
|
+
helpRequests: {
|
|
3410
|
+
/** Newest first, at most 50; `state` defaults to `open` on the server. */
|
|
3411
|
+
list(apiKey: string, params?: ListHelpRequestsParams): Promise<HelpRequest[]>;
|
|
3412
|
+
get(apiKey: string, id: SaltId): Promise<HelpRequest>;
|
|
3413
|
+
/** Close a request you asked: `answered` (you have what you need) or `withdrawn`. Stops the computer's stills. */
|
|
3414
|
+
close(apiKey: string, id: SaltId, status: "answered" | "withdrawn"): Promise<HelpRequest>;
|
|
3415
|
+
};
|
|
3331
3416
|
apps: {
|
|
3332
3417
|
/**
|
|
3333
3418
|
* Creates an app + version 1. `forId` builds it for someone else (that
|