shadowkey-agent-sdk 1.0.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 +178 -0
- package/dist/index.d.mts +72 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +165 -0
- package/dist/index.mjs +138 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 ShadowKey Team
|
|
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,178 @@
|
|
|
1
|
+
# ShadowKey Agent SDK
|
|
2
|
+
|
|
3
|
+
Official SDK for integrating AI agents with ShadowKey's privacy-preserving data vault.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- đ Secure API key authentication with request signing
|
|
8
|
+
- đ Automatic retry logic with exponential backoff
|
|
9
|
+
- âąī¸ Configurable timeouts and polling
|
|
10
|
+
- đ¯ TypeScript-first with full type definitions
|
|
11
|
+
- đĒļ Zero dependencies for minimal bundle size
|
|
12
|
+
- đ Debug mode for development
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install @shadowkey/agent-sdk
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Quick Start
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { ShadowKeyClient } from '@shadowkey/agent-sdk';
|
|
24
|
+
|
|
25
|
+
const client = new ShadowKeyClient({
|
|
26
|
+
apiUrl: 'https://your-project.supabase.co/functions/v1',
|
|
27
|
+
apiKey: 'your-api-key',
|
|
28
|
+
debug: true // Enable logging during development
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Request access to user data
|
|
32
|
+
const response = await client.requestAccess({
|
|
33
|
+
agentId: 'shopping-assistant-001',
|
|
34
|
+
agentName: 'Smart Shopping Assistant',
|
|
35
|
+
requestedFields: ['creditCard', 'shippingAddress'],
|
|
36
|
+
purpose: 'Complete your purchase of wireless headphones',
|
|
37
|
+
category: 'shopping'
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log('Request ID:', response.requestId);
|
|
41
|
+
|
|
42
|
+
// Wait for user approval (with timeout)
|
|
43
|
+
const result = await client.waitForApproval(response.requestId, 300000);
|
|
44
|
+
|
|
45
|
+
if (result.status === 'approved') {
|
|
46
|
+
console.log('Access granted!', result.grantedData);
|
|
47
|
+
} else {
|
|
48
|
+
console.log('Access denied:', result.message);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## API Reference
|
|
53
|
+
|
|
54
|
+
### Constructor
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
new ShadowKeyClient(config: ShadowKeyConfig)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Config Options:**
|
|
61
|
+
- `apiUrl` (required): Your Supabase project URL + `/functions/v1`
|
|
62
|
+
- `apiKey` (required): API key generated from ShadowKey settings
|
|
63
|
+
- `timeout` (optional): Request timeout in ms (default: 30000)
|
|
64
|
+
- `retryAttempts` (optional): Number of retry attempts (default: 3)
|
|
65
|
+
- `debug` (optional): Enable debug logging (default: false)
|
|
66
|
+
|
|
67
|
+
### Methods
|
|
68
|
+
|
|
69
|
+
#### `requestAccess(request: AccessRequest): Promise<AccessResponse>`
|
|
70
|
+
|
|
71
|
+
Request access to user's vault data.
|
|
72
|
+
|
|
73
|
+
**Parameters:**
|
|
74
|
+
```typescript
|
|
75
|
+
{
|
|
76
|
+
agentId: string; // Unique identifier for your agent
|
|
77
|
+
agentName: string; // Human-readable agent name
|
|
78
|
+
requestedFields: string[]; // Array of field names to request
|
|
79
|
+
purpose: string; // Clear explanation of why you need access
|
|
80
|
+
category?: string; // Optional category (shopping, travel, etc)
|
|
81
|
+
expiresIn?: number; // Optional expiration time in seconds
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Returns:**
|
|
86
|
+
```typescript
|
|
87
|
+
{
|
|
88
|
+
requestId: string; // Use this to check status
|
|
89
|
+
status: 'pending' | 'approved' | 'denied' | 'expired';
|
|
90
|
+
grantedData?: Record<string, any>; // Present if approved
|
|
91
|
+
expiresAt?: string; // ISO timestamp
|
|
92
|
+
message?: string;
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### `checkStatus(requestId: string): Promise<DisclosureStatus>`
|
|
97
|
+
|
|
98
|
+
Check the current status of an access request.
|
|
99
|
+
|
|
100
|
+
#### `waitForApproval(requestId: string, maxWaitMs?: number, pollIntervalMs?: number): Promise<AccessResponse>`
|
|
101
|
+
|
|
102
|
+
Poll for approval with automatic retries.
|
|
103
|
+
|
|
104
|
+
**Parameters:**
|
|
105
|
+
- `requestId`: The ID returned from `requestAccess()`
|
|
106
|
+
- `maxWaitMs`: Maximum time to wait (default: 300000 = 5 minutes)
|
|
107
|
+
- `pollIntervalMs`: Time between polls (default: 2000 = 2 seconds)
|
|
108
|
+
|
|
109
|
+
#### `submitReverseDisclosure(request: ReverseDisclosureRequest): Promise<ReverseDisclosureResponse>`
|
|
110
|
+
|
|
111
|
+
Submit data to user's vault (reverse data flow).
|
|
112
|
+
|
|
113
|
+
**Parameters:**
|
|
114
|
+
```typescript
|
|
115
|
+
{
|
|
116
|
+
serviceId: string;
|
|
117
|
+
serviceName: string;
|
|
118
|
+
dataOffered: Array<{
|
|
119
|
+
field: string;
|
|
120
|
+
value: string;
|
|
121
|
+
category: string;
|
|
122
|
+
}>;
|
|
123
|
+
purpose: string;
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Examples
|
|
128
|
+
|
|
129
|
+
See the `/examples` directory for complete integration examples:
|
|
130
|
+
|
|
131
|
+
- `openrouter/` - AI agent using OpenRouter free models
|
|
132
|
+
- `node-express/` - Express.js server with webhook handling
|
|
133
|
+
- `langchain/` - LangChain tool integration
|
|
134
|
+
- `python/` - Python SDK wrapper
|
|
135
|
+
|
|
136
|
+
## Error Handling
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
try {
|
|
140
|
+
const response = await client.requestAccess({...});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error.message.includes('timeout')) {
|
|
143
|
+
console.error('Request timed out');
|
|
144
|
+
} else if (error.message.includes('401')) {
|
|
145
|
+
console.error('Invalid API key');
|
|
146
|
+
} else {
|
|
147
|
+
console.error('Request failed:', error.message);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Security Best Practices
|
|
153
|
+
|
|
154
|
+
1. **Never expose your API key** in client-side code
|
|
155
|
+
2. **Use environment variables** to store credentials
|
|
156
|
+
3. **Rotate keys regularly** using the ShadowKey dashboard
|
|
157
|
+
4. **Set appropriate rate limits** for your use case
|
|
158
|
+
5. **Validate all data** received from the vault
|
|
159
|
+
6. **Use HTTPS** for all requests (enforced by default)
|
|
160
|
+
|
|
161
|
+
## Rate Limits
|
|
162
|
+
|
|
163
|
+
Default rate limits per API key:
|
|
164
|
+
- 100 requests per minute
|
|
165
|
+
- 1000 requests per hour
|
|
166
|
+
- 10000 requests per day
|
|
167
|
+
|
|
168
|
+
Contact support for enterprise limits.
|
|
169
|
+
|
|
170
|
+
## Support
|
|
171
|
+
|
|
172
|
+
- Documentation: https://shadowkey.dev/docs
|
|
173
|
+
- Issues: https://github.com/shadowkey/agent-sdk/issues
|
|
174
|
+
- Discord: https://discord.gg/shadowkey
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
interface ShadowKeyConfig {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
retryAttempts?: number;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface AccessRequest {
|
|
9
|
+
agentId: string;
|
|
10
|
+
agentName: string;
|
|
11
|
+
requestedFields: string[];
|
|
12
|
+
purpose: string;
|
|
13
|
+
category?: string;
|
|
14
|
+
expiresIn?: number;
|
|
15
|
+
}
|
|
16
|
+
interface AccessResponse {
|
|
17
|
+
requestId: string;
|
|
18
|
+
status: 'pending' | 'approved' | 'denied' | 'expired';
|
|
19
|
+
grantedFields?: string[];
|
|
20
|
+
grantedData?: Record<string, any>;
|
|
21
|
+
expiresAt?: string;
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
interface ReverseDisclosureRequest {
|
|
25
|
+
serviceId: string;
|
|
26
|
+
serviceName: string;
|
|
27
|
+
dataOffered: Array<{
|
|
28
|
+
field: string;
|
|
29
|
+
value: string;
|
|
30
|
+
category: string;
|
|
31
|
+
}>;
|
|
32
|
+
purpose: string;
|
|
33
|
+
}
|
|
34
|
+
interface ReverseDisclosureResponse {
|
|
35
|
+
receiptId: string;
|
|
36
|
+
status: 'accepted' | 'rejected';
|
|
37
|
+
storedFields?: string[];
|
|
38
|
+
message?: string;
|
|
39
|
+
}
|
|
40
|
+
interface DisclosureStatus {
|
|
41
|
+
requestId: string;
|
|
42
|
+
status: 'pending' | 'approved' | 'denied' | 'expired';
|
|
43
|
+
approvedAt?: string;
|
|
44
|
+
deniedAt?: string;
|
|
45
|
+
expiresAt?: string;
|
|
46
|
+
grantedFields?: string[];
|
|
47
|
+
grantedData?: Record<string, any>;
|
|
48
|
+
}
|
|
49
|
+
interface APIError {
|
|
50
|
+
code: string;
|
|
51
|
+
message: string;
|
|
52
|
+
details?: any;
|
|
53
|
+
}
|
|
54
|
+
interface RequestMetadata {
|
|
55
|
+
timestamp: number;
|
|
56
|
+
signature: string;
|
|
57
|
+
nonce: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare class ShadowKeyClient {
|
|
61
|
+
private config;
|
|
62
|
+
constructor(config: ShadowKeyConfig);
|
|
63
|
+
private log;
|
|
64
|
+
private generateSignature;
|
|
65
|
+
private makeRequest;
|
|
66
|
+
requestAccess(request: AccessRequest): Promise<AccessResponse>;
|
|
67
|
+
checkStatus(requestId: string): Promise<DisclosureStatus>;
|
|
68
|
+
submitReverseDisclosure(request: ReverseDisclosureRequest): Promise<ReverseDisclosureResponse>;
|
|
69
|
+
waitForApproval(requestId: string, maxWaitMs?: number, pollIntervalMs?: number): Promise<AccessResponse>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { type APIError, type AccessRequest, type AccessResponse, type DisclosureStatus, type RequestMetadata, type ReverseDisclosureRequest, type ReverseDisclosureResponse, ShadowKeyClient, type ShadowKeyConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
interface ShadowKeyConfig {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
retryAttempts?: number;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
}
|
|
8
|
+
interface AccessRequest {
|
|
9
|
+
agentId: string;
|
|
10
|
+
agentName: string;
|
|
11
|
+
requestedFields: string[];
|
|
12
|
+
purpose: string;
|
|
13
|
+
category?: string;
|
|
14
|
+
expiresIn?: number;
|
|
15
|
+
}
|
|
16
|
+
interface AccessResponse {
|
|
17
|
+
requestId: string;
|
|
18
|
+
status: 'pending' | 'approved' | 'denied' | 'expired';
|
|
19
|
+
grantedFields?: string[];
|
|
20
|
+
grantedData?: Record<string, any>;
|
|
21
|
+
expiresAt?: string;
|
|
22
|
+
message?: string;
|
|
23
|
+
}
|
|
24
|
+
interface ReverseDisclosureRequest {
|
|
25
|
+
serviceId: string;
|
|
26
|
+
serviceName: string;
|
|
27
|
+
dataOffered: Array<{
|
|
28
|
+
field: string;
|
|
29
|
+
value: string;
|
|
30
|
+
category: string;
|
|
31
|
+
}>;
|
|
32
|
+
purpose: string;
|
|
33
|
+
}
|
|
34
|
+
interface ReverseDisclosureResponse {
|
|
35
|
+
receiptId: string;
|
|
36
|
+
status: 'accepted' | 'rejected';
|
|
37
|
+
storedFields?: string[];
|
|
38
|
+
message?: string;
|
|
39
|
+
}
|
|
40
|
+
interface DisclosureStatus {
|
|
41
|
+
requestId: string;
|
|
42
|
+
status: 'pending' | 'approved' | 'denied' | 'expired';
|
|
43
|
+
approvedAt?: string;
|
|
44
|
+
deniedAt?: string;
|
|
45
|
+
expiresAt?: string;
|
|
46
|
+
grantedFields?: string[];
|
|
47
|
+
grantedData?: Record<string, any>;
|
|
48
|
+
}
|
|
49
|
+
interface APIError {
|
|
50
|
+
code: string;
|
|
51
|
+
message: string;
|
|
52
|
+
details?: any;
|
|
53
|
+
}
|
|
54
|
+
interface RequestMetadata {
|
|
55
|
+
timestamp: number;
|
|
56
|
+
signature: string;
|
|
57
|
+
nonce: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare class ShadowKeyClient {
|
|
61
|
+
private config;
|
|
62
|
+
constructor(config: ShadowKeyConfig);
|
|
63
|
+
private log;
|
|
64
|
+
private generateSignature;
|
|
65
|
+
private makeRequest;
|
|
66
|
+
requestAccess(request: AccessRequest): Promise<AccessResponse>;
|
|
67
|
+
checkStatus(requestId: string): Promise<DisclosureStatus>;
|
|
68
|
+
submitReverseDisclosure(request: ReverseDisclosureRequest): Promise<ReverseDisclosureResponse>;
|
|
69
|
+
waitForApproval(requestId: string, maxWaitMs?: number, pollIntervalMs?: number): Promise<AccessResponse>;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { type APIError, type AccessRequest, type AccessResponse, type DisclosureStatus, type RequestMetadata, type ReverseDisclosureRequest, type ReverseDisclosureResponse, ShadowKeyClient, type ShadowKeyConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
ShadowKeyClient: () => ShadowKeyClient
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/client.ts
|
|
28
|
+
var ShadowKeyClient = class {
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.config = {
|
|
31
|
+
timeout: 3e4,
|
|
32
|
+
retryAttempts: 3,
|
|
33
|
+
debug: false,
|
|
34
|
+
...config
|
|
35
|
+
};
|
|
36
|
+
if (!this.config.apiUrl) {
|
|
37
|
+
throw new Error("apiUrl is required");
|
|
38
|
+
}
|
|
39
|
+
if (!this.config.apiKey) {
|
|
40
|
+
throw new Error("apiKey is required");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
log(message, data) {
|
|
44
|
+
if (this.config.debug) {
|
|
45
|
+
if (data && typeof data === "object") {
|
|
46
|
+
const sanitized = { ...data };
|
|
47
|
+
const sensitiveFields = ["grantedData", "grantedFields", "response_data", "decrypted_data"];
|
|
48
|
+
sensitiveFields.forEach((field) => {
|
|
49
|
+
if (sanitized[field]) {
|
|
50
|
+
sanitized[field] = "[REDACTED]";
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
console.log("[ShadowKey SDK]", message, sanitized);
|
|
54
|
+
} else {
|
|
55
|
+
console.log("[ShadowKey SDK]", message, data);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async generateSignature(payload) {
|
|
60
|
+
const timestamp = Date.now();
|
|
61
|
+
const nonceBytes = new Uint8Array(12);
|
|
62
|
+
crypto.getRandomValues(nonceBytes);
|
|
63
|
+
const nonce = Array.from(nonceBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
64
|
+
const message = JSON.stringify({ ...payload, timestamp, nonce });
|
|
65
|
+
const encoder = new TextEncoder();
|
|
66
|
+
const data = encoder.encode(message + this.config.apiKey);
|
|
67
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
68
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
69
|
+
const signature = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
70
|
+
return { timestamp, nonce, signature };
|
|
71
|
+
}
|
|
72
|
+
async makeRequest(endpoint, method, body, attempt = 1) {
|
|
73
|
+
const metadata = await this.generateSignature(body || {});
|
|
74
|
+
const url = `${this.config.apiUrl}${endpoint}`;
|
|
75
|
+
this.log(`${method} ${url}`, body);
|
|
76
|
+
try {
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
79
|
+
const response = await fetch(url, {
|
|
80
|
+
method,
|
|
81
|
+
headers: {
|
|
82
|
+
"Content-Type": "application/json",
|
|
83
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
84
|
+
"X-Timestamp": metadata.timestamp.toString(),
|
|
85
|
+
"X-Nonce": metadata.nonce,
|
|
86
|
+
"X-Signature": metadata.signature
|
|
87
|
+
},
|
|
88
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
89
|
+
signal: controller.signal
|
|
90
|
+
});
|
|
91
|
+
clearTimeout(timeoutId);
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const error = await response.json().catch(() => ({
|
|
94
|
+
code: "UNKNOWN_ERROR",
|
|
95
|
+
message: `HTTP ${response.status}: ${response.statusText}`
|
|
96
|
+
}));
|
|
97
|
+
const isClientError = response.status >= 400 && response.status < 500;
|
|
98
|
+
if (isClientError) {
|
|
99
|
+
const err = new Error(error.message || "Request failed");
|
|
100
|
+
err.isClientError = true;
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
throw new Error(error.message || "Request failed");
|
|
104
|
+
}
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
this.log("Response:", data);
|
|
107
|
+
return data;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
this.log("Request failed:", error.message);
|
|
110
|
+
const shouldRetry = attempt < this.config.retryAttempts && error.name !== "AbortError" && !error.isClientError;
|
|
111
|
+
if (shouldRetry) {
|
|
112
|
+
const delay = Math.pow(2, attempt) * 1e3;
|
|
113
|
+
this.log(`Retrying in ${delay}ms...`, { attempt: attempt + 1, max: this.config.retryAttempts });
|
|
114
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
115
|
+
return this.makeRequest(endpoint, method, body, attempt + 1);
|
|
116
|
+
}
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async requestAccess(request) {
|
|
121
|
+
return this.makeRequest("/access-request", "POST", request);
|
|
122
|
+
}
|
|
123
|
+
async checkStatus(requestId) {
|
|
124
|
+
return this.makeRequest(`/access-status/${requestId}`, "GET");
|
|
125
|
+
}
|
|
126
|
+
async submitReverseDisclosure(request) {
|
|
127
|
+
return this.makeRequest("/reverse-disclosure", "POST", request);
|
|
128
|
+
}
|
|
129
|
+
async waitForApproval(requestId, maxWaitMs = 3e5, pollIntervalMs = 2e3) {
|
|
130
|
+
const startTime = Date.now();
|
|
131
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
132
|
+
const status = await this.checkStatus(requestId);
|
|
133
|
+
if (status.status === "approved") {
|
|
134
|
+
return {
|
|
135
|
+
requestId,
|
|
136
|
+
status: "approved",
|
|
137
|
+
grantedFields: status.grantedFields || [],
|
|
138
|
+
grantedData: status.grantedData || {},
|
|
139
|
+
expiresAt: status.expiresAt,
|
|
140
|
+
message: "Access granted"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (status.status === "denied") {
|
|
144
|
+
return {
|
|
145
|
+
requestId,
|
|
146
|
+
status: "denied",
|
|
147
|
+
message: "Access denied by user"
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (status.status === "expired") {
|
|
151
|
+
return {
|
|
152
|
+
requestId,
|
|
153
|
+
status: "expired",
|
|
154
|
+
message: "Request expired"
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
158
|
+
}
|
|
159
|
+
throw new Error("Timeout waiting for approval");
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
163
|
+
0 && (module.exports = {
|
|
164
|
+
ShadowKeyClient
|
|
165
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// src/client.ts
|
|
2
|
+
var ShadowKeyClient = class {
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = {
|
|
5
|
+
timeout: 3e4,
|
|
6
|
+
retryAttempts: 3,
|
|
7
|
+
debug: false,
|
|
8
|
+
...config
|
|
9
|
+
};
|
|
10
|
+
if (!this.config.apiUrl) {
|
|
11
|
+
throw new Error("apiUrl is required");
|
|
12
|
+
}
|
|
13
|
+
if (!this.config.apiKey) {
|
|
14
|
+
throw new Error("apiKey is required");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
log(message, data) {
|
|
18
|
+
if (this.config.debug) {
|
|
19
|
+
if (data && typeof data === "object") {
|
|
20
|
+
const sanitized = { ...data };
|
|
21
|
+
const sensitiveFields = ["grantedData", "grantedFields", "response_data", "decrypted_data"];
|
|
22
|
+
sensitiveFields.forEach((field) => {
|
|
23
|
+
if (sanitized[field]) {
|
|
24
|
+
sanitized[field] = "[REDACTED]";
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
console.log("[ShadowKey SDK]", message, sanitized);
|
|
28
|
+
} else {
|
|
29
|
+
console.log("[ShadowKey SDK]", message, data);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async generateSignature(payload) {
|
|
34
|
+
const timestamp = Date.now();
|
|
35
|
+
const nonceBytes = new Uint8Array(12);
|
|
36
|
+
crypto.getRandomValues(nonceBytes);
|
|
37
|
+
const nonce = Array.from(nonceBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
38
|
+
const message = JSON.stringify({ ...payload, timestamp, nonce });
|
|
39
|
+
const encoder = new TextEncoder();
|
|
40
|
+
const data = encoder.encode(message + this.config.apiKey);
|
|
41
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
42
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
43
|
+
const signature = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
44
|
+
return { timestamp, nonce, signature };
|
|
45
|
+
}
|
|
46
|
+
async makeRequest(endpoint, method, body, attempt = 1) {
|
|
47
|
+
const metadata = await this.generateSignature(body || {});
|
|
48
|
+
const url = `${this.config.apiUrl}${endpoint}`;
|
|
49
|
+
this.log(`${method} ${url}`, body);
|
|
50
|
+
try {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
|
|
53
|
+
const response = await fetch(url, {
|
|
54
|
+
method,
|
|
55
|
+
headers: {
|
|
56
|
+
"Content-Type": "application/json",
|
|
57
|
+
"Authorization": `Bearer ${this.config.apiKey}`,
|
|
58
|
+
"X-Timestamp": metadata.timestamp.toString(),
|
|
59
|
+
"X-Nonce": metadata.nonce,
|
|
60
|
+
"X-Signature": metadata.signature
|
|
61
|
+
},
|
|
62
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
63
|
+
signal: controller.signal
|
|
64
|
+
});
|
|
65
|
+
clearTimeout(timeoutId);
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const error = await response.json().catch(() => ({
|
|
68
|
+
code: "UNKNOWN_ERROR",
|
|
69
|
+
message: `HTTP ${response.status}: ${response.statusText}`
|
|
70
|
+
}));
|
|
71
|
+
const isClientError = response.status >= 400 && response.status < 500;
|
|
72
|
+
if (isClientError) {
|
|
73
|
+
const err = new Error(error.message || "Request failed");
|
|
74
|
+
err.isClientError = true;
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
throw new Error(error.message || "Request failed");
|
|
78
|
+
}
|
|
79
|
+
const data = await response.json();
|
|
80
|
+
this.log("Response:", data);
|
|
81
|
+
return data;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this.log("Request failed:", error.message);
|
|
84
|
+
const shouldRetry = attempt < this.config.retryAttempts && error.name !== "AbortError" && !error.isClientError;
|
|
85
|
+
if (shouldRetry) {
|
|
86
|
+
const delay = Math.pow(2, attempt) * 1e3;
|
|
87
|
+
this.log(`Retrying in ${delay}ms...`, { attempt: attempt + 1, max: this.config.retryAttempts });
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
89
|
+
return this.makeRequest(endpoint, method, body, attempt + 1);
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async requestAccess(request) {
|
|
95
|
+
return this.makeRequest("/access-request", "POST", request);
|
|
96
|
+
}
|
|
97
|
+
async checkStatus(requestId) {
|
|
98
|
+
return this.makeRequest(`/access-status/${requestId}`, "GET");
|
|
99
|
+
}
|
|
100
|
+
async submitReverseDisclosure(request) {
|
|
101
|
+
return this.makeRequest("/reverse-disclosure", "POST", request);
|
|
102
|
+
}
|
|
103
|
+
async waitForApproval(requestId, maxWaitMs = 3e5, pollIntervalMs = 2e3) {
|
|
104
|
+
const startTime = Date.now();
|
|
105
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
106
|
+
const status = await this.checkStatus(requestId);
|
|
107
|
+
if (status.status === "approved") {
|
|
108
|
+
return {
|
|
109
|
+
requestId,
|
|
110
|
+
status: "approved",
|
|
111
|
+
grantedFields: status.grantedFields || [],
|
|
112
|
+
grantedData: status.grantedData || {},
|
|
113
|
+
expiresAt: status.expiresAt,
|
|
114
|
+
message: "Access granted"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (status.status === "denied") {
|
|
118
|
+
return {
|
|
119
|
+
requestId,
|
|
120
|
+
status: "denied",
|
|
121
|
+
message: "Access denied by user"
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (status.status === "expired") {
|
|
125
|
+
return {
|
|
126
|
+
requestId,
|
|
127
|
+
status: "expired",
|
|
128
|
+
message: "Request expired"
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
132
|
+
}
|
|
133
|
+
throw new Error("Timeout waiting for approval");
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
export {
|
|
137
|
+
ShadowKeyClient
|
|
138
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shadowkey-agent-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official SDK for integrating AI agents with ShadowKey privacy vault",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
22
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
23
|
+
"test": "vitest",
|
|
24
|
+
"prepublishOnly": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"shadowkey",
|
|
28
|
+
"privacy",
|
|
29
|
+
"ai-agent",
|
|
30
|
+
"data-vault",
|
|
31
|
+
"zero-knowledge"
|
|
32
|
+
],
|
|
33
|
+
"author": "ShadowKey Team",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/kimboltpro3-create/shadowkey"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"tsup": "^8.0.0",
|
|
42
|
+
"typescript": "^5.5.3",
|
|
43
|
+
"vitest": "^1.0.0"
|
|
44
|
+
}
|
|
45
|
+
}
|