@kehto/services 0.7.0 → 0.8.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/index.d.ts +11 -58
- package/dist/index.js +0 -154
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ServiceHandler, Signer } from '@kehto/runtime';
|
|
2
|
-
import {
|
|
2
|
+
import { NostrFilter, NostrEvent, EventTemplate } from '@napplet/core';
|
|
3
3
|
import { MediaMetadata, MediaAction } from '@napplet/nub/media/types';
|
|
4
4
|
export { MediaAction } from '@napplet/nub/media/types';
|
|
5
5
|
import { NotifySendMessage } from '@napplet/nub/notify/types';
|
|
@@ -181,53 +181,19 @@ declare function createNotificationService(options?: NotificationServiceOptions)
|
|
|
181
181
|
* - signer.nip04.encrypt/decrypt -> DELETED
|
|
182
182
|
* - signer.nip44.encrypt/decrypt -> DELETED (shell encrypts internally
|
|
183
183
|
* inside relay.publishEncrypted)
|
|
184
|
-
* - identity.decrypt -> ADDED in v1.8 as a shell-mediated decrypt request
|
|
185
|
-
* with class gate + typed error union.
|
|
186
184
|
*
|
|
187
|
-
*
|
|
185
|
+
* Identity is strictly read-only per NAP-IDENTITY: napplets learn *about* the
|
|
186
|
+
* user but cannot act *as* the user — no signing, encryption, or decryption.
|
|
187
|
+
* (An `identity.decrypt` capability was briefly added in v1.8 and removed as a
|
|
188
|
+
* spec violation; decryption belongs to the runtime, inline over the wire.)
|
|
188
189
|
*
|
|
189
|
-
* Handles
|
|
190
|
+
* Handles 9 identity.* request types from @napplet/nub/identity. getPublicKey
|
|
190
191
|
* and getRelays return real values sourced from hooks.auth.getSigner(); the
|
|
191
192
|
* remaining 7 (getProfile/getFollows/getList/getZaps/getMutes/getBlocked/
|
|
192
|
-
* getBadges) are stub-level
|
|
193
|
-
*
|
|
193
|
+
* getBadges) are stub-level. Host apps plug real backends via
|
|
194
|
+
* runtime.registerService('identity', realHandler).
|
|
194
195
|
*/
|
|
195
196
|
|
|
196
|
-
type IdentityDecryptErrorCode = 'class-forbidden' | 'signer-denied' | 'signer-unavailable' | 'decrypt-failed' | 'malformed-wrap' | 'impersonation' | 'unsupported-encryption' | 'policy-denied';
|
|
197
|
-
interface Rumor {
|
|
198
|
-
id: string;
|
|
199
|
-
pubkey: string;
|
|
200
|
-
created_at: number;
|
|
201
|
-
kind: number;
|
|
202
|
-
tags: string[][];
|
|
203
|
-
content: string;
|
|
204
|
-
}
|
|
205
|
-
interface IdentityDecryptMessage extends NappletMessage {
|
|
206
|
-
type: 'identity.decrypt';
|
|
207
|
-
id: string;
|
|
208
|
-
event: NostrEvent;
|
|
209
|
-
}
|
|
210
|
-
interface IdentityDecryptResultMessage extends NappletMessage {
|
|
211
|
-
type: 'identity.decrypt.result';
|
|
212
|
-
id: string;
|
|
213
|
-
rumor: Rumor;
|
|
214
|
-
sender: string;
|
|
215
|
-
}
|
|
216
|
-
interface IdentityDecryptErrorMessage extends NappletMessage {
|
|
217
|
-
type: 'identity.decrypt.error';
|
|
218
|
-
id: string;
|
|
219
|
-
error: IdentityDecryptErrorCode;
|
|
220
|
-
}
|
|
221
|
-
interface GiftWrapDecryptResult {
|
|
222
|
-
seal: NostrEvent;
|
|
223
|
-
rumor: Rumor;
|
|
224
|
-
}
|
|
225
|
-
interface HostDecryptBridge {
|
|
226
|
-
nip04Decrypt(senderPubkey: string, ciphertext: string): Promise<string>;
|
|
227
|
-
nip44Decrypt(senderPubkey: string, ciphertext: string): Promise<string>;
|
|
228
|
-
unwrapGiftWrap(wrap: NostrEvent): Promise<GiftWrapDecryptResult>;
|
|
229
|
-
}
|
|
230
|
-
type VerifyEvent = (event: NostrEvent) => boolean | Promise<boolean>;
|
|
231
197
|
/**
|
|
232
198
|
* Options for creating the identity service.
|
|
233
199
|
*
|
|
@@ -246,27 +212,14 @@ interface IdentityServiceOptions {
|
|
|
246
212
|
* availability can change dynamically.
|
|
247
213
|
*/
|
|
248
214
|
getSigner: () => Signer | null;
|
|
249
|
-
/**
|
|
250
|
-
* Return the host decrypt bridge. Called only after outer event signature
|
|
251
|
-
* verification and encryption-mode detection succeed. Null means decrypt is
|
|
252
|
-
* unavailable while the rest of the identity service remains usable.
|
|
253
|
-
*/
|
|
254
|
-
getDecryptor?: () => HostDecryptBridge | null;
|
|
255
|
-
/**
|
|
256
|
-
* Verify a received event before any decrypt attempt. Host shells should
|
|
257
|
-
* wire this to their canonical Nostr event verifier; tests and old hosts
|
|
258
|
-
* default to true for backward compatibility with the 9 read-only actions.
|
|
259
|
-
*/
|
|
260
|
-
verifyEvent?: VerifyEvent;
|
|
261
215
|
}
|
|
262
216
|
/**
|
|
263
217
|
* Create an identity service that handles NIP-5D identity.* envelope messages.
|
|
264
218
|
*
|
|
265
|
-
* Supports
|
|
266
|
-
*
|
|
219
|
+
* Supports the 9 read-only identity.* request types from @napplet/nub/identity.
|
|
220
|
+
* The two nostr-info queries (getPublicKey, getRelays) resolve through the
|
|
267
221
|
* caller-supplied signer; the remaining 7 return default/empty payloads with
|
|
268
222
|
* spec-correct envelope shapes so napplets always receive a result envelope.
|
|
269
|
-
* identity.decrypt delegates to the host decrypt bridge.
|
|
270
223
|
*
|
|
271
224
|
* @param options - Identity service configuration (getSigner)
|
|
272
225
|
* @returns A ServiceHandler ready for runtime.registerService('identity', handler)
|
|
@@ -2028,4 +1981,4 @@ interface HttpUploaderOptions {
|
|
|
2028
1981
|
*/
|
|
2029
1982
|
declare function createHttpUploader(options: HttpUploaderOptions): Uploader;
|
|
2030
1983
|
|
|
2031
|
-
export { type AudioServiceOptions, type AudioSource, type CacheServiceOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type CoordinatedRelayOptions, type
|
|
1984
|
+
export { type AudioServiceOptions, type AudioSource, type CacheServiceOptions, type ConfigSchemaValidation, type ConfigService, type ConfigServiceOptions, type CoordinatedRelayOptions, type HostCacheBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityServiceOptions, type KeysServiceOptions, type MediaMetadataLike, type MediaPlaybackOwner, type MediaServiceOptions, type MediaSessionCreateOptions, type MediaSessionTarget, type MediaSourceRef, type NostrTag, type Notification, type NotificationServiceOptions, type NotifyServiceOptions, type OutboxPublishOptions, type OutboxPublishResult, type OutboxQueryOptions, type OutboxRelayPlan, type OutboxRelayPool, type OutboxResult, type OutboxRouter, type OutboxRouterSubscription, type OutboxServiceOptions, type OutboxStrategy, type OutboxSubscribeOptions, type OutboxSubscriptionSink, type OutboxTarget, type RailServerConfig, type RelayListEntry, type RelayPoolOutboxRouterOptions, type RelayPoolServiceOptions, type ResourceService, type ResourceServiceOptions, type SignEvent, type ThemeService, type ThemeServiceOptions, type UploadDimensions, type UploadRail, type UploadRequest, type UploadResult, type UploadServiceOptions, type UploadState, type UploadStatus, type Uploader, type UploaderContext, createAudioService, createBrowserMediaBridge, createCacheService, createConfigService, createCoordinatedRelay, createHttpUploader, createIdentityService, createKeysService, createMediaService, createNotificationService, createNotifyService, createOutboxService, createRelayPoolOutboxRouter, createRelayPoolService, createResourceService, createThemeService, createUploadService };
|
package/dist/index.js
CHANGED
|
@@ -231,156 +231,6 @@ function createNotificationService(options) {
|
|
|
231
231
|
|
|
232
232
|
// src/identity-service.ts
|
|
233
233
|
var IDENTITY_SERVICE_VERSION = "1.0.0";
|
|
234
|
-
var DECRYPT_ERROR_CODES = [
|
|
235
|
-
"class-forbidden",
|
|
236
|
-
"signer-denied",
|
|
237
|
-
"signer-unavailable",
|
|
238
|
-
"decrypt-failed",
|
|
239
|
-
"malformed-wrap",
|
|
240
|
-
"impersonation",
|
|
241
|
-
"unsupported-encryption",
|
|
242
|
-
"policy-denied"
|
|
243
|
-
];
|
|
244
|
-
var DECRYPT_ERROR_CODE_SET = new Set(DECRYPT_ERROR_CODES);
|
|
245
|
-
function isDecryptErrorCode(value) {
|
|
246
|
-
return typeof value === "string" && DECRYPT_ERROR_CODE_SET.has(value);
|
|
247
|
-
}
|
|
248
|
-
function normalizeDecryptError(error) {
|
|
249
|
-
if (isDecryptErrorCode(error)) return error;
|
|
250
|
-
if (typeof error === "object" && error !== null) {
|
|
251
|
-
const candidate = error;
|
|
252
|
-
if (isDecryptErrorCode(candidate.code)) return candidate.code;
|
|
253
|
-
if (isDecryptErrorCode(candidate.error)) return candidate.error;
|
|
254
|
-
if (isDecryptErrorCode(candidate.message)) return candidate.message;
|
|
255
|
-
}
|
|
256
|
-
return "decrypt-failed";
|
|
257
|
-
}
|
|
258
|
-
function sendDecryptError(id, error, send) {
|
|
259
|
-
const result = {
|
|
260
|
-
type: "identity.decrypt.error",
|
|
261
|
-
id,
|
|
262
|
-
error
|
|
263
|
-
};
|
|
264
|
-
send(result);
|
|
265
|
-
}
|
|
266
|
-
function isStringArrayArray(value) {
|
|
267
|
-
return Array.isArray(value) && value.every(
|
|
268
|
-
(tag) => Array.isArray(tag) && tag.every((part) => typeof part === "string")
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
function isNostrEvent(value) {
|
|
272
|
-
const event = value;
|
|
273
|
-
return typeof event === "object" && event !== null && typeof event.id === "string" && typeof event.pubkey === "string" && typeof event.created_at === "number" && typeof event.kind === "number" && isStringArrayArray(event.tags) && typeof event.content === "string" && typeof event.sig === "string";
|
|
274
|
-
}
|
|
275
|
-
function isRumor(value) {
|
|
276
|
-
const rumor = value;
|
|
277
|
-
return typeof rumor === "object" && rumor !== null && typeof rumor.id === "string" && typeof rumor.pubkey === "string" && typeof rumor.created_at === "number" && typeof rumor.kind === "number" && isStringArrayArray(rumor.tags) && typeof rumor.content === "string";
|
|
278
|
-
}
|
|
279
|
-
function isGiftWrapDecryptResult(value) {
|
|
280
|
-
const result = value;
|
|
281
|
-
return typeof result === "object" && result !== null && isNostrEvent(result.seal) && isRumor(result.rumor);
|
|
282
|
-
}
|
|
283
|
-
function firstDecodedByte(content) {
|
|
284
|
-
const trimmed = content.trim();
|
|
285
|
-
if (trimmed.length === 0) return null;
|
|
286
|
-
const normalized = trimmed.replace(/-/g, "+").replace(/_/g, "/");
|
|
287
|
-
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
288
|
-
try {
|
|
289
|
-
const decoded = atob(padded);
|
|
290
|
-
return decoded.length > 0 ? decoded.charCodeAt(0) : null;
|
|
291
|
-
} catch {
|
|
292
|
-
return null;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
function detectEncryptionMode(event) {
|
|
296
|
-
if (event.kind === 4) return "nip04";
|
|
297
|
-
if (event.kind === 1059) return "nip17";
|
|
298
|
-
if (event.kind === 14 || firstDecodedByte(event.content) === 2) {
|
|
299
|
-
return "nip44-direct";
|
|
300
|
-
}
|
|
301
|
-
return null;
|
|
302
|
-
}
|
|
303
|
-
function rumorFromSignedEvent(event, content) {
|
|
304
|
-
return {
|
|
305
|
-
id: event.id,
|
|
306
|
-
pubkey: event.pubkey,
|
|
307
|
-
kind: event.kind,
|
|
308
|
-
tags: event.tags,
|
|
309
|
-
created_at: event.created_at,
|
|
310
|
-
content
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
async function handleDecrypt(id, message, send, options) {
|
|
314
|
-
const event = message.event;
|
|
315
|
-
if (!isNostrEvent(event)) {
|
|
316
|
-
sendDecryptError(id, "malformed-wrap", send);
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
const verifyEvent = options.verifyEvent ?? (() => true);
|
|
320
|
-
let verified;
|
|
321
|
-
try {
|
|
322
|
-
verified = await Promise.resolve(verifyEvent(event));
|
|
323
|
-
} catch {
|
|
324
|
-
sendDecryptError(id, "malformed-wrap", send);
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
if (!verified) {
|
|
328
|
-
sendDecryptError(id, "malformed-wrap", send);
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
const mode = detectEncryptionMode(event);
|
|
332
|
-
if (!mode) {
|
|
333
|
-
sendDecryptError(id, "unsupported-encryption", send);
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const decryptor = options.getDecryptor?.() ?? null;
|
|
337
|
-
if (!decryptor) {
|
|
338
|
-
sendDecryptError(id, "signer-unavailable", send);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
try {
|
|
342
|
-
if (mode === "nip04") {
|
|
343
|
-
const plaintext = await decryptor.nip04Decrypt(event.pubkey, event.content);
|
|
344
|
-
const result2 = {
|
|
345
|
-
type: "identity.decrypt.result",
|
|
346
|
-
id,
|
|
347
|
-
rumor: rumorFromSignedEvent(event, plaintext),
|
|
348
|
-
sender: event.pubkey
|
|
349
|
-
};
|
|
350
|
-
send(result2);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (mode === "nip44-direct") {
|
|
354
|
-
const plaintext = await decryptor.nip44Decrypt(event.pubkey, event.content);
|
|
355
|
-
const result2 = {
|
|
356
|
-
type: "identity.decrypt.result",
|
|
357
|
-
id,
|
|
358
|
-
rumor: rumorFromSignedEvent(event, plaintext),
|
|
359
|
-
sender: event.pubkey
|
|
360
|
-
};
|
|
361
|
-
send(result2);
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const unwrapped = await decryptor.unwrapGiftWrap(event);
|
|
365
|
-
if (!isGiftWrapDecryptResult(unwrapped)) {
|
|
366
|
-
sendDecryptError(id, "malformed-wrap", send);
|
|
367
|
-
return;
|
|
368
|
-
}
|
|
369
|
-
if (unwrapped.seal.pubkey !== unwrapped.rumor.pubkey) {
|
|
370
|
-
sendDecryptError(id, "impersonation", send);
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
const result = {
|
|
374
|
-
type: "identity.decrypt.result",
|
|
375
|
-
id,
|
|
376
|
-
rumor: unwrapped.rumor,
|
|
377
|
-
sender: unwrapped.seal.pubkey
|
|
378
|
-
};
|
|
379
|
-
send(result);
|
|
380
|
-
} catch (error) {
|
|
381
|
-
sendDecryptError(id, normalizeDecryptError(error), send);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
234
|
function createIdentityService(options) {
|
|
385
235
|
return {
|
|
386
236
|
descriptor: {
|
|
@@ -496,10 +346,6 @@ function createIdentityService(options) {
|
|
|
496
346
|
send(result);
|
|
497
347
|
return;
|
|
498
348
|
}
|
|
499
|
-
case "identity.decrypt": {
|
|
500
|
-
void handleDecrypt(id, message, send, options);
|
|
501
|
-
return;
|
|
502
|
-
}
|
|
503
349
|
default:
|
|
504
350
|
sendError(message.type, `Unknown identity method: ${message.type}`);
|
|
505
351
|
}
|