@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 CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ServiceHandler, Signer } from '@kehto/runtime';
2
- import { NostrEvent, NappletMessage, NostrFilter, EventTemplate } from '@napplet/core';
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
- * See REQUIREMENTS.md DEPS-03 (Phase 15 changelog).
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 10 identity.* request types from @napplet/nub/identity. getPublicKey
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; decrypt delegates to a host-supplied bridge.
193
- * Host apps plug real backends via runtime.registerService('identity', realHandler).
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 all 10 identity.* request types from @napplet/nub/identity. The two
266
- * read-only nostr-info queries (getPublicKey, getRelays) resolve through the
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 GiftWrapDecryptResult, type HostCacheBridge, type HostDecryptBridge, type HostKeyEvent, type HostKeysBridge, type HostMediaBridge, type HttpUploaderOptions, type HttpUploaderRails, type IdentityDecryptErrorCode, type IdentityDecryptErrorMessage, type IdentityDecryptMessage, type IdentityDecryptResultMessage, 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 Rumor, type SignEvent, type ThemeService, type ThemeServiceOptions, type UploadDimensions, type UploadRail, type UploadRequest, type UploadResult, type UploadServiceOptions, type UploadState, type UploadStatus, type Uploader, type UploaderContext, type VerifyEvent, createAudioService, createBrowserMediaBridge, createCacheService, createConfigService, createCoordinatedRelay, createHttpUploader, createIdentityService, createKeysService, createMediaService, createNotificationService, createNotifyService, createOutboxService, createRelayPoolOutboxRouter, createRelayPoolService, createResourceService, createThemeService, createUploadService };
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
  }