mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-17 19:07:06 +02:00
chore: prepare relay-auth-manager for npm publishing
- Point exports/main/types to dist/ for correct npm resolution - Add tsconfig.build.json for emitting ESM JS + declarations - Add build/clean/prepublishOnly scripts - Add LICENSE, description, keywords, author, files field - Add Vite resolve alias + tsconfig paths for workspace dev (resolves source directly, no pre-build needed for dev) - Fix TypeScript strict errors in test file - Clean npm pack output: dist/, README.md, LICENSE only https://claude.ai/code/session_01XqrjeQVtJKw9uC1XAw6rqd
This commit is contained in:
21
packages/relay-auth-manager/LICENSE
Normal file
21
packages/relay-auth-manager/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Alejandro Gómez
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -12,8 +12,8 @@ This is a workspace package. It has a single peer dependency on `rxjs >= 7`.
|
||||
import { RelayAuthManager } from "relay-auth-manager";
|
||||
|
||||
const manager = new RelayAuthManager({
|
||||
pool, // relay pool (applesauce-relay compatible)
|
||||
signer$, // Observable<AuthSigner | null>
|
||||
pool, // relay pool (applesauce-relay compatible)
|
||||
signer$, // Observable<AuthSigner | null>
|
||||
storage: localStorage, // optional persistence
|
||||
});
|
||||
|
||||
@@ -40,15 +40,15 @@ manager.setPreference("wss://relay.example.com", "always");
|
||||
new RelayAuthManager(options: RelayAuthManagerOptions)
|
||||
```
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `pool` | `AuthRelayPool` | *required* | Relay pool to monitor. Relays are auto-monitored on `add$` and cleaned up on `remove$`. |
|
||||
| `signer$` | `Observable<AuthSigner \| null>` | *required* | Current signer. Emit `null` when logged out or read-only. |
|
||||
| `storage` | `AuthPreferenceStorage` | `undefined` | Persistent storage for preferences. Anything with `getItem`/`setItem` works. |
|
||||
| `storageKey` | `string` | `"relay-auth-preferences"` | Key used in storage. |
|
||||
| `challengeTTL` | `number` | `300000` (5 min) | How long a challenge stays pending before being filtered out. |
|
||||
| `initialRelays` | `Iterable<AuthRelay>` | `[]` | Relays already in the pool at creation time. |
|
||||
| `normalizeUrl` | `(url: string) => string` | adds `wss://`, strips trailing `/` | Custom URL normalizer. Applied to all URLs used as map keys (preferences, state lookups). Provide this if your app uses a different normalization (e.g., lowercase hostname, trailing slash). |
|
||||
| Option | Type | Default | Description |
|
||||
| --------------- | -------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `pool` | `AuthRelayPool` | _required_ | Relay pool to monitor. Relays are auto-monitored on `add$` and cleaned up on `remove$`. |
|
||||
| `signer$` | `Observable<AuthSigner \| null>` | _required_ | Current signer. Emit `null` when logged out or read-only. |
|
||||
| `storage` | `AuthPreferenceStorage` | `undefined` | Persistent storage for preferences. Anything with `getItem`/`setItem` works. |
|
||||
| `storageKey` | `string` | `"relay-auth-preferences"` | Key used in storage. |
|
||||
| `challengeTTL` | `number` | `300000` (5 min) | How long a challenge stays pending before being filtered out. |
|
||||
| `initialRelays` | `Iterable<AuthRelay>` | `[]` | Relays already in the pool at creation time. |
|
||||
| `normalizeUrl` | `(url: string) => string` | adds `wss://`, strips trailing `/` | Custom URL normalizer. Applied to all URLs used as map keys (preferences, state lookups). Provide this if your app uses a different normalization (e.g., lowercase hostname, trailing slash). |
|
||||
|
||||
## Observables
|
||||
|
||||
@@ -67,6 +67,7 @@ manager.states$.subscribe((states) => {
|
||||
### `pendingChallenges$: BehaviorSubject<PendingAuthChallenge[]>`
|
||||
|
||||
Challenges that need user interaction. Already filtered — only includes relays where:
|
||||
|
||||
- Status is `"challenge_received"`
|
||||
- A signer is available
|
||||
- Challenge hasn't expired
|
||||
@@ -77,40 +78,40 @@ Challenges that need user interaction. Already filtered — only includes relays
|
||||
|
||||
### Authentication
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `authenticate(relayUrl)` | Accept a pending challenge. Signs and sends AUTH. Returns a Promise that resolves when `authenticated$` confirms. Rejects if relay disconnects, auth fails, or preconditions aren't met (no challenge, no signer, relay not monitored). |
|
||||
| `retry(relayUrl)` | Retry authentication for a relay in `"failed"` state. Re-reads the challenge from the relay. Same promise semantics as `authenticate()`. |
|
||||
| `reject(relayUrl, rememberForSession?)` | Reject a challenge. If `rememberForSession` is `true` (default), suppresses future prompts for this relay until page reload. |
|
||||
| Method | Description |
|
||||
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `authenticate(relayUrl)` | Accept a pending challenge. Signs and sends AUTH. Returns a Promise that resolves when `authenticated$` confirms. Rejects if relay disconnects, auth fails, or preconditions aren't met (no challenge, no signer, relay not monitored). |
|
||||
| `retry(relayUrl)` | Retry authentication for a relay in `"failed"` state. Re-reads the challenge from the relay. Same promise semantics as `authenticate()`. |
|
||||
| `reject(relayUrl, rememberForSession?)` | Reject a challenge. If `rememberForSession` is `true` (default), suppresses future prompts for this relay until page reload. |
|
||||
|
||||
### Preferences
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| Method | Description |
|
||||
| -------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `setPreference(url, pref)` | Set `"always"`, `"never"`, or `"ask"` for a relay. Persisted to storage. URL is normalized for consistent matching. |
|
||||
| `getPreference(url)` | Get preference for a relay, or `undefined`. |
|
||||
| `removePreference(url)` | Remove a preference. Returns `true` if one existed. Persisted to storage. |
|
||||
| `getAllPreferences()` | `ReadonlyMap<string, AuthPreference>` of all preferences. |
|
||||
| `getPreference(url)` | Get preference for a relay, or `undefined`. |
|
||||
| `removePreference(url)` | Remove a preference. Returns `true` if one existed. Persisted to storage. |
|
||||
| `getAllPreferences()` | `ReadonlyMap<string, AuthPreference>` of all preferences. |
|
||||
|
||||
### Relay Monitoring
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| Method | Description |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| `monitorRelay(relay)` | Start monitoring a relay for challenges. Idempotent. Called automatically for pool relays. |
|
||||
| `unmonitorRelay(url)` | Stop monitoring. Called automatically on pool `remove$`. |
|
||||
| `unmonitorRelay(url)` | Stop monitoring. Called automatically on pool `remove$`. |
|
||||
|
||||
### State Queries
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `getRelayState(url)` | Get `RelayAuthState` snapshot for a single relay. Returns a copy, not a live reference. |
|
||||
| `getAllStates()` | Snapshot of all states. Same as `states$.value`. |
|
||||
| `hasSignerAvailable()` | Whether a signer is currently available. |
|
||||
| Method | Description |
|
||||
| ---------------------- | --------------------------------------------------------------------------------------- |
|
||||
| `getRelayState(url)` | Get `RelayAuthState` snapshot for a single relay. Returns a copy, not a live reference. |
|
||||
| `getAllStates()` | Snapshot of all states. Same as `states$.value`. |
|
||||
| `hasSignerAvailable()` | Whether a signer is currently available. |
|
||||
|
||||
### Lifecycle
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| Method | Description |
|
||||
| ----------- | -------------------------------------------------------------------------- |
|
||||
| `destroy()` | Unsubscribe everything, complete observables. Safe to call multiple times. |
|
||||
|
||||
## Auth Lifecycle
|
||||
@@ -131,11 +132,11 @@ Disconnect from any state resets to `none`. Failed relays can be retried via `re
|
||||
|
||||
Preferences control what happens when a challenge arrives:
|
||||
|
||||
| Preference | Behavior |
|
||||
|------------|----------|
|
||||
| `"always"` | Auto-authenticate (no user prompt). Waits for signer if unavailable. |
|
||||
| `"never"` | Auto-reject (no user prompt). |
|
||||
| `"ask"` | Show in `pendingChallenges$` for user to decide. This is the default. |
|
||||
| Preference | Behavior |
|
||||
| ---------- | --------------------------------------------------------------------- |
|
||||
| `"always"` | Auto-authenticate (no user prompt). Waits for signer if unavailable. |
|
||||
| `"never"` | Auto-reject (no user prompt). |
|
||||
| `"ask"` | Show in `pendingChallenges$` for user to decide. This is the default. |
|
||||
|
||||
## Storage
|
||||
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
{
|
||||
"name": "relay-auth-manager",
|
||||
"version": "0.1.0",
|
||||
"description": "Generic NIP-42 relay authentication manager for Nostr clients. Framework and storage agnostic.",
|
||||
"type": "module",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "MIT",
|
||||
"author": "Alejandro Gómez",
|
||||
"keywords": [
|
||||
"nostr",
|
||||
"nip-42",
|
||||
"relay",
|
||||
"authentication",
|
||||
"auth"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.build.json",
|
||||
"clean": "rm -rf dist",
|
||||
"prepublishOnly": "npm run clean && npm run build"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": "^7.0.0"
|
||||
|
||||
@@ -486,7 +486,8 @@ describe("RelayAuthManager", () => {
|
||||
|
||||
// Set challenge directly on relay without triggering observable state transition
|
||||
// (In production, relay.challenge is a getter synced with challenge$)
|
||||
(relay as Record<string, unknown>).challenge = "retry-challenge";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(relay as any).challenge = "retry-challenge";
|
||||
|
||||
// Retry succeeds (default mock behavior restores after mockRejectedValueOnce)
|
||||
await manager.retry("wss://relay.example.com");
|
||||
@@ -545,7 +546,8 @@ describe("RelayAuthManager", () => {
|
||||
|
||||
// Remove signer and set challenge directly (without triggering state transition)
|
||||
signer$.next(null);
|
||||
(relay as Record<string, unknown>).challenge = "retry-challenge";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(relay as any).challenge = "retry-challenge";
|
||||
|
||||
await expect(manager.retry("wss://relay.example.com")).rejects.toThrow(
|
||||
"No signer available",
|
||||
|
||||
17
packages/relay-auth-manager/tsconfig.build.json
Normal file
17
packages/relay-auth-manager/tsconfig.build.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"strict": true,
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src/__tests__"]
|
||||
}
|
||||
@@ -24,7 +24,8 @@
|
||||
/* Path aliases */
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
"@/*": ["./src/*"],
|
||||
"relay-auth-manager": ["./packages/relay-auth-manager/src/index.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
|
||||
@@ -12,6 +12,11 @@ export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
// Resolve workspace package source directly (bypasses dist/)
|
||||
"relay-auth-manager": path.resolve(
|
||||
__dirname,
|
||||
"./packages/relay-auth-manager/src/index.ts",
|
||||
),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
|
||||
Reference in New Issue
Block a user