Skip to content

Commit d88459f

Browse files
committed
Minor updates
1 parent 48241f6 commit d88459f

5 files changed

Lines changed: 222 additions & 146 deletions

File tree

README.md

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@
77

88
Official JavaScript SDK for LicenseChain - Secure license management for web applications.
99

10+
> Consolidation note: use `LicenseChain-JavaScript-SDK` for browser and isomorphic code. `LicenseChain-NodeJS-SDK` remains the server-only track while the shared HTTP layer is consolidated under the workspace plan in `../docs/SDK_CONSOLIDATION.md`.
11+
12+
## API Base
13+
14+
- Canonical API base: `https://api.licensechain.app/v1`
15+
- Dashboard reference: [LicenseChain/Dashboard](https://github.com/LicenseChain/Dashboard)
16+
- Core API reference: [LicenseChain/api](https://github.com/LicenseChain/api)
17+
- SDK governance: [SDK_GOVERNANCE.md](../SDK_GOVERNANCE.md)
18+
- Workspace conformance: [conformance/README.md](../conformance/README.md)
19+
1020
## 🚀 Features
1121

1222
- **🔐 Secure Authentication** - User registration, login, and session management
@@ -59,7 +69,8 @@ import LicenseChain from 'licensechain-sdk';
5969
const client = new LicenseChain({
6070
apiKey: 'your-api-key',
6171
appName: 'your-app-name',
62-
version: '1.0.0'
72+
version: '1.0.0',
73+
baseUrl: 'https://api.licensechain.app/v1'
6374
});
6475

6576
// Connect to LicenseChain
@@ -167,7 +178,7 @@ await client.startWebhookListener();
167178

168179
## 📚 API Endpoints
169180

170-
All endpoints automatically use the `/v1` prefix when connecting to `https://api.licensechain.app`.
181+
All endpoints target the LicenseChain HTTP API at `https://api.licensechain.app/v1`. The client accepts either the canonical `/v1` base or the root host and normalizes requests to the same API version.
171182

172183
### Base URL
173184
- **Production**: `https://api.licensechain.app/v1`
@@ -201,7 +212,7 @@ const client = new LicenseChain({
201212
apiKey: 'your-api-key',
202213
appName: 'your-app-name',
203214
version: '1.0.0',
204-
baseUrl: 'https://api.licensechain.app' // Optional
215+
baseUrl: 'https://api.licensechain.app/v1' // Optional
205216
});
206217
```
207218

@@ -307,7 +318,7 @@ export LICENSECHAIN_APP_NAME=your-app-name
307318
export LICENSECHAIN_APP_VERSION=1.0.0
308319

309320
# Optional
310-
export LICENSECHAIN_BASE_URL=https://api.licensechain.app
321+
export LICENSECHAIN_BASE_URL=https://api.licensechain.app/v1
311322
export LICENSECHAIN_DEBUG=true
312323
```
313324

@@ -318,7 +329,7 @@ const client = new LicenseChain({
318329
apiKey: 'your-api-key',
319330
appName: 'your-app-name',
320331
version: '1.0.0',
321-
baseUrl: 'https://api.licensechain.app',
332+
baseUrl: 'https://api.licensechain.app/v1',
322333
timeout: 30000, // Request timeout in milliseconds
323334
retries: 3, // Number of retry attempts
324335
debug: false, // Enable debug logging
@@ -469,7 +480,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
469480

470481
## 🆘 Support
471482

472-
- **Documentation**: [https://docs.licensechain.app/javascript](https://docs.licensechain.app/javascript)
483+
- **Documentation**: [https://docs.licensechain.app/sdks/javascript](https://docs.licensechain.app/sdks/javascript)
473484
- **Issues**: [GitHub Issues](https://github.com/LicenseChain/LicenseChain-JavaScript-SDK/issues)
474485
- **Discord**: [LicenseChain Discord](https://discord.gg/licensechain)
475486
- **Email**: support@licensechain.app
@@ -483,19 +494,19 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
483494
---
484495

485496
**Made with ❤️ for the JavaScript community**
486-
487-
## LicenseChain API (v1)
488-
489-
This SDK targets the **LicenseChain HTTP API v1** implemented by the open-source API service.
490-
491-
- **Production base URL:** https://api.licensechain.app/v1
492-
- **API repository (source of routes & behavior):** https://github.com/LicenseChain/api
493-
- **Baseline REST mapping (documented for integrators):**
494-
- GET /health
495-
- POST /auth/register
496-
- POST /licenses/verify
497-
- PATCH /licenses/:id/revoke
498-
- PATCH /licenses/:id/activate
499-
- PATCH /licenses/:id/extend
500-
- GET /analytics/stats
501-
497+
498+
## LicenseChain API (v1)
499+
500+
This SDK targets the **LicenseChain HTTP API v1** implemented by the open-source API service.
501+
502+
- **Production base URL:** https://api.licensechain.app/v1
503+
- **API repository (source of routes & behavior):** https://github.com/LicenseChain/api
504+
- **Baseline REST mapping (documented for integrators):**
505+
- GET /health
506+
- POST /auth/register
507+
- POST /licenses/verify
508+
- PATCH /licenses/:id/revoke
509+
- PATCH /licenses/:id/activate
510+
- PATCH /licenses/:id/extend
511+
- GET /analytics/stats
512+

src/api-client.ts

Lines changed: 26 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -8,146 +8,51 @@ import {
88
NotFoundError,
99
RateLimitError
1010
} from './exceptions';
11+
import { LicenseChainHttpCore } from './http-core';
1112

1213
export class ApiClient {
13-
private config: Configuration;
14-
private baseUrl: string;
14+
private readonly core: LicenseChainHttpCore;
1515

1616
constructor(config: Configuration) {
17-
this.config = config;
18-
this.baseUrl = config.getBaseUrl().replace(/\/$/, '');
17+
this.core = new LicenseChainHttpCore({
18+
apiKey: config.getApiKey(),
19+
baseUrl: config.getBaseUrl(),
20+
deserialize: jsonDeserialize,
21+
exceptions: {
22+
authentication: (message) => new AuthenticationError(message),
23+
network: (message) => new NetworkError(message),
24+
notFound: (message) => new NotFoundError(message),
25+
rateLimit: (message) => new RateLimitError(message),
26+
server: (message) => new ServerError(message),
27+
validation: (message) => new ValidationError(message),
28+
},
29+
platform: 'javascript-sdk',
30+
retries: config.getRetries(),
31+
retryWithBackoff,
32+
serialize: jsonSerialize,
33+
timeout: config.getTimeout(),
34+
userAgent: 'LicenseChain-JavaScript-SDK/1.0.0',
35+
});
1936
}
2037

2138
async get<T = any>(endpoint: string, params?: Record<string, any>): Promise<T> {
22-
return this.makeRequest<T>('GET', endpoint, undefined, params);
39+
return this.core.get<T>(endpoint, params);
2340
}
2441

2542
async post<T = any>(endpoint: string, data?: any): Promise<T> {
26-
return this.makeRequest<T>('POST', endpoint, data);
43+
return this.core.post<T>(endpoint, data);
2744
}
2845

2946
async put<T = any>(endpoint: string, data?: any): Promise<T> {
30-
return this.makeRequest<T>('PUT', endpoint, data);
47+
return this.core.put<T>(endpoint, data);
3148
}
3249

3350
async patch<T = any>(endpoint: string, data?: any): Promise<T> {
34-
return this.makeRequest<T>('PATCH', endpoint, data);
51+
return this.core.patch<T>(endpoint, data);
3552
}
3653

3754
async delete<T = any>(endpoint: string, data?: any): Promise<T> {
38-
return this.makeRequest<T>('DELETE', endpoint, data);
39-
}
40-
41-
private async makeRequest<T>(
42-
method: string,
43-
endpoint: string,
44-
data?: any,
45-
params?: Record<string, any>
46-
): Promise<T> {
47-
const url = this.buildUrl(endpoint, params);
48-
const requestOptions = this.buildRequestOptions(method, data);
49-
50-
return retryWithBackoff(async () => {
51-
return this.sendRequest<T>(url, requestOptions);
52-
}, this.config.getRetries());
53-
}
54-
55-
private buildUrl(endpoint: string, params?: Record<string, any>): string {
56-
// Ensure endpoint starts with /v1 prefix
57-
const normalizedEndpoint = endpoint.startsWith('/v1/')
58-
? endpoint
59-
: endpoint.startsWith('/')
60-
? `/v1${endpoint}`
61-
: `/v1/${endpoint}`;
62-
63-
let url = `${this.baseUrl}${normalizedEndpoint}`;
64-
65-
if (params && Object.keys(params).length > 0) {
66-
const searchParams = new URLSearchParams();
67-
for (const [key, value] of Object.entries(params)) {
68-
if (value !== undefined && value !== null) {
69-
searchParams.append(key, String(value));
70-
}
71-
}
72-
url += `?${searchParams.toString()}`;
73-
}
74-
75-
return url;
76-
}
77-
78-
private buildRequestOptions(method: string, data?: any): RequestInit {
79-
const options: RequestInit = {
80-
method,
81-
headers: {
82-
'Authorization': `Bearer ${this.config.getApiKey()}`,
83-
'Content-Type': 'application/json',
84-
'X-API-Version': '1.0',
85-
'X-Platform': 'javascript-sdk',
86-
'User-Agent': 'LicenseChain-JavaScript-SDK/1.0.0'
87-
}
88-
};
89-
90-
if (data) {
91-
options.body = jsonSerialize(data);
92-
}
93-
94-
return options;
95-
}
96-
97-
private async sendRequest<T>(url: string, options: RequestInit): Promise<T> {
98-
try {
99-
const response = await fetch(url, {
100-
...options,
101-
signal: AbortSignal.timeout(this.config.getTimeout())
102-
});
103-
104-
if (response.ok) {
105-
const text = await response.text();
106-
return text ? jsonDeserialize(text) : {} as T;
107-
}
108-
109-
const errorText = await response.text();
110-
let errorMessage = 'Unknown error';
111-
112-
try {
113-
const errorData = jsonDeserialize(errorText);
114-
errorMessage = errorData.error || errorData.message || errorMessage;
115-
} catch {
116-
errorMessage = errorText || errorMessage;
117-
}
118-
119-
this.handleHttpError(response.status, errorMessage);
120-
} catch (error) {
121-
if (error instanceof Error) {
122-
if (error.name === 'AbortError') {
123-
throw new NetworkError('Request timeout');
124-
}
125-
throw new NetworkError(error.message);
126-
}
127-
throw error;
128-
}
129-
}
130-
131-
private handleHttpError(statusCode: number, message: string): never {
132-
switch (statusCode) {
133-
case 400:
134-
throw new ValidationError(`Bad Request: ${message}`);
135-
case 401:
136-
throw new AuthenticationError(`Unauthorized: ${message}`);
137-
case 403:
138-
throw new AuthenticationError(`Forbidden: ${message}`);
139-
case 404:
140-
throw new NotFoundError(`Not Found: ${message}`);
141-
case 429:
142-
throw new RateLimitError(`Rate Limited: ${message}`);
143-
case 500:
144-
case 502:
145-
case 503:
146-
case 504:
147-
throw new ServerError(`Server Error: ${message}`);
148-
default:
149-
throw new ServerError(`Unexpected response: ${statusCode} ${message}`);
150-
}
55+
return this.core.delete<T>(endpoint, data);
15156
}
15257

15358
async ping(): Promise<any> {

src/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class LicenseChainClient {
7070
static create(apiKey: string, baseUrl?: string): LicenseChainClient {
7171
return new LicenseChainClient({
7272
apiKey,
73-
baseUrl: baseUrl || 'https://api.licensechain.app'
73+
baseUrl: baseUrl || 'https://api.licensechain.app/v1'
7474
});
7575
}
7676

src/configuration.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class Configuration {
1313

1414
constructor(options: ConfigurationOptions) {
1515
this.apiKey = options.apiKey;
16-
this.baseUrl = options.baseUrl || 'https://api.licensechain.app';
16+
this.baseUrl = options.baseUrl || 'https://api.licensechain.app/v1';
1717
this.timeout = options.timeout || 30000; // 30 seconds
1818
this.retries = options.retries || 3;
1919
}
@@ -74,7 +74,7 @@ export class Configuration {
7474
static fromEnvironment(): Configuration {
7575
return new Configuration({
7676
apiKey: process.env.LICENSECHAIN_API_KEY || '',
77-
baseUrl: process.env.LICENSECHAIN_BASE_URL || 'https://api.licensechain.app',
77+
baseUrl: process.env.LICENSECHAIN_BASE_URL || 'https://api.licensechain.app/v1',
7878
timeout: parseInt(process.env.LICENSECHAIN_TIMEOUT || '30000'),
7979
retries: parseInt(process.env.LICENSECHAIN_RETRIES || '3')
8080
});

0 commit comments

Comments
 (0)