@nority/bridge-sdk 0.1.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/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/agent.d.ts +101 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +6 -0
- package/dist/agent.js.map +1 -0
- package/dist/auth/jwt.d.ts +38 -0
- package/dist/auth/jwt.d.ts.map +1 -0
- package/dist/auth/jwt.js +83 -0
- package/dist/auth/jwt.js.map +1 -0
- package/dist/auth/pairing.d.ts +14 -0
- package/dist/auth/pairing.d.ts.map +1 -0
- package/dist/auth/pairing.js +34 -0
- package/dist/auth/pairing.js.map +1 -0
- package/dist/auth/reconnect.d.ts +28 -0
- package/dist/auth/reconnect.d.ts.map +1 -0
- package/dist/auth/reconnect.js +110 -0
- package/dist/auth/reconnect.js.map +1 -0
- package/dist/bridge.d.ts +44 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +311 -0
- package/dist/bridge.js.map +1 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +47 -0
- package/dist/config.js.map +1 -0
- package/dist/conversation/replay.d.ts +30 -0
- package/dist/conversation/replay.d.ts.map +1 -0
- package/dist/conversation/replay.js +85 -0
- package/dist/conversation/replay.js.map +1 -0
- package/dist/conversation/runtime.d.ts +43 -0
- package/dist/conversation/runtime.d.ts.map +1 -0
- package/dist/conversation/runtime.js +481 -0
- package/dist/conversation/runtime.js.map +1 -0
- package/dist/conversation/state.d.ts +25 -0
- package/dist/conversation/state.d.ts.map +1 -0
- package/dist/conversation/state.js +130 -0
- package/dist/conversation/state.js.map +1 -0
- package/dist/crypto/conversation.d.ts +20 -0
- package/dist/crypto/conversation.d.ts.map +1 -0
- package/dist/crypto/conversation.js +134 -0
- package/dist/crypto/conversation.js.map +1 -0
- package/dist/crypto/hkdf-sha3.d.ts +5 -0
- package/dist/crypto/hkdf-sha3.d.ts.map +1 -0
- package/dist/crypto/hkdf-sha3.js +8 -0
- package/dist/crypto/hkdf-sha3.js.map +1 -0
- package/dist/crypto/identity.d.ts +21 -0
- package/dist/crypto/identity.d.ts.map +1 -0
- package/dist/crypto/identity.js +70 -0
- package/dist/crypto/identity.js.map +1 -0
- package/dist/crypto/payload.d.ts +10 -0
- package/dist/crypto/payload.d.ts.map +1 -0
- package/dist/crypto/payload.js +28 -0
- package/dist/crypto/payload.js.map +1 -0
- package/dist/crypto/x25519.d.ts +16 -0
- package/dist/crypto/x25519.d.ts.map +1 -0
- package/dist/crypto/x25519.js +44 -0
- package/dist/crypto/x25519.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol/frames.d.ts +27 -0
- package/dist/protocol/frames.d.ts.map +1 -0
- package/dist/protocol/frames.js +43 -0
- package/dist/protocol/frames.js.map +1 -0
- package/dist/protocol/validation.d.ts +6 -0
- package/dist/protocol/validation.d.ts.map +1 -0
- package/dist/protocol/validation.js +33 -0
- package/dist/protocol/validation.js.map +1 -0
- package/dist/runtime/proactive.d.ts +14 -0
- package/dist/runtime/proactive.d.ts.map +1 -0
- package/dist/runtime/proactive.js +36 -0
- package/dist/runtime/proactive.js.map +1 -0
- package/dist/storage/conversation-store.d.ts +15 -0
- package/dist/storage/conversation-store.d.ts.map +1 -0
- package/dist/storage/conversation-store.js +75 -0
- package/dist/storage/conversation-store.js.map +1 -0
- package/dist/storage/state-store.d.ts +20 -0
- package/dist/storage/state-store.d.ts.map +1 -0
- package/dist/storage/state-store.js +74 -0
- package/dist/storage/state-store.js.map +1 -0
- package/dist/transport/outbound-queue.d.ts +18 -0
- package/dist/transport/outbound-queue.d.ts.map +1 -0
- package/dist/transport/outbound-queue.js +37 -0
- package/dist/transport/outbound-queue.js.map +1 -0
- package/dist/transport/websocket.d.ts +109 -0
- package/dist/transport/websocket.d.ts.map +1 -0
- package/dist/transport/websocket.js +346 -0
- package/dist/transport/websocket.js.map +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nority
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# @nority/bridge-sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for building Nority bridge implementations. Works with Node.js and Bun.
|
|
4
|
+
|
|
5
|
+
A **bridge** connects an external agent runtime (e.g., OpenClaw, LangChain, custom LLM pipelines) to the Nority platform so that conversations flow between the agent and Nority users — with end-to-end encryption, automatic reconnection, and replay.
|
|
6
|
+
|
|
7
|
+
## Who is this for?
|
|
8
|
+
|
|
9
|
+
Third-party developers who want to build a bridge for their own agent runtime. You implement a single `AgentAdapter` interface; the SDK handles everything else:
|
|
10
|
+
|
|
11
|
+
- QR/deeplink pairing flow
|
|
12
|
+
- Bridge-scoped credential storage and Ed25519 challenge-response auth
|
|
13
|
+
- Authenticated WebSocket transport with jittered backoff reconnect
|
|
14
|
+
- Per-conversation X25519 key exchange and AES-256-GCM encryption
|
|
15
|
+
- Replay and deterministic history rebuild after reconnect
|
|
16
|
+
- Ping/pong heartbeat and close-code-aware lifecycle
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @nority/bridge-sdk
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
<details>
|
|
25
|
+
<summary>Other package managers</summary>
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add @nority/bridge-sdk
|
|
29
|
+
yarn add @nority/bridge-sdk
|
|
30
|
+
bun add @nority/bridge-sdk
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
</details>
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
### 1. Implement `AgentAdapter`
|
|
38
|
+
|
|
39
|
+
The adapter is the only code you write. It receives decrypted user messages and yields streamed agent events:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import type { AgentAdapter, AgentEvent, InvokeRequest } from "@nority/bridge-sdk";
|
|
43
|
+
|
|
44
|
+
class MyAdapter implements AgentAdapter {
|
|
45
|
+
async *invokeAgent(
|
|
46
|
+
signal: AbortSignal,
|
|
47
|
+
req: InvokeRequest,
|
|
48
|
+
): AsyncIterable<AgentEvent> {
|
|
49
|
+
// Call your agent runtime with req.inputs and req.history
|
|
50
|
+
const response = await myAgent.run(req.inputs, { signal });
|
|
51
|
+
|
|
52
|
+
for await (const chunk of response) {
|
|
53
|
+
yield { type: "text_delta", content: chunk.text };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
yield { type: "run_status", runStatus: "completed" };
|
|
57
|
+
yield { type: "done" };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Start the bridge
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { NorityBridge } from "@nority/bridge-sdk";
|
|
66
|
+
|
|
67
|
+
const bridge = new NorityBridge({
|
|
68
|
+
backendUrl: "wss://api.nority.ai/v1/bridge",
|
|
69
|
+
dataDir: "./bridge-data",
|
|
70
|
+
agent: new MyAdapter(),
|
|
71
|
+
onPairingToken: (info) => {
|
|
72
|
+
console.log(`Pair: ${info.deeplinkUrl} (expires ${info.expiresAt})`);
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await bridge.start();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
On first run, the SDK generates an Ed25519 identity, enters the pairing flow, and calls `onPairingToken` with a QR/deeplink URL. After the user approves in the Nority app, the bridge receives a durable credential and connects automatically.
|
|
80
|
+
|
|
81
|
+
On subsequent runs, the SDK loads the stored credential, mints a fresh JWT via challenge-response, reconnects, replays missed events, and resumes normal operation.
|
|
82
|
+
|
|
83
|
+
## Key concepts
|
|
84
|
+
|
|
85
|
+
| Concept | Description |
|
|
86
|
+
|---------|-------------|
|
|
87
|
+
| **Bridge credential** | Long-lived secret stored locally. Never the user's JWT. |
|
|
88
|
+
| **Bridge access JWT** | Short-lived token for WebSocket auth. Cached ephemerally. |
|
|
89
|
+
| **Pairing token** | 120-second single-use token displayed as QR/deeplink. Auto-regenerates. |
|
|
90
|
+
| **Conversation key** | Per-conversation AES-256-GCM symmetric key derived via X25519 ECDH + HKDF-SHA3-256. |
|
|
91
|
+
| **Replay** | After reconnect, the SDK requests missed events and rebuilds conversation history deterministically. |
|
|
92
|
+
|
|
93
|
+
## Agent event types
|
|
94
|
+
|
|
95
|
+
Your adapter yields `AgentEvent` values. The SDK encrypts and forwards them to the backend:
|
|
96
|
+
|
|
97
|
+
| Type | Purpose |
|
|
98
|
+
|------|---------|
|
|
99
|
+
| `text_delta` | Streamed text content |
|
|
100
|
+
| `tool_call_start` | Tool invocation started |
|
|
101
|
+
| `tool_call_update` | Partial tool result |
|
|
102
|
+
| `tool_result` | Final tool result |
|
|
103
|
+
| `thinking_start` | Reasoning block started |
|
|
104
|
+
| `thinking_delta` | Streamed reasoning content |
|
|
105
|
+
| `thinking_complete` | Reasoning block finished |
|
|
106
|
+
| `run_status` | Agent lifecycle (`started`, `completed`, `failed`) |
|
|
107
|
+
| `media` | Media reference (multimodal-safe) |
|
|
108
|
+
| `error` | Agent-reported error |
|
|
109
|
+
| `done` | Terminal event — must be the last event yielded |
|
|
110
|
+
|
|
111
|
+
## Content model
|
|
112
|
+
|
|
113
|
+
The SDK uses a multimodal-safe content model:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
type ContentPart =
|
|
117
|
+
| { kind: "text"; text: string }
|
|
118
|
+
| { kind: "media"; media: MediaReference };
|
|
119
|
+
|
|
120
|
+
interface ChatTurn {
|
|
121
|
+
role: "user" | "assistant";
|
|
122
|
+
parts: ContentPart[];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
interface InvokeRequest {
|
|
126
|
+
conversationId: string;
|
|
127
|
+
inputs: ContentPart[]; // Current user turn
|
|
128
|
+
history: ChatTurn[]; // Previous conversation turns
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Bridge lifecycle states
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
idle → pairing → connecting → connected
|
|
136
|
+
↕
|
|
137
|
+
reconnecting → blocked → failed
|
|
138
|
+
↓
|
|
139
|
+
stopped
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Configuration
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
interface BridgeConfig {
|
|
146
|
+
backendUrl: string; // Nority backend WebSocket URL
|
|
147
|
+
dataDir: string; // Directory for durable state (identity, keys)
|
|
148
|
+
agent: AgentAdapter; // Your adapter implementation
|
|
149
|
+
onPairingToken?: (info) => void; // Called when pairing token is ready
|
|
150
|
+
logger?: BridgeLogger; // Structured logging
|
|
151
|
+
webSocketFactory?: WebSocketFactory; // Custom WebSocket creation
|
|
152
|
+
heartbeatIntervalMs?: number; // Default: 15000
|
|
153
|
+
idleTimeoutMs?: number; // Default: 45000
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## Requirements
|
|
158
|
+
|
|
159
|
+
- **Node.js** >= 20 or **Bun** >= 1.0
|
|
160
|
+
- The adapter-to-agent transport is entirely provider-specific — the SDK makes no assumptions about how your adapter talks to its agent runtime
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
MIT
|
package/dist/agent.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public SDK types for the Nority bridge protocol.
|
|
3
|
+
* Provider-agnostic — no assumptions about the agent runtime transport.
|
|
4
|
+
*/
|
|
5
|
+
import type { WebSocketFactory } from "./transport/websocket.js";
|
|
6
|
+
export interface MediaReference {
|
|
7
|
+
mediaId: string;
|
|
8
|
+
mediaType: string;
|
|
9
|
+
size: number;
|
|
10
|
+
thumbnailB64?: string;
|
|
11
|
+
}
|
|
12
|
+
export type ContentPart = {
|
|
13
|
+
kind: "text";
|
|
14
|
+
text: string;
|
|
15
|
+
} | {
|
|
16
|
+
kind: "media";
|
|
17
|
+
media: MediaReference;
|
|
18
|
+
};
|
|
19
|
+
export interface ChatTurn {
|
|
20
|
+
role: "user" | "assistant";
|
|
21
|
+
parts: ContentPart[];
|
|
22
|
+
}
|
|
23
|
+
export interface InvokeRequest {
|
|
24
|
+
conversationId: string;
|
|
25
|
+
inputs: ContentPart[];
|
|
26
|
+
history: ChatTurn[];
|
|
27
|
+
}
|
|
28
|
+
export type AgentEvent = {
|
|
29
|
+
type: "text_delta";
|
|
30
|
+
content: string;
|
|
31
|
+
} | {
|
|
32
|
+
type: "tool_call_start";
|
|
33
|
+
toolCallId: string;
|
|
34
|
+
toolName: string;
|
|
35
|
+
arguments: string;
|
|
36
|
+
status?: string;
|
|
37
|
+
} | {
|
|
38
|
+
type: "tool_call_update";
|
|
39
|
+
toolCallId: string;
|
|
40
|
+
partialResult?: string;
|
|
41
|
+
status?: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: "tool_result";
|
|
44
|
+
toolCallId: string;
|
|
45
|
+
result: string;
|
|
46
|
+
status?: string;
|
|
47
|
+
} | {
|
|
48
|
+
type: "thinking_start";
|
|
49
|
+
thinkingBlockId: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: "thinking_delta";
|
|
52
|
+
thinkingBlockId: string;
|
|
53
|
+
content: string;
|
|
54
|
+
} | {
|
|
55
|
+
type: "thinking_complete";
|
|
56
|
+
thinkingBlockId: string;
|
|
57
|
+
content?: string;
|
|
58
|
+
} | {
|
|
59
|
+
type: "run_status";
|
|
60
|
+
runStatus: string;
|
|
61
|
+
reason?: string;
|
|
62
|
+
} | {
|
|
63
|
+
type: "media";
|
|
64
|
+
media: MediaReference;
|
|
65
|
+
} | {
|
|
66
|
+
type: "error";
|
|
67
|
+
errorCode?: string;
|
|
68
|
+
errorMessage: string;
|
|
69
|
+
reason?: string;
|
|
70
|
+
} | {
|
|
71
|
+
type: "done";
|
|
72
|
+
};
|
|
73
|
+
export interface AgentAdapter {
|
|
74
|
+
invokeAgent(signal: AbortSignal, req: InvokeRequest): AsyncIterable<AgentEvent>;
|
|
75
|
+
}
|
|
76
|
+
export interface PairingInfo {
|
|
77
|
+
token: string;
|
|
78
|
+
deeplinkUrl: string;
|
|
79
|
+
expiresAt: string;
|
|
80
|
+
}
|
|
81
|
+
export interface BridgeLogger {
|
|
82
|
+
debug: (msg: string, ...args: unknown[]) => void;
|
|
83
|
+
info: (msg: string, ...args: unknown[]) => void;
|
|
84
|
+
warn: (msg: string, ...args: unknown[]) => void;
|
|
85
|
+
error: (msg: string, ...args: unknown[]) => void;
|
|
86
|
+
}
|
|
87
|
+
export interface BridgeConfig {
|
|
88
|
+
backendUrl: string;
|
|
89
|
+
dataDir: string;
|
|
90
|
+
agent: AgentAdapter;
|
|
91
|
+
onPairingToken?: (pairing: PairingInfo) => void;
|
|
92
|
+
logger?: BridgeLogger;
|
|
93
|
+
fetchImplementation?: typeof fetch;
|
|
94
|
+
webSocketFactory?: WebSocketFactory;
|
|
95
|
+
clock?: () => Date;
|
|
96
|
+
pairingTokenFactory?: () => string;
|
|
97
|
+
backoffRandom?: () => number;
|
|
98
|
+
heartbeatIntervalMs?: number;
|
|
99
|
+
idleTimeoutMs?: number;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAIjE,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,CAAC;AAE7C,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,KAAK,EAAE,WAAW,EAAE,CAAC;CACtB;AAID,MAAM,WAAW,aAAa;IAC5B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvC;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,aAAa,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACpE;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GACxE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1D;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC5E;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,MAAM,WAAW,YAAY;IAC3B,WAAW,CACT,MAAM,EAAE,WAAW,EACnB,GAAG,EAAE,aAAa,GACjB,aAAa,CAAC,UAAU,CAAC,CAAC;CAC9B;AAID,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IACjD,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAChD,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAChD,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,YAAY,CAAC;IACpB,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IAChD,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,KAAK,CAAC;IACnC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,mBAAmB,CAAC,EAAE,MAAM,MAAM,CAAC;IACnC,aAAa,CAAC,EAAE,MAAM,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge JWT utilities.
|
|
3
|
+
*
|
|
4
|
+
* Bridge JWTs are:
|
|
5
|
+
* - Short-lived (cached ephemerally, never persisted as durable credential)
|
|
6
|
+
* - Bridge-scoped (audience: "nority-bridge")
|
|
7
|
+
* - Least-privilege (scope: "bridge:connect bridge:replay bridge:events")
|
|
8
|
+
*/
|
|
9
|
+
export declare const BRIDGE_JWT_AUDIENCE = "nority-bridge";
|
|
10
|
+
export declare const BRIDGE_JWT_SCOPES: readonly ["bridge:connect", "bridge:replay", "bridge:events"];
|
|
11
|
+
export interface AccessTokenRecord {
|
|
12
|
+
accessJwt: string;
|
|
13
|
+
expiresAt: string;
|
|
14
|
+
agentId?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface JwtCacheOptions {
|
|
17
|
+
clock?: () => Date;
|
|
18
|
+
expirySkewMs?: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check if a JWT is likely expired (without full validation).
|
|
22
|
+
* Returns true if the token appears to be expired based on the exp claim.
|
|
23
|
+
*/
|
|
24
|
+
export declare function isJwtExpired(jwt: string, nowEpochSeconds?: number): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Ephemeral in-memory JWT cache.
|
|
27
|
+
*/
|
|
28
|
+
export declare class JwtCache {
|
|
29
|
+
private readonly clock;
|
|
30
|
+
private readonly expirySkewMs;
|
|
31
|
+
private current;
|
|
32
|
+
constructor(options?: JwtCacheOptions);
|
|
33
|
+
seed(record: AccessTokenRecord): void;
|
|
34
|
+
clear(): void;
|
|
35
|
+
getValid(): AccessTokenRecord | null;
|
|
36
|
+
private assertRecord;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=jwt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../../src/auth/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AACnD,eAAO,MAAM,iBAAiB,+DAIpB,CAAC;AAEX,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,MAAM,EACX,eAAe,SAAoB,GAClC,OAAO,CAqBT;AAED;;GAEG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,OAAO,CAAkC;gBAErC,OAAO,GAAE,eAAoB;IAKzC,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAKrC,KAAK,IAAI,IAAI;IAIb,QAAQ,IAAI,iBAAiB,GAAG,IAAI;IAqBpC,OAAO,CAAC,YAAY;CAarB"}
|
package/dist/auth/jwt.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge JWT utilities.
|
|
3
|
+
*
|
|
4
|
+
* Bridge JWTs are:
|
|
5
|
+
* - Short-lived (cached ephemerally, never persisted as durable credential)
|
|
6
|
+
* - Bridge-scoped (audience: "nority-bridge")
|
|
7
|
+
* - Least-privilege (scope: "bridge:connect bridge:replay bridge:events")
|
|
8
|
+
*/
|
|
9
|
+
export const BRIDGE_JWT_AUDIENCE = "nority-bridge";
|
|
10
|
+
export const BRIDGE_JWT_SCOPES = [
|
|
11
|
+
"bridge:connect",
|
|
12
|
+
"bridge:replay",
|
|
13
|
+
"bridge:events",
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Check if a JWT is likely expired (without full validation).
|
|
17
|
+
* Returns true if the token appears to be expired based on the exp claim.
|
|
18
|
+
*/
|
|
19
|
+
export function isJwtExpired(jwt, nowEpochSeconds = Date.now() / 1000) {
|
|
20
|
+
if (!jwt) {
|
|
21
|
+
throw new Error("isJwtExpired: jwt is required");
|
|
22
|
+
}
|
|
23
|
+
const parts = jwt.split(".");
|
|
24
|
+
if (parts.length !== 3) {
|
|
25
|
+
throw new Error("isJwtExpired: invalid JWT format");
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString("utf-8"));
|
|
29
|
+
if (typeof payload.exp !== "number") {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return nowEpochSeconds >= payload.exp;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Ephemeral in-memory JWT cache.
|
|
40
|
+
*/
|
|
41
|
+
export class JwtCache {
|
|
42
|
+
clock;
|
|
43
|
+
expirySkewMs;
|
|
44
|
+
current = null;
|
|
45
|
+
constructor(options = {}) {
|
|
46
|
+
this.clock = options.clock ?? (() => new Date());
|
|
47
|
+
this.expirySkewMs = options.expirySkewMs ?? 5_000;
|
|
48
|
+
}
|
|
49
|
+
seed(record) {
|
|
50
|
+
this.assertRecord(record);
|
|
51
|
+
this.current = { ...record };
|
|
52
|
+
}
|
|
53
|
+
clear() {
|
|
54
|
+
this.current = null;
|
|
55
|
+
}
|
|
56
|
+
getValid() {
|
|
57
|
+
if (this.current === null) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const expiryMs = Date.parse(this.current.expiresAt);
|
|
61
|
+
if (Number.isNaN(expiryMs)) {
|
|
62
|
+
throw new Error(`JwtCache.getValid: invalid expiresAt timestamp: ${this.current.expiresAt}`);
|
|
63
|
+
}
|
|
64
|
+
const nowMs = this.clock().getTime();
|
|
65
|
+
if (nowMs >= expiryMs - this.expirySkewMs) {
|
|
66
|
+
this.current = null;
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return { ...this.current };
|
|
70
|
+
}
|
|
71
|
+
assertRecord(record) {
|
|
72
|
+
if (!record.accessJwt) {
|
|
73
|
+
throw new Error("JwtCache.seed: accessJwt is required");
|
|
74
|
+
}
|
|
75
|
+
if (!record.expiresAt) {
|
|
76
|
+
throw new Error("JwtCache.seed: expiresAt is required");
|
|
77
|
+
}
|
|
78
|
+
if (Number.isNaN(Date.parse(record.expiresAt))) {
|
|
79
|
+
throw new Error(`JwtCache.seed: expiresAt must be RFC3339, got ${record.expiresAt}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=jwt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwt.js","sourceRoot":"","sources":["../../src/auth/jwt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pairing flow helpers.
|
|
3
|
+
*/
|
|
4
|
+
export declare const PAIRING_TOKEN_TTL_SECONDS = 120;
|
|
5
|
+
export declare const DEEPLINK_PREFIX = "nority://pair-bridge?token=";
|
|
6
|
+
export interface PairingClockOptions {
|
|
7
|
+
clock?: () => Date;
|
|
8
|
+
tokenTtlSeconds?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function createDeeplinkUrl(token: string): string;
|
|
11
|
+
export declare function createPairingToken(): string;
|
|
12
|
+
export declare function createPairingExpiry(options?: PairingClockOptions): string;
|
|
13
|
+
export declare function isPairingExpired(expiresAt: string, clock?: () => Date): boolean;
|
|
14
|
+
//# sourceMappingURL=pairing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing.d.ts","sourceRoot":"","sources":["../../src/auth/pairing.ts"],"names":[],"mappings":"AAEA;;GAEG;AAEH,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,eAAe,gCAAgC,CAAC;AAE7D,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAKvD;AAED,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,mBAAwB,GAChC,MAAM,CAUR;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAM,IAAuB,GACnC,OAAO,CAaT"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Pairing flow helpers.
|
|
4
|
+
*/
|
|
5
|
+
export const PAIRING_TOKEN_TTL_SECONDS = 120;
|
|
6
|
+
export const DEEPLINK_PREFIX = "nority://pair-bridge?token=";
|
|
7
|
+
export function createDeeplinkUrl(token) {
|
|
8
|
+
if (!token) {
|
|
9
|
+
throw new Error("createDeeplinkUrl: token is required");
|
|
10
|
+
}
|
|
11
|
+
return `${DEEPLINK_PREFIX}${token}`;
|
|
12
|
+
}
|
|
13
|
+
export function createPairingToken() {
|
|
14
|
+
return `pair_${randomBytes(15).toString("base64url")}`;
|
|
15
|
+
}
|
|
16
|
+
export function createPairingExpiry(options = {}) {
|
|
17
|
+
const clock = options.clock ?? (() => new Date());
|
|
18
|
+
const tokenTtlSeconds = options.tokenTtlSeconds ?? PAIRING_TOKEN_TTL_SECONDS;
|
|
19
|
+
if (tokenTtlSeconds <= 0) {
|
|
20
|
+
throw new Error(`createPairingExpiry: tokenTtlSeconds must be positive, got ${tokenTtlSeconds}`);
|
|
21
|
+
}
|
|
22
|
+
return new Date(clock().getTime() + tokenTtlSeconds * 1000).toISOString();
|
|
23
|
+
}
|
|
24
|
+
export function isPairingExpired(expiresAt, clock = () => new Date()) {
|
|
25
|
+
if (!expiresAt) {
|
|
26
|
+
throw new Error("isPairingExpired: expiresAt is required");
|
|
27
|
+
}
|
|
28
|
+
const expiryMs = Date.parse(expiresAt);
|
|
29
|
+
if (Number.isNaN(expiryMs)) {
|
|
30
|
+
throw new Error(`isPairingExpired: expiresAt must be RFC3339, got ${expiresAt}`);
|
|
31
|
+
}
|
|
32
|
+
return clock().getTime() >= expiryMs;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=pairing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pairing.js","sourceRoot":"","sources":["../../src/auth/pairing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;GAEG;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAC7C,MAAM,CAAC,MAAM,eAAe,GAAG,6BAA6B,CAAC;AAO7D,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,GAAG,eAAe,GAAG,KAAK,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,OAAO,QAAQ,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,UAA+B,EAAE;IAEjC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,yBAAyB,CAAC;IAC7E,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,KAAK,CACb,8DAA8D,eAAe,EAAE,CAChF,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,GAAG,eAAe,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,QAAoB,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;IAEpC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACvC,IAAI,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CACb,oDAAoD,SAAS,EAAE,CAChE,CAAC;IACJ,CAAC;IAED,OAAO,KAAK,EAAE,CAAC,OAAO,EAAE,IAAI,QAAQ,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AccessTokenRecord, type JwtCacheOptions } from "./jwt.js";
|
|
2
|
+
export declare const CHALLENGE_ENDPOINT = "/v1/bridge/auth/challenge";
|
|
3
|
+
export declare const VERIFY_ENDPOINT = "/v1/bridge/auth/verify";
|
|
4
|
+
export interface BridgeAccessTokenManagerOptions extends JwtCacheOptions {
|
|
5
|
+
backendUrl: string;
|
|
6
|
+
fetchImplementation?: typeof fetch;
|
|
7
|
+
}
|
|
8
|
+
export interface GetAccessTokenOptions {
|
|
9
|
+
credential: string;
|
|
10
|
+
privateKeyPem: string;
|
|
11
|
+
forceRefresh?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Mints and caches short-lived bridge access JWTs.
|
|
15
|
+
*/
|
|
16
|
+
export declare class BridgeAccessTokenManager {
|
|
17
|
+
private readonly backendUrl;
|
|
18
|
+
private readonly fetchImplementation;
|
|
19
|
+
private readonly jwtCache;
|
|
20
|
+
constructor(options: BridgeAccessTokenManagerOptions);
|
|
21
|
+
seed(record: AccessTokenRecord): void;
|
|
22
|
+
clear(): void;
|
|
23
|
+
getAccessToken(options: GetAccessTokenOptions): Promise<AccessTokenRecord>;
|
|
24
|
+
private fetchChallenge;
|
|
25
|
+
private fetchVerify;
|
|
26
|
+
}
|
|
27
|
+
export type { AccessTokenRecord } from "./jwt.js";
|
|
28
|
+
//# sourceMappingURL=reconnect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconnect.d.ts","sourceRoot":"","sources":["../../src/auth/reconnect.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EAEjB,KAAK,eAAe,EACrB,MAAM,UAAU,CAAC;AAGlB,eAAO,MAAM,kBAAkB,8BAA8B,CAAC;AAC9D,eAAO,MAAM,eAAe,2BAA2B,CAAC;AAaxD,MAAM,WAAW,+BAAgC,SAAQ,eAAe;IACtE,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,CAAC,EAAE,OAAO,KAAK,CAAC;CACpC;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAe;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;gBAExB,OAAO,EAAE,+BAA+B;IAapD,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAIrC,KAAK,IAAI,IAAI;IAIP,cAAc,CAClB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,iBAAiB,CAAC;YA0Cf,cAAc;YA+Bd,WAAW;CAmC1B;AAWD,YAAY,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { JwtCache, } from "./jwt.js";
|
|
2
|
+
import { signEd25519 } from "../crypto/identity.js";
|
|
3
|
+
export const CHALLENGE_ENDPOINT = "/v1/bridge/auth/challenge";
|
|
4
|
+
export const VERIFY_ENDPOINT = "/v1/bridge/auth/verify";
|
|
5
|
+
/**
|
|
6
|
+
* Mints and caches short-lived bridge access JWTs.
|
|
7
|
+
*/
|
|
8
|
+
export class BridgeAccessTokenManager {
|
|
9
|
+
backendUrl;
|
|
10
|
+
fetchImplementation;
|
|
11
|
+
jwtCache;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
if (!options) {
|
|
14
|
+
throw new Error("BridgeAccessTokenManager: options are required");
|
|
15
|
+
}
|
|
16
|
+
if (!options.backendUrl) {
|
|
17
|
+
throw new Error("BridgeAccessTokenManager: backendUrl is required");
|
|
18
|
+
}
|
|
19
|
+
this.backendUrl = options.backendUrl;
|
|
20
|
+
this.fetchImplementation = options.fetchImplementation ?? fetch;
|
|
21
|
+
this.jwtCache = new JwtCache(options);
|
|
22
|
+
}
|
|
23
|
+
seed(record) {
|
|
24
|
+
this.jwtCache.seed(record);
|
|
25
|
+
}
|
|
26
|
+
clear() {
|
|
27
|
+
this.jwtCache.clear();
|
|
28
|
+
}
|
|
29
|
+
async getAccessToken(options) {
|
|
30
|
+
if (!options) {
|
|
31
|
+
throw new Error("BridgeAccessTokenManager.getAccessToken: options are required");
|
|
32
|
+
}
|
|
33
|
+
if (!options.credential) {
|
|
34
|
+
throw new Error("BridgeAccessTokenManager.getAccessToken: credential is required");
|
|
35
|
+
}
|
|
36
|
+
if (!options.forceRefresh) {
|
|
37
|
+
const cached = this.jwtCache.getValid();
|
|
38
|
+
if (cached !== null) {
|
|
39
|
+
return cached;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!options.privateKeyPem) {
|
|
43
|
+
throw new Error("BridgeAccessTokenManager.getAccessToken: privateKeyPem is required");
|
|
44
|
+
}
|
|
45
|
+
const challenge = await this.fetchChallenge(options.credential);
|
|
46
|
+
const signature = await signEd25519(options.privateKeyPem, Buffer.from(challenge.challenge, "hex"));
|
|
47
|
+
const verified = await this.fetchVerify({
|
|
48
|
+
credential: options.credential,
|
|
49
|
+
challenge: challenge.challenge,
|
|
50
|
+
signature: signature.toString("base64"),
|
|
51
|
+
});
|
|
52
|
+
const record = {
|
|
53
|
+
accessJwt: verified.access_jwt,
|
|
54
|
+
agentId: verified.agent_id,
|
|
55
|
+
expiresAt: verified.expires_at,
|
|
56
|
+
};
|
|
57
|
+
this.jwtCache.seed(record);
|
|
58
|
+
return record;
|
|
59
|
+
}
|
|
60
|
+
async fetchChallenge(credential) {
|
|
61
|
+
const response = await this.fetchImplementation(resolveHttpEndpoint(this.backendUrl, CHALLENGE_ENDPOINT), {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: {
|
|
64
|
+
"content-type": "application/json",
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify({ credential }),
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
throw new Error(`BridgeAccessTokenManager.fetchChallenge: backend returned ${response.status}`);
|
|
70
|
+
}
|
|
71
|
+
const body = (await response.json());
|
|
72
|
+
if (!body.challenge || !body.expires_at) {
|
|
73
|
+
throw new Error("BridgeAccessTokenManager.fetchChallenge: challenge and expires_at are required");
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
challenge: body.challenge,
|
|
77
|
+
expires_at: body.expires_at,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
async fetchVerify(input) {
|
|
81
|
+
const response = await this.fetchImplementation(resolveHttpEndpoint(this.backendUrl, VERIFY_ENDPOINT), {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers: {
|
|
84
|
+
"content-type": "application/json",
|
|
85
|
+
},
|
|
86
|
+
body: JSON.stringify(input),
|
|
87
|
+
});
|
|
88
|
+
if (!response.ok) {
|
|
89
|
+
throw new Error(`BridgeAccessTokenManager.fetchVerify: backend returned ${response.status}`);
|
|
90
|
+
}
|
|
91
|
+
const body = (await response.json());
|
|
92
|
+
if (!body.access_jwt || !body.expires_at || !body.agent_id) {
|
|
93
|
+
throw new Error("BridgeAccessTokenManager.fetchVerify: access_jwt, agent_id, and expires_at are required");
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
access_jwt: body.access_jwt,
|
|
97
|
+
agent_id: body.agent_id,
|
|
98
|
+
expires_at: body.expires_at,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function resolveHttpEndpoint(backendUrl, path) {
|
|
103
|
+
const url = new URL(backendUrl);
|
|
104
|
+
url.protocol = url.protocol === "wss:" ? "https:" : "http:";
|
|
105
|
+
url.pathname = path;
|
|
106
|
+
url.search = "";
|
|
107
|
+
url.hash = "";
|
|
108
|
+
return url.toString();
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=reconnect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconnect.js","sourceRoot":"","sources":["../../src/auth/reconnect.ts"],"names":[],"mappings":"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"}
|