Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,19 @@ Use the signer command once at least one encrypted share is saved locally:

Use either `igloo status` or `igloo share status` to decrypt a saved share, connect a temporary bifrost node, and ping each peer. The command prints relay endpoints plus a color-coded list of online/offline peers. Provide `--password` or `--password-file` for automation, and customise relays with `--relays` when needed.

### Echo debugging

- `--debug-echo` — turn on verbose echo logs (both listener and sender). This sets `IGLOO_DEBUG_ECHO=1` for the current run.
- `IGLOO_TEST_RELAY` — optionally pin a specific relay for both desktop and CLI, e.g.

```bash
IGLOO_TEST_RELAY=wss://your-relay igloo share load --debug-echo
```

Desktop and CLI must share at least one relay to exchange echo events.

Behind the scenes, the CLI normalizes Nostr subscribe filters so relays that require a single filter object (instead of a one-element array) accept subscriptions reliably.

## Development scripts

- Node (default)
Expand Down
4 changes: 4 additions & 0 deletions llm/context/igloo-core-readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ with `data === 'echo'` and newer challenge-based requests where `data` is an
even-length hex string. No changes are required on the sender side when
upgrading.

Tip (CLI): when testing end-to-end with igloo-cli and igloo-desktop, you can enable detailed echo logs with the CLI flag `--debug-echo` (sets `IGLOO_DEBUG_ECHO=1` for that run). To guarantee relay overlap, set `IGLOO_TEST_RELAY=wss://your-relay` in both apps.

```typescript
import { awaitShareEcho } from '@frostr/igloo-core';

Expand Down Expand Up @@ -366,6 +368,8 @@ try {
```

`challenge` must be an even-length hexadecimal string (32 bytes / 64 hex characters recommended).

Troubleshooting: some Nostr relays reject a single-element filter array during subscription. igloo-cli patches `nostr-tools`’s `SimplePool.subscribeMany` at runtime to unwrap `[[filter]]` → `filter`. If you are wiring listeners manually outside the CLI, apply the same normalization.
#### `startListeningForAllEchoes(groupCredential, shareCredentials, callback, options?)`

Starts listening for echo events on all shares in a keyset.
Expand Down
26 changes: 26 additions & 0 deletions src/cli.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import './polyfills/websocket.js';
import './polyfills/nostr.js';
import React from 'react';
import {render} from 'ink';
import {PassThrough} from 'node:stream';
Expand Down Expand Up @@ -114,6 +115,12 @@ function parseArgv(argv: string[]): ParsedArgs {
delete flags.T;
}

// Short alias: -E → --debug-echo
if (flags.E !== undefined && flags['debug-echo'] === undefined) {
flags['debug-echo'] = flags.E;
delete flags.E;
}

return {
command: positionals[0] ?? 'intro',
args: positionals.slice(1),
Expand All @@ -123,6 +130,16 @@ function parseArgv(argv: string[]): ParsedArgs {
};
}

function toBool(value: string | boolean | undefined): boolean {
if (typeof value === 'boolean') return value;
if (typeof value === 'string') {
const v = value.trim().toLowerCase();
if (['1', 'true', 'yes', 'on'].includes(v)) return true;
if (['0', 'false', 'no', 'off'].includes(v)) return false;
}
return false;
}

function showHelpScreen(version: string, opts?: any) {
const instance = render(<Help version={version} />, opts);
instance.waitUntilExit().then(() => process.exit(0));
Expand All @@ -137,6 +154,15 @@ const {command, args, flags, showHelp, showVersion: shouldShowVersion} = parseAr
process.argv.slice(2)
);

// Allow --debug-echo to enable/disable echo diagnostics without env vars.
// This is read by echo send/listen utilities.
(() => {
const raw = (flags['debug-echo'] ?? (flags as any).debugEcho) as string | boolean | undefined;
if (raw !== undefined) {
process.env.IGLOO_DEBUG_ECHO = toBool(raw) ? '1' : '0';
}
})();

// In non-interactive environments (CI/tests), Ink raw mode can throw.
// Allow tests to opt-out via IGLOO_DISABLE_RAW_MODE=1
let inkOptions: any | undefined;
Expand Down
1 change: 1 addition & 0 deletions src/components/Help.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export function Help({version}: HelpProps) {
<Text>--relays list Override relay list (comma-separated)</Text>
<Text>--verbose Stream signer diagnostics</Text>
<Text>--log-level level Signer log level (debug|info|warn|error)</Text>
<Text>--debug-echo Enable echo debug logs (relay + events)</Text>
</Box>
</Box>
);
Expand Down
49 changes: 2 additions & 47 deletions src/components/keyset/KeysetSigner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import {
PeerManager
} from '@frostr/igloo-core';
import type {BifrostNode} from '@frostr/igloo-core';
import {SimplePool} from 'nostr-tools';
// Ensure nostr subscribe shim is loaded if this component is used in isolation.
import '../../polyfills/nostr.js';
import {Prompt} from '../ui/Prompt.js';
import {
readShareFiles,
Expand Down Expand Up @@ -799,49 +800,3 @@ export function KeysetSigner({args, flags}: KeysetSignerProps) {
}

export default KeysetSigner;
let simplePoolPatched = false;

function ensureSimplePoolPatched() {
if (simplePoolPatched) {
return;
}

const prototype = (SimplePool as any)?.prototype;
if (!prototype) {
simplePoolPatched = true;
return;
}

if (prototype.__iglooFilterNormalizePatched) {
simplePoolPatched = true;
return;
}

const originalSubscribeMany = prototype.subscribeMany;
if (typeof originalSubscribeMany !== 'function') {
simplePoolPatched = true;
return;
}

prototype.subscribeMany = function patchedSubscribeMany(this: unknown, relays: unknown, filters: unknown, params: unknown) {
const normalizedFilters =
Array.isArray(filters) &&
filters.length === 1 &&
filters[0] !== null &&
typeof filters[0] === 'object' &&
!Array.isArray(filters[0])
? filters[0]
: filters;

return originalSubscribeMany.call(this, relays, normalizedFilters, params);
};

Object.defineProperty(prototype, '__iglooFilterNormalizePatched', {
value: true,
enumerable: false
});

simplePoolPatched = true;
}

ensureSimplePoolPatched();
6 changes: 6 additions & 0 deletions src/components/keyset/useShareEchoListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,12 @@ export function useShareEchoListener(
} catch {}
})
: undefined;
if (debugEnabled) {
try {
// eslint-disable-next-line no-console
console.log('[echo-listen] INFO using relays', relays ?? 'default');
} catch {}
}
const result = await awaitShareEcho(
groupCredential,
shareCredential,
Expand Down
25 changes: 25 additions & 0 deletions src/polyfills/nostr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Global runtime patch for nostr-tools SimplePool.subscribeMany
// Some relays reject a single-element array for filters (expecting an object).
// Normalize `[[filter]]` to `filter` to avoid "provided filter is not an object" errors.
// Idempotent and safe to import multiple times.
import {SimplePool} from 'nostr-tools';

try {
const proto = (SimplePool as any)?.prototype;
if (proto && !proto.__iglooFilterNormalizePatched) {
const original = proto.subscribeMany;
if (typeof original === 'function') {
proto.subscribeMany = function patchedSubscribeMany(this: unknown, relays: unknown, filters: unknown, params: unknown) {
const normalized = Array.isArray(filters) && filters.length === 1 && filters[0] &&
typeof filters[0] === 'object' && !Array.isArray(filters[0])
? filters[0]
: filters;
return original.call(this, relays, normalized, params);
};
Object.defineProperty(proto, '__iglooFilterNormalizePatched', {value: true});
}
}
} catch {
// Best-effort only; if nostr-tools changes, we fail silently.
}