@zeph-to/mcp-server 1.15.2 → 2.0.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 +20 -7
- package/dist/api-client.d.ts +1 -1
- package/dist/api-client.d.ts.map +1 -1
- package/dist/crypto.d.ts +65 -42
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +169 -143
- package/dist/e2e-fallback.d.ts +18 -5
- package/dist/e2e-fallback.d.ts.map +1 -1
- package/dist/e2e-fallback.js +38 -11
- package/dist/index.js +8 -3
- package/dist/mime.d.ts.map +1 -1
- package/dist/mime.js +20 -0
- package/dist/tools/ask.d.ts.map +1 -1
- package/dist/tools/ask.js +12 -22
- package/dist/tools/file.d.ts.map +1 -1
- package/dist/tools/file.js +105 -38
- package/dist/tools/notify.d.ts.map +1 -1
- package/dist/tools/notify.js +44 -40
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/tools/ask.js
CHANGED
|
@@ -5,7 +5,6 @@ const zod_1 = require("zod");
|
|
|
5
5
|
const error_format_js_1 = require("../error-format.js");
|
|
6
6
|
const poll_js_1 = require("../poll.js");
|
|
7
7
|
const config_js_1 = require("../config.js");
|
|
8
|
-
const crypto_js_1 = require("../crypto.js");
|
|
9
8
|
const mime_js_1 = require("../mime.js");
|
|
10
9
|
const sanitize_js_1 = require("../sanitize.js");
|
|
11
10
|
// The device feed shows a short preview of the body. Anything longer than
|
|
@@ -23,7 +22,7 @@ const buildAskMarkdown = (title, body, actions) => {
|
|
|
23
22
|
};
|
|
24
23
|
const registerAskTool = (server, client, config, waiter) => {
|
|
25
24
|
server.registerTool('zeph_ask', {
|
|
26
|
-
description: 'Ask the user a question with optional quick-reply buttons and a text input field. Combines prompt (buttons) and input (text) in a single notification. The user can either tap a button or type a response. Blocks until the user responds or the timeout is reached. Requires ZEPH_HOOK_ID environment variable.',
|
|
25
|
+
description: 'Ask the user a question with optional quick-reply buttons and a text input field. Combines prompt (buttons) and input (text) in a single notification. The user can either tap a button or type a response. Blocks until the user responds or the timeout is reached. Requires ZEPH_HOOK_ID environment variable. NOTE: unlike zeph_notify and zeph_file, this tool is never end-to-end encrypted — the hook route it uses cannot carry the sender key — so do not put secrets in the question or expect a private answer.',
|
|
27
26
|
annotations: {
|
|
28
27
|
readOnlyHint: false,
|
|
29
28
|
destructiveHint: false,
|
|
@@ -77,30 +76,21 @@ const registerAskTool = (server, client, config, waiter) => {
|
|
|
77
76
|
if (exceedsPreview && cleanBody) {
|
|
78
77
|
const fileName = 'response.md';
|
|
79
78
|
const fileType = (0, mime_js_1.inferMimeType)(fileName);
|
|
80
|
-
const canEncrypt = !!(0, crypto_js_1.getKeyPair)() && !!(0, crypto_js_1.getPublicKey)();
|
|
81
79
|
// Self-contained Markdown so the file alone tells the whole story.
|
|
82
80
|
const fileMarkdown = buildAskMarkdown(title, cleanBody, effectiveActions);
|
|
83
81
|
const fileBytes = new TextEncoder().encode(fileMarkdown).byteLength;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
fileEncryptedKey = encrypted.encryptedKey;
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
console.error('[Crypto] File encryption failed, sending plaintext:', err);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const upload = await client.requestUpload({ fileName, fileType: uploadContentType, fileSize: typeof uploadContent === 'string' ? fileBytes : uploadContent.length });
|
|
101
|
-
await client.uploadToS3(upload.data.uploadUrl, uploadContent, uploadContentType);
|
|
82
|
+
// Deliberately NOT encrypted, unlike zeph_notify / zeph_file.
|
|
83
|
+
//
|
|
84
|
+
// This attachment rides `POST /hooks/:id/trigger`, which creates a
|
|
85
|
+
// plaintext push: it neither accepts nor persists `isEncrypted` or
|
|
86
|
+
// `senderPublicKey` (apps/server/src/functions/hooks.ts). Clients
|
|
87
|
+
// gate decryption on both, so an encrypted attachment here would be
|
|
88
|
+
// downloaded as raw ciphertext named response.md. Encrypting this
|
|
89
|
+
// path needs the hook route to carry the sender key first.
|
|
90
|
+
const upload = await client.requestUpload({ fileName, fileType, fileSize: fileBytes });
|
|
91
|
+
await client.uploadToS3(upload.data.uploadUrl, fileMarkdown, fileType);
|
|
102
92
|
triggerBody = cleanBody.slice(0, PREVIEW_LENGTH) + '...';
|
|
103
|
-
files = [{ fileKey: upload.data.fileKey, fileName, fileSize: fileBytes, fileType
|
|
93
|
+
files = [{ fileKey: upload.data.fileKey, fileName, fileSize: fileBytes, fileType }];
|
|
104
94
|
}
|
|
105
95
|
const trigger = await client.triggerHook(config.hookId, {
|
|
106
96
|
title: pushTitle,
|
package/dist/tools/file.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/tools/file.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/tools/file.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AA4BrE,eAAO,MAAM,gBAAgB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,SAmIjG,CAAC"}
|
package/dist/tools/file.js
CHANGED
|
@@ -2,77 +2,144 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerFileTool = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
+
const promises_1 = require("node:fs/promises");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
5
7
|
const config_js_1 = require("../config.js");
|
|
6
8
|
const error_format_js_1 = require("../error-format.js");
|
|
7
9
|
const crypto_js_1 = require("../crypto.js");
|
|
8
10
|
const e2e_fallback_js_1 = require("../e2e-fallback.js");
|
|
9
11
|
const mime_js_1 = require("../mime.js");
|
|
12
|
+
/**
|
|
13
|
+
* Collapse the two accepted input shapes into one payload.
|
|
14
|
+
*
|
|
15
|
+
* A `filePath` stays a Buffer end to end — images and PDFs only survive the
|
|
16
|
+
* trip as raw bytes, and reading from disk keeps a several-hundred-KB
|
|
17
|
+
* attachment out of the tool call itself (base64 in the arguments would
|
|
18
|
+
* inflate it by a third and burn the agent's context).
|
|
19
|
+
*/
|
|
20
|
+
const resolvePayload = async (args) => {
|
|
21
|
+
if (args.filePath) {
|
|
22
|
+
const body = await (0, promises_1.readFile)(args.filePath);
|
|
23
|
+
return { fileName: args.fileName ?? (0, node_path_1.basename)(args.filePath), body, size: body.byteLength };
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
fileName: args.fileName,
|
|
27
|
+
body: args.content,
|
|
28
|
+
size: new TextEncoder().encode(args.content).byteLength,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
10
31
|
const registerFileTool = (server, client, config) => {
|
|
11
32
|
server.registerTool('zeph_file', {
|
|
12
|
-
description: 'Send a
|
|
33
|
+
description: 'Send a file to the user\'s device. Pass `filePath` to send a file that already exists on disk — images (png/jpg/gif/webp/heic), PDFs, logs, anything. Pass `content` instead to send text you generated. Images render inline on the device.',
|
|
13
34
|
annotations: {
|
|
14
35
|
readOnlyHint: false,
|
|
15
36
|
destructiveHint: false,
|
|
16
37
|
openWorldHint: true,
|
|
17
38
|
},
|
|
18
39
|
inputSchema: {
|
|
19
|
-
|
|
20
|
-
|
|
40
|
+
filePath: zod_1.z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('Absolute path to a local file to send. Required for images, PDFs, and any other binary — never base64 a file into `content`.'),
|
|
44
|
+
content: zod_1.z.string().optional().describe('Text content of the file. Use only for text you generated; requires `fileName`.'),
|
|
45
|
+
fileName: zod_1.z
|
|
46
|
+
.string()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe('File name with extension (e.g., "report.txt"). Required with `content`; defaults to the basename of `filePath`.'),
|
|
21
49
|
title: zod_1.z.string().optional().describe('Notification title (defaults to fileName)'),
|
|
22
50
|
targetDeviceId: zod_1.z.string().optional().describe('Target device ID. Omit to use configured default or send to all devices.'),
|
|
23
51
|
},
|
|
24
|
-
}, async ({ fileName, content, title, targetDeviceId }) => {
|
|
52
|
+
}, async ({ filePath, fileName: fileNameArg, content, title, targetDeviceId }) => {
|
|
53
|
+
if (!filePath && content === undefined) {
|
|
54
|
+
return (0, error_format_js_1.errorResult)({
|
|
55
|
+
error: 'INVALID_INPUT',
|
|
56
|
+
message: 'Either filePath or content is required',
|
|
57
|
+
suggestion: 'Pass filePath for a file on disk (images, PDFs), or content + fileName for generated text',
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
if (!filePath && !fileNameArg) {
|
|
61
|
+
return (0, error_format_js_1.errorResult)({
|
|
62
|
+
error: 'INVALID_INPUT',
|
|
63
|
+
message: 'fileName is required when sending content',
|
|
64
|
+
suggestion: 'Add fileName with an extension, e.g. "report.md"',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
let payload;
|
|
68
|
+
try {
|
|
69
|
+
// Resolved once, outside `send`, so the plaintext retry below doesn't
|
|
70
|
+
// read the file from disk a second time.
|
|
71
|
+
payload = await resolvePayload({ filePath, content, fileName: fileNameArg });
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
return (0, error_format_js_1.errorResult)({
|
|
75
|
+
error: 'FILE_READ_FAILED',
|
|
76
|
+
message: err instanceof Error ? err.message : String(err),
|
|
77
|
+
suggestion: 'Check that filePath is an absolute path to a readable file',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const { fileName, body, size: originalSize } = payload;
|
|
25
81
|
// Runs a second time as plaintext if the server refuses E2E (Pro-only,
|
|
26
82
|
// ADR-0008). The retry re-uploads the file unencrypted, so the whole
|
|
27
83
|
// upload-then-send sequence has to sit inside this closure.
|
|
28
|
-
const send = async (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
let
|
|
35
|
-
|
|
36
|
-
if (canEncrypt) {
|
|
84
|
+
const send = async (recipients) => {
|
|
85
|
+
const pushTitle = (0, config_js_1.formatPushTitle)(config.projectName, title ?? fileName);
|
|
86
|
+
// Step 1: Encrypt the attachment and the push body together, before
|
|
87
|
+
// anything is uploaded. Doing them one at a time around the upload let
|
|
88
|
+
// a failure land in between and ship ciphertext under a push with no
|
|
89
|
+
// `isEncrypted` — an attachment no client would even try to open.
|
|
90
|
+
let encrypted = null;
|
|
91
|
+
if (recipients) {
|
|
37
92
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
fileIv = encrypted.iv;
|
|
43
|
-
fileEncryptedKey = encrypted.encryptedKey;
|
|
93
|
+
encrypted = {
|
|
94
|
+
file: await (0, crypto_js_1.encryptFileForDevices)(body, recipients),
|
|
95
|
+
push: await (0, crypto_js_1.encryptPushBodyForDevices)({ title: pushTitle }, recipients),
|
|
96
|
+
};
|
|
44
97
|
}
|
|
45
98
|
catch (err) {
|
|
46
|
-
console.error('[Crypto]
|
|
99
|
+
console.error('[Crypto] Encryption failed, sending plaintext:', err);
|
|
47
100
|
}
|
|
48
101
|
}
|
|
102
|
+
const uploadContent = encrypted?.file.ciphertext ?? body;
|
|
103
|
+
const uploadType = encrypted ? 'application/octet-stream' : (0, mime_js_1.inferMimeType)(fileName);
|
|
104
|
+
const uploadSize = encrypted ? encrypted.file.ciphertext.length : originalSize;
|
|
49
105
|
// Step 2: Request upload URL
|
|
50
|
-
const upload = await client.requestUpload({ fileName, fileType, fileSize: uploadSize });
|
|
106
|
+
const upload = await client.requestUpload({ fileName, fileType: uploadType, fileSize: uploadSize });
|
|
51
107
|
// Step 3: Upload content to S3
|
|
52
|
-
await client.uploadToS3(upload.data.uploadUrl, uploadContent,
|
|
53
|
-
// Step 4: Send
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
title: pushTitle,
|
|
108
|
+
await client.uploadToS3(upload.data.uploadUrl, uploadContent, uploadType);
|
|
109
|
+
// Step 4: Send the push. `fileType` on the descriptor stays the real
|
|
110
|
+
// type — it drives how the client renders the decrypted bytes.
|
|
111
|
+
const pushPayload = {
|
|
112
|
+
title: encrypted ? undefined : pushTitle,
|
|
57
113
|
type: 'file',
|
|
58
|
-
files: [{
|
|
114
|
+
files: [{
|
|
115
|
+
fileKey: upload.data.fileKey,
|
|
116
|
+
fileName,
|
|
117
|
+
fileSize: originalSize,
|
|
118
|
+
fileType: (0, mime_js_1.inferMimeType)(fileName),
|
|
119
|
+
iv: encrypted?.file.iv,
|
|
120
|
+
deviceKeyMap: encrypted?.file.deviceKeyMap,
|
|
121
|
+
}],
|
|
59
122
|
targetDeviceId: targetDeviceId ?? config.deviceId,
|
|
60
123
|
sessionId: config.sessionId,
|
|
124
|
+
...(encrypted && {
|
|
125
|
+
body: encrypted.push.body,
|
|
126
|
+
isEncrypted: encrypted.push.isEncrypted,
|
|
127
|
+
deviceKeyMap: encrypted.push.deviceKeyMap,
|
|
128
|
+
senderPublicKey: encrypted.push.senderPublicKey,
|
|
129
|
+
}),
|
|
61
130
|
};
|
|
62
|
-
if (canEncrypt) {
|
|
63
|
-
try {
|
|
64
|
-
const enc = await (0, crypto_js_1.encryptPushBodyForSelf)({ title: pushTitle });
|
|
65
|
-
pushPayload = { ...pushPayload, title: undefined, body: enc.body, isEncrypted: enc.isEncrypted, encryptedKey: enc.encryptedKey, senderPublicKey: enc.senderPublicKey };
|
|
66
|
-
}
|
|
67
|
-
catch (err) {
|
|
68
|
-
console.error('[Crypto] Push encryption failed, sending plaintext:', err);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
131
|
const result = await client.sendPush(pushPayload);
|
|
72
|
-
|
|
132
|
+
// Report what actually went out — an encryption failure above falls
|
|
133
|
+
// back to plaintext, so the recipient list alone would over-claim.
|
|
134
|
+
return (0, error_format_js_1.textResult)({
|
|
135
|
+
pushId: result.data.pushId,
|
|
136
|
+
fileKey: upload.data.fileKey,
|
|
137
|
+
fileSize: originalSize,
|
|
138
|
+
encrypted: !!encrypted,
|
|
139
|
+
});
|
|
73
140
|
};
|
|
74
141
|
try {
|
|
75
|
-
return await (0, e2e_fallback_js_1.withPlaintextFallback)(send);
|
|
142
|
+
return await (0, e2e_fallback_js_1.withPlaintextFallback)(client, send);
|
|
76
143
|
}
|
|
77
144
|
catch (err) {
|
|
78
145
|
return (0, error_format_js_1.formatToolError)(err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/tools/notify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAUrE,eAAO,MAAM,kBAAkB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,
|
|
1
|
+
{"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../src/tools/notify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,OAAO,EAAmB,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAUrE,eAAO,MAAM,kBAAkB,GAAI,QAAQ,SAAS,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,SAuInG,CAAC"}
|
package/dist/tools/notify.js
CHANGED
|
@@ -32,7 +32,7 @@ const registerNotifyTool = (server, client, config) => {
|
|
|
32
32
|
}, async ({ title, body, url, priority, targetDeviceId }) => {
|
|
33
33
|
// Runs a second time as plaintext if the server refuses E2E (Pro-only,
|
|
34
34
|
// ADR-0008) — hence everything after the encryption decision lives here.
|
|
35
|
-
const send = async (
|
|
35
|
+
const send = async (recipients) => {
|
|
36
36
|
const deviceId = targetDeviceId ?? config.deviceId;
|
|
37
37
|
const pushTitle = (0, config_js_1.formatPushTitle)(config.projectName, title);
|
|
38
38
|
// Strip any tool-call markup that leaked into the body argument.
|
|
@@ -45,70 +45,74 @@ const registerNotifyTool = (server, client, config) => {
|
|
|
45
45
|
// Self-contained Markdown so the file alone carries the full text.
|
|
46
46
|
const fileMarkdown = `# ${title}\n\n${cleanBody}`;
|
|
47
47
|
const fileBytes = new TextEncoder().encode(fileMarkdown).byteLength;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
let
|
|
54
|
-
if (
|
|
48
|
+
const preview = cleanBody.slice(0, PREVIEW_LENGTH) + '...';
|
|
49
|
+
// Encrypt the attachment and the push body together, before anything
|
|
50
|
+
// is uploaded. Doing them one at a time around the upload let a
|
|
51
|
+
// failure land in between and ship ciphertext under a push with no
|
|
52
|
+
// `isEncrypted` — an attachment no client would even try to open.
|
|
53
|
+
let encrypted = null;
|
|
54
|
+
if (recipients) {
|
|
55
55
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
fileEncryptedKey = encrypted.encryptedKey;
|
|
61
|
-
fileEncrypted = true;
|
|
56
|
+
encrypted = {
|
|
57
|
+
file: await (0, crypto_js_1.encryptFileForDevices)(fileMarkdown, recipients),
|
|
58
|
+
push: await (0, crypto_js_1.encryptPushBodyForDevices)({ title: pushTitle, body: preview, url }, recipients),
|
|
59
|
+
};
|
|
62
60
|
}
|
|
63
61
|
catch (err) {
|
|
64
|
-
console.error('[Crypto]
|
|
62
|
+
console.error('[Crypto] Encryption failed, sending plaintext:', err);
|
|
65
63
|
}
|
|
66
64
|
}
|
|
65
|
+
const uploadContent = encrypted?.file.ciphertext ?? fileMarkdown;
|
|
66
|
+
const uploadContentType = encrypted ? 'application/octet-stream' : fileType;
|
|
67
67
|
const upload = await client.requestUpload({ fileName, fileType: uploadContentType, fileSize: typeof uploadContent === 'string' ? fileBytes : uploadContent.length });
|
|
68
68
|
await client.uploadToS3(upload.data.uploadUrl, uploadContent, uploadContentType);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
title:
|
|
73
|
-
|
|
74
|
-
|
|
69
|
+
const pushPayload = {
|
|
70
|
+
title: encrypted ? undefined : pushTitle,
|
|
71
|
+
body: encrypted ? encrypted.push.body : preview,
|
|
72
|
+
// Dropped alongside the title when encrypted: the url is already
|
|
73
|
+
// sealed inside the ciphertext, and for a link push it is the whole
|
|
74
|
+
// payload. Leaving the plaintext copy here would hand the server the
|
|
75
|
+
// one thing `isEncrypted` promises it cannot see.
|
|
76
|
+
url: encrypted ? undefined : url,
|
|
75
77
|
type: 'file',
|
|
76
78
|
priority,
|
|
77
|
-
files: [{
|
|
79
|
+
files: [{
|
|
80
|
+
fileKey: upload.data.fileKey,
|
|
81
|
+
fileName,
|
|
82
|
+
fileSize: fileBytes,
|
|
83
|
+
fileType,
|
|
84
|
+
iv: encrypted?.file.iv,
|
|
85
|
+
deviceKeyMap: encrypted?.file.deviceKeyMap,
|
|
86
|
+
}],
|
|
78
87
|
targetDeviceId: deviceId,
|
|
79
88
|
sessionId: config.sessionId,
|
|
89
|
+
...(encrypted && {
|
|
90
|
+
isEncrypted: encrypted.push.isEncrypted,
|
|
91
|
+
deviceKeyMap: encrypted.push.deviceKeyMap,
|
|
92
|
+
senderPublicKey: encrypted.push.senderPublicKey,
|
|
93
|
+
}),
|
|
80
94
|
};
|
|
81
|
-
let pushEncrypted = false;
|
|
82
|
-
if (canEncrypt) {
|
|
83
|
-
try {
|
|
84
|
-
const enc = await (0, crypto_js_1.encryptPushBodyForSelf)({ title: pushTitle, body: preview, url });
|
|
85
|
-
pushPayload = { ...pushPayload, title: undefined, body: enc.body, isEncrypted: enc.isEncrypted, encryptedKey: enc.encryptedKey, senderPublicKey: enc.senderPublicKey };
|
|
86
|
-
pushEncrypted = true;
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
console.error('[Crypto] Push encryption failed, sending plaintext:', err);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
95
|
const result = await client.sendPush(pushPayload);
|
|
93
96
|
// Report what actually went out — an encryption failure above falls
|
|
94
|
-
// back to plaintext, so
|
|
95
|
-
return (0, error_format_js_1.textResult)({ pushId: result.data.pushId, fileKey: upload.data.fileKey, autoFile: true, encrypted:
|
|
97
|
+
// back to plaintext, so the recipient list alone would over-claim.
|
|
98
|
+
return (0, error_format_js_1.textResult)({ pushId: result.data.pushId, fileKey: upload.data.fileKey, autoFile: true, encrypted: !!encrypted });
|
|
96
99
|
}
|
|
97
100
|
// Short body — encrypt push only
|
|
98
101
|
let pushPayload = {
|
|
99
102
|
title: pushTitle,
|
|
100
103
|
body: cleanBody,
|
|
101
|
-
url,
|
|
104
|
+
url, // replaced below when the encrypted envelope takes over
|
|
102
105
|
type: 'hook',
|
|
103
106
|
priority,
|
|
104
107
|
targetDeviceId: deviceId,
|
|
105
108
|
sessionId: config.sessionId,
|
|
106
109
|
};
|
|
107
110
|
let pushEncrypted = false;
|
|
108
|
-
if (
|
|
111
|
+
if (recipients) {
|
|
109
112
|
try {
|
|
110
|
-
const enc = await (0, crypto_js_1.
|
|
111
|
-
|
|
113
|
+
const enc = await (0, crypto_js_1.encryptPushBodyForDevices)({ title: pushTitle, body: cleanBody, url }, recipients);
|
|
114
|
+
// `url: undefined` for the same reason as `title` — see the file branch.
|
|
115
|
+
pushPayload = { ...pushPayload, title: undefined, url: undefined, body: enc.body, isEncrypted: enc.isEncrypted, deviceKeyMap: enc.deviceKeyMap, senderPublicKey: enc.senderPublicKey };
|
|
112
116
|
pushEncrypted = true;
|
|
113
117
|
}
|
|
114
118
|
catch (err) {
|
|
@@ -119,7 +123,7 @@ const registerNotifyTool = (server, client, config) => {
|
|
|
119
123
|
return (0, error_format_js_1.textResult)({ pushId: result.data.pushId, encrypted: pushEncrypted });
|
|
120
124
|
};
|
|
121
125
|
try {
|
|
122
|
-
return await (0, e2e_fallback_js_1.withPlaintextFallback)(send);
|
|
126
|
+
return await (0, e2e_fallback_js_1.withPlaintextFallback)(client, send);
|
|
123
127
|
}
|
|
124
128
|
catch (err) {
|
|
125
129
|
return (0, error_format_js_1.formatToolError)(err);
|
package/dist/types.d.ts
CHANGED
|
@@ -95,7 +95,13 @@ export interface AttachedFile {
|
|
|
95
95
|
fileSize: number;
|
|
96
96
|
fileType: string;
|
|
97
97
|
iv?: string;
|
|
98
|
-
|
|
98
|
+
/**
|
|
99
|
+
* E2E: the file AES key wrapped once per recipient device, keyed by
|
|
100
|
+
* `deviceId`. Each value is a JSON string `{ encryptedKey, keyIv }`.
|
|
101
|
+
* (The superseded account-wide `encryptedKey` field is not written here —
|
|
102
|
+
* nothing can unwrap it since key escrow was removed.)
|
|
103
|
+
*/
|
|
104
|
+
deviceKeyMap?: Record<string, string>;
|
|
99
105
|
}
|
|
100
106
|
export interface ToolError {
|
|
101
107
|
error: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;QAC5D,QAAQ,CAAC,EAAE;YACT,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;SAC5B,CAAC;KACH,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,UAAU,EAAE,CAAC;IACnB,UAAU,EAAE;QACV,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,OAAO,CAAC;KAClB,CAAC;CACH;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE;QACJ,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;QAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,aAAa,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|