@safercity/sdk-core 0.0.1
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/README.md +132 -0
- package/dist/index.cjs +445 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +243 -0
- package/dist/index.d.ts +243 -0
- package/dist/index.js +433 -0
- package/dist/index.js.map +1 -0
- package/package.json +50 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var SaferCityApiError = class _SaferCityApiError extends Error {
|
|
3
|
+
constructor(error, message, status, details) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.error = error;
|
|
6
|
+
this.status = status;
|
|
7
|
+
this.details = details;
|
|
8
|
+
this.name = "SaferCityApiError";
|
|
9
|
+
}
|
|
10
|
+
static fromResponse(response, body) {
|
|
11
|
+
const errorBody = body;
|
|
12
|
+
return new _SaferCityApiError(
|
|
13
|
+
errorBody.error ?? "unknown_error",
|
|
14
|
+
errorBody.message ?? `Request failed with status ${response.status}`,
|
|
15
|
+
response.status,
|
|
16
|
+
errorBody.details
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// src/auth.ts
|
|
22
|
+
var MemoryTokenStorage = class {
|
|
23
|
+
tokens = null;
|
|
24
|
+
get() {
|
|
25
|
+
return this.tokens;
|
|
26
|
+
}
|
|
27
|
+
set(tokens) {
|
|
28
|
+
this.tokens = tokens;
|
|
29
|
+
}
|
|
30
|
+
clear() {
|
|
31
|
+
this.tokens = null;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
function isTokenExpired(expiresAt, bufferMs = 6e4) {
|
|
35
|
+
if (!expiresAt) return false;
|
|
36
|
+
return Date.now() + bufferMs >= expiresAt;
|
|
37
|
+
}
|
|
38
|
+
function createAuthHeader(token, type = "Bearer") {
|
|
39
|
+
return `${type} ${token}`;
|
|
40
|
+
}
|
|
41
|
+
function parseJwtPayload(token) {
|
|
42
|
+
try {
|
|
43
|
+
const parts = token.split(".");
|
|
44
|
+
if (parts.length !== 3) return null;
|
|
45
|
+
const payload = parts[1];
|
|
46
|
+
const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
|
|
47
|
+
return JSON.parse(decoded);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function getJwtExpiration(token) {
|
|
53
|
+
const payload = parseJwtPayload(token);
|
|
54
|
+
if (!payload?.exp) return null;
|
|
55
|
+
return payload.exp * 1e3;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/streaming.ts
|
|
59
|
+
function parseSSE(buffer) {
|
|
60
|
+
const events = [];
|
|
61
|
+
const lines = buffer.split("\n");
|
|
62
|
+
let currentEvent = {};
|
|
63
|
+
let dataLines = [];
|
|
64
|
+
let remaining = "";
|
|
65
|
+
for (let i = 0; i < lines.length; i++) {
|
|
66
|
+
const line = lines[i];
|
|
67
|
+
if (i === lines.length - 1 && line !== "" && !buffer.endsWith("\n")) {
|
|
68
|
+
remaining = line;
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
if (line === "") {
|
|
72
|
+
if (dataLines.length > 0 || currentEvent.event || currentEvent.id) {
|
|
73
|
+
events.push({
|
|
74
|
+
...currentEvent,
|
|
75
|
+
data: dataLines.join("\n")
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
currentEvent = {};
|
|
79
|
+
dataLines = [];
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const colonIndex = line.indexOf(":");
|
|
83
|
+
if (colonIndex === 0) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
let field;
|
|
87
|
+
let value;
|
|
88
|
+
if (colonIndex === -1) {
|
|
89
|
+
field = line;
|
|
90
|
+
value = "";
|
|
91
|
+
} else {
|
|
92
|
+
field = line.slice(0, colonIndex);
|
|
93
|
+
value = line.slice(colonIndex + 1).replace(/^ /, "");
|
|
94
|
+
}
|
|
95
|
+
switch (field) {
|
|
96
|
+
case "event":
|
|
97
|
+
currentEvent.event = value;
|
|
98
|
+
break;
|
|
99
|
+
case "data":
|
|
100
|
+
dataLines.push(value);
|
|
101
|
+
break;
|
|
102
|
+
case "id":
|
|
103
|
+
currentEvent.id = value;
|
|
104
|
+
break;
|
|
105
|
+
case "retry":
|
|
106
|
+
const retry = parseInt(value, 10);
|
|
107
|
+
if (!isNaN(retry)) {
|
|
108
|
+
currentEvent.retry = retry;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return { parsed: events, remaining };
|
|
114
|
+
}
|
|
115
|
+
var WebStreamAdapter = class {
|
|
116
|
+
supportsNativeSSE() {
|
|
117
|
+
return typeof EventSource !== "undefined";
|
|
118
|
+
}
|
|
119
|
+
createEventSource(url, options) {
|
|
120
|
+
return {
|
|
121
|
+
[Symbol.asyncIterator]() {
|
|
122
|
+
let eventSource = null;
|
|
123
|
+
let resolveNext = null;
|
|
124
|
+
let rejectNext = null;
|
|
125
|
+
const eventQueue = [];
|
|
126
|
+
let done = false;
|
|
127
|
+
let error = null;
|
|
128
|
+
const cleanup = () => {
|
|
129
|
+
if (eventSource) {
|
|
130
|
+
eventSource.close();
|
|
131
|
+
eventSource = null;
|
|
132
|
+
}
|
|
133
|
+
done = true;
|
|
134
|
+
};
|
|
135
|
+
options?.signal?.addEventListener("abort", () => {
|
|
136
|
+
cleanup();
|
|
137
|
+
if (rejectNext) {
|
|
138
|
+
rejectNext(new Error("Aborted"));
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
eventSource = new EventSource(url);
|
|
142
|
+
eventSource.onopen = () => {
|
|
143
|
+
options?.onOpen?.();
|
|
144
|
+
};
|
|
145
|
+
eventSource.onmessage = (event) => {
|
|
146
|
+
const sseEvent = {
|
|
147
|
+
id: event.lastEventId || void 0,
|
|
148
|
+
data: event.data
|
|
149
|
+
};
|
|
150
|
+
if (resolveNext) {
|
|
151
|
+
resolveNext({ value: sseEvent, done: false });
|
|
152
|
+
resolveNext = null;
|
|
153
|
+
rejectNext = null;
|
|
154
|
+
} else {
|
|
155
|
+
eventQueue.push(sseEvent);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
eventSource.onerror = (e) => {
|
|
159
|
+
const err = new Error("EventSource error");
|
|
160
|
+
error = err;
|
|
161
|
+
options?.onError?.(err);
|
|
162
|
+
cleanup();
|
|
163
|
+
if (rejectNext) {
|
|
164
|
+
rejectNext(err);
|
|
165
|
+
resolveNext = null;
|
|
166
|
+
rejectNext = null;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
return {
|
|
170
|
+
async next() {
|
|
171
|
+
if (error) {
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
if (eventQueue.length > 0) {
|
|
175
|
+
return { value: eventQueue.shift(), done: false };
|
|
176
|
+
}
|
|
177
|
+
if (done) {
|
|
178
|
+
return { value: void 0, done: true };
|
|
179
|
+
}
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
resolveNext = resolve;
|
|
182
|
+
rejectNext = reject;
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
async return() {
|
|
186
|
+
cleanup();
|
|
187
|
+
return { value: void 0, done: true };
|
|
188
|
+
},
|
|
189
|
+
async throw(e) {
|
|
190
|
+
cleanup();
|
|
191
|
+
throw e;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var FetchStreamAdapter = class {
|
|
199
|
+
constructor(fetchFn = fetch) {
|
|
200
|
+
this.fetchFn = fetchFn;
|
|
201
|
+
}
|
|
202
|
+
supportsNativeSSE() {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
createEventSource(url, options) {
|
|
206
|
+
const fetchFn = this.fetchFn;
|
|
207
|
+
return {
|
|
208
|
+
[Symbol.asyncIterator]() {
|
|
209
|
+
let reader = null;
|
|
210
|
+
let buffer = "";
|
|
211
|
+
let done = false;
|
|
212
|
+
const eventQueue = [];
|
|
213
|
+
const cleanup = () => {
|
|
214
|
+
if (reader) {
|
|
215
|
+
reader.cancel().catch(() => {
|
|
216
|
+
});
|
|
217
|
+
reader = null;
|
|
218
|
+
}
|
|
219
|
+
done = true;
|
|
220
|
+
};
|
|
221
|
+
options?.signal?.addEventListener("abort", cleanup);
|
|
222
|
+
const fetchPromise = fetchFn(url, {
|
|
223
|
+
method: "GET",
|
|
224
|
+
headers: {
|
|
225
|
+
"Accept": "text/event-stream",
|
|
226
|
+
"Cache-Control": "no-cache",
|
|
227
|
+
...options?.headers
|
|
228
|
+
},
|
|
229
|
+
signal: options?.signal
|
|
230
|
+
}).then((response) => {
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
233
|
+
}
|
|
234
|
+
if (!response.body) {
|
|
235
|
+
throw new Error("Response body is not available");
|
|
236
|
+
}
|
|
237
|
+
options?.onOpen?.();
|
|
238
|
+
reader = response.body.getReader();
|
|
239
|
+
return reader;
|
|
240
|
+
}).catch((err) => {
|
|
241
|
+
options?.onError?.(err);
|
|
242
|
+
throw err;
|
|
243
|
+
});
|
|
244
|
+
const decoder = new TextDecoder();
|
|
245
|
+
return {
|
|
246
|
+
async next() {
|
|
247
|
+
if (eventQueue.length > 0) {
|
|
248
|
+
return { value: eventQueue.shift(), done: false };
|
|
249
|
+
}
|
|
250
|
+
if (done) {
|
|
251
|
+
return { value: void 0, done: true };
|
|
252
|
+
}
|
|
253
|
+
if (!reader) {
|
|
254
|
+
reader = await fetchPromise;
|
|
255
|
+
}
|
|
256
|
+
while (eventQueue.length === 0 && !done) {
|
|
257
|
+
const { value, done: readerDone } = await reader.read();
|
|
258
|
+
if (readerDone) {
|
|
259
|
+
done = true;
|
|
260
|
+
if (buffer.trim()) {
|
|
261
|
+
const { parsed: parsed2 } = parseSSE(buffer + "\n\n");
|
|
262
|
+
eventQueue.push(...parsed2);
|
|
263
|
+
}
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
buffer += decoder.decode(value, { stream: true });
|
|
267
|
+
const { parsed, remaining } = parseSSE(buffer);
|
|
268
|
+
buffer = remaining;
|
|
269
|
+
eventQueue.push(...parsed);
|
|
270
|
+
}
|
|
271
|
+
if (eventQueue.length > 0) {
|
|
272
|
+
return { value: eventQueue.shift(), done: false };
|
|
273
|
+
}
|
|
274
|
+
return { value: void 0, done: true };
|
|
275
|
+
},
|
|
276
|
+
async return() {
|
|
277
|
+
cleanup();
|
|
278
|
+
return { value: void 0, done: true };
|
|
279
|
+
},
|
|
280
|
+
async throw(e) {
|
|
281
|
+
cleanup();
|
|
282
|
+
throw e;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
function createStreamAdapter(fetchFn) {
|
|
290
|
+
if (typeof EventSource !== "undefined") {
|
|
291
|
+
return new WebStreamAdapter();
|
|
292
|
+
}
|
|
293
|
+
return new FetchStreamAdapter(fetchFn ?? fetch);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/client.ts
|
|
297
|
+
function createHeaders(config, options) {
|
|
298
|
+
const headers = {
|
|
299
|
+
"Content-Type": "application/json",
|
|
300
|
+
"Accept": "application/json",
|
|
301
|
+
...config.headers,
|
|
302
|
+
...options?.headers
|
|
303
|
+
};
|
|
304
|
+
if (config.token) {
|
|
305
|
+
headers["Authorization"] = createAuthHeader(config.token);
|
|
306
|
+
}
|
|
307
|
+
if (config.tenantId) {
|
|
308
|
+
headers["X-Tenant-ID"] = config.tenantId;
|
|
309
|
+
}
|
|
310
|
+
return headers;
|
|
311
|
+
}
|
|
312
|
+
function createTimeoutSignal(timeout, existingSignal) {
|
|
313
|
+
const controller = new AbortController();
|
|
314
|
+
const timeoutId = setTimeout(() => {
|
|
315
|
+
controller.abort(new Error(`Request timeout after ${timeout}ms`));
|
|
316
|
+
}, timeout);
|
|
317
|
+
if (existingSignal) {
|
|
318
|
+
existingSignal.addEventListener("abort", () => {
|
|
319
|
+
clearTimeout(timeoutId);
|
|
320
|
+
controller.abort(existingSignal.reason);
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
return controller.signal;
|
|
324
|
+
}
|
|
325
|
+
var BaseClient = class {
|
|
326
|
+
config;
|
|
327
|
+
fetchFn;
|
|
328
|
+
constructor(options) {
|
|
329
|
+
this.config = {
|
|
330
|
+
timeout: 3e4,
|
|
331
|
+
...options,
|
|
332
|
+
baseUrl: options.baseUrl.replace(/\/$/, "")
|
|
333
|
+
// Remove trailing slash
|
|
334
|
+
};
|
|
335
|
+
this.fetchFn = options.fetch ?? fetch;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Update authentication token
|
|
339
|
+
*/
|
|
340
|
+
setToken(token) {
|
|
341
|
+
this.config.token = token;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Update tenant ID
|
|
345
|
+
*/
|
|
346
|
+
setTenantId(tenantId) {
|
|
347
|
+
this.config.tenantId = tenantId;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Get current configuration (read-only)
|
|
351
|
+
*/
|
|
352
|
+
getConfig() {
|
|
353
|
+
return { ...this.config };
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Build full URL from path
|
|
357
|
+
*/
|
|
358
|
+
buildUrl(path, query) {
|
|
359
|
+
const url = new URL(path.startsWith("/") ? path : `/${path}`, this.config.baseUrl);
|
|
360
|
+
if (query) {
|
|
361
|
+
Object.entries(query).forEach(([key, value]) => {
|
|
362
|
+
if (value !== void 0) {
|
|
363
|
+
url.searchParams.set(key, String(value));
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
return url.toString();
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Make HTTP request
|
|
371
|
+
*/
|
|
372
|
+
async request(method, path, options) {
|
|
373
|
+
const url = this.buildUrl(path, options?.query);
|
|
374
|
+
const headers = createHeaders(this.config, options);
|
|
375
|
+
const timeout = options?.timeout ?? this.config.timeout;
|
|
376
|
+
const signal = createTimeoutSignal(timeout, options?.signal);
|
|
377
|
+
const response = await this.fetchFn(url, {
|
|
378
|
+
method,
|
|
379
|
+
headers,
|
|
380
|
+
body: options?.body ? JSON.stringify(options.body) : void 0,
|
|
381
|
+
signal
|
|
382
|
+
});
|
|
383
|
+
let data;
|
|
384
|
+
const contentType = response.headers.get("content-type");
|
|
385
|
+
if (contentType?.includes("application/json")) {
|
|
386
|
+
data = await response.json();
|
|
387
|
+
} else {
|
|
388
|
+
data = await response.text();
|
|
389
|
+
}
|
|
390
|
+
if (!response.ok) {
|
|
391
|
+
throw SaferCityApiError.fromResponse(response, data);
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
data,
|
|
395
|
+
status: response.status,
|
|
396
|
+
headers: response.headers
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* GET request
|
|
401
|
+
*/
|
|
402
|
+
async get(path, options) {
|
|
403
|
+
return this.request("GET", path, options);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* POST request
|
|
407
|
+
*/
|
|
408
|
+
async post(path, body, options) {
|
|
409
|
+
return this.request("POST", path, { ...options, body });
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* PUT request
|
|
413
|
+
*/
|
|
414
|
+
async put(path, body, options) {
|
|
415
|
+
return this.request("PUT", path, { ...options, body });
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* PATCH request
|
|
419
|
+
*/
|
|
420
|
+
async patch(path, body, options) {
|
|
421
|
+
return this.request("PATCH", path, { ...options, body });
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* DELETE request
|
|
425
|
+
*/
|
|
426
|
+
async delete(path, options) {
|
|
427
|
+
return this.request("DELETE", path, options);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
export { BaseClient, FetchStreamAdapter, MemoryTokenStorage, SaferCityApiError, WebStreamAdapter, createAuthHeader, createStreamAdapter, getJwtExpiration, isTokenExpired, parseJwtPayload, parseSSE };
|
|
432
|
+
//# sourceMappingURL=index.js.map
|
|
433
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/streaming.ts","../src/client.ts"],"names":["parsed"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Common types for SaferCity SDK\n */\n\nexport interface SaferCityConfig {\n /**\n * Base URL for the SaferCity API\n * @example \"https://api.safercity.com\"\n */\n baseUrl: string;\n\n /**\n * Authentication token (JWT)\n */\n token?: string;\n\n /**\n * Tenant ID for multi-tenant operations\n */\n tenantId?: string;\n\n /**\n * Custom fetch implementation (useful for React Native)\n */\n fetch?: typeof fetch;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeout?: number;\n\n /**\n * Custom headers to include in all requests\n */\n headers?: Record<string, string>;\n}\n\nexport interface RequestOptions {\n /**\n * Additional headers for this request\n */\n headers?: Record<string, string>;\n\n /**\n * Request timeout override\n */\n timeout?: number;\n\n /**\n * Abort signal for cancellation\n */\n signal?: AbortSignal;\n}\n\nexport interface ApiResponse<T> {\n data: T;\n status: number;\n headers: Headers;\n}\n\nexport interface ApiError {\n error: string;\n message: string;\n status: number;\n details?: unknown;\n}\n\nexport class SaferCityApiError extends Error {\n constructor(\n public readonly error: string,\n message: string,\n public readonly status: number,\n public readonly details?: unknown\n ) {\n super(message);\n this.name = 'SaferCityApiError';\n }\n\n static fromResponse(response: Response, body: unknown): SaferCityApiError {\n const errorBody = body as Partial<ApiError>;\n return new SaferCityApiError(\n errorBody.error ?? 'unknown_error',\n errorBody.message ?? `Request failed with status ${response.status}`,\n response.status,\n errorBody.details\n );\n }\n}\n\n/**\n * Server-Sent Event structure\n */\nexport interface ServerSentEvent {\n id?: string;\n event?: string;\n data: string;\n retry?: number;\n}\n\n/**\n * Options for SSE connections\n */\nexport interface EventSourceOptions {\n headers?: Record<string, string>;\n signal?: AbortSignal;\n onOpen?: () => void;\n onError?: (error: Error) => void;\n}\n","/**\n * Authentication utilities for SaferCity SDK\n */\n\nexport interface AuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n tokenType: string;\n}\n\nexport interface TokenStorage {\n get(): AuthTokens | null;\n set(tokens: AuthTokens): void;\n clear(): void;\n}\n\n/**\n * In-memory token storage (default)\n */\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n get(): AuthTokens | null {\n return this.tokens;\n }\n\n set(tokens: AuthTokens): void {\n this.tokens = tokens;\n }\n\n clear(): void {\n this.tokens = null;\n }\n}\n\n/**\n * Check if a token is expired (with buffer)\n */\nexport function isTokenExpired(expiresAt: number | undefined, bufferMs = 60000): boolean {\n if (!expiresAt) return false;\n return Date.now() + bufferMs >= expiresAt;\n}\n\n/**\n * Create Authorization header value\n */\nexport function createAuthHeader(token: string, type = 'Bearer'): string {\n return `${type} ${token}`;\n}\n\n/**\n * Parse JWT payload (without verification)\n * Only use for client-side display, not security decisions\n */\nexport function parseJwtPayload<T = Record<string, unknown>>(token: string): T | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n \n const payload = parts[1];\n const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));\n return JSON.parse(decoded) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Extract expiration from JWT\n */\nexport function getJwtExpiration(token: string): number | null {\n const payload = parseJwtPayload<{ exp?: number }>(token);\n if (!payload?.exp) return null;\n return payload.exp * 1000; // Convert to milliseconds\n}\n\nexport interface SaferCityJwtPayload {\n sub?: string;\n tenantId?: string;\n environment?: string;\n scopes?: string[];\n iat?: number;\n exp?: number;\n}\n","/**\n * Cross-platform SSE/Streaming support for SaferCity SDK\n * \n * Provides adapters for:\n * - Web browsers (native EventSource)\n * - React Native (fetch-based streaming via expo-fetch or polyfill)\n * - Node.js (fetch-based streaming)\n */\n\nimport type { ServerSentEvent, EventSourceOptions } from './types';\n\n/**\n * Interface for stream adapters\n */\nexport interface StreamAdapter {\n /**\n * Create an async iterable for SSE events\n */\n createEventSource(url: string, options?: EventSourceOptions): AsyncIterable<ServerSentEvent>;\n \n /**\n * Check if native SSE is supported\n */\n supportsNativeSSE(): boolean;\n}\n\n/**\n * Parse SSE data from a text buffer\n */\nexport function parseSSE(buffer: string): { parsed: ServerSentEvent[]; remaining: string } {\n const events: ServerSentEvent[] = [];\n const lines = buffer.split('\\n');\n \n let currentEvent: Partial<ServerSentEvent> = {};\n let dataLines: string[] = [];\n let remaining = '';\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n \n // Check if this might be an incomplete line at the end\n if (i === lines.length - 1 && line !== '' && !buffer.endsWith('\\n')) {\n remaining = line;\n break;\n }\n \n if (line === '') {\n // Empty line = event boundary\n if (dataLines.length > 0 || currentEvent.event || currentEvent.id) {\n events.push({\n ...currentEvent,\n data: dataLines.join('\\n'),\n } as ServerSentEvent);\n }\n currentEvent = {};\n dataLines = [];\n continue;\n }\n \n const colonIndex = line.indexOf(':');\n \n // Comment line (starts with :)\n if (colonIndex === 0) {\n continue;\n }\n \n let field: string;\n let value: string;\n \n if (colonIndex === -1) {\n field = line;\n value = '';\n } else {\n field = line.slice(0, colonIndex);\n // Skip the optional space after colon\n value = line.slice(colonIndex + 1).replace(/^ /, '');\n }\n \n switch (field) {\n case 'event':\n currentEvent.event = value;\n break;\n case 'data':\n dataLines.push(value);\n break;\n case 'id':\n currentEvent.id = value;\n break;\n case 'retry':\n const retry = parseInt(value, 10);\n if (!isNaN(retry)) {\n currentEvent.retry = retry;\n }\n break;\n }\n }\n \n return { parsed: events, remaining };\n}\n\n/**\n * Web/Browser stream adapter using native EventSource\n */\nexport class WebStreamAdapter implements StreamAdapter {\n supportsNativeSSE(): boolean {\n return typeof EventSource !== 'undefined';\n }\n \n createEventSource(url: string, options?: EventSourceOptions): AsyncIterable<ServerSentEvent> {\n const adapter = this;\n \n return {\n [Symbol.asyncIterator]() {\n let eventSource: EventSource | null = null;\n let resolveNext: ((value: IteratorResult<ServerSentEvent>) => void) | null = null;\n let rejectNext: ((error: Error) => void) | null = null;\n const eventQueue: ServerSentEvent[] = [];\n let done = false;\n let error: Error | null = null;\n \n const cleanup = () => {\n if (eventSource) {\n eventSource.close();\n eventSource = null;\n }\n done = true;\n };\n \n // Handle abort signal\n options?.signal?.addEventListener('abort', () => {\n cleanup();\n if (rejectNext) {\n rejectNext(new Error('Aborted'));\n }\n });\n \n // Initialize EventSource\n eventSource = new EventSource(url);\n \n eventSource.onopen = () => {\n options?.onOpen?.();\n };\n \n eventSource.onmessage = (event) => {\n const sseEvent: ServerSentEvent = {\n id: event.lastEventId || undefined,\n data: event.data,\n };\n \n if (resolveNext) {\n resolveNext({ value: sseEvent, done: false });\n resolveNext = null;\n rejectNext = null;\n } else {\n eventQueue.push(sseEvent);\n }\n };\n \n eventSource.onerror = (e) => {\n const err = new Error('EventSource error');\n error = err;\n options?.onError?.(err);\n cleanup();\n \n if (rejectNext) {\n rejectNext(err);\n resolveNext = null;\n rejectNext = null;\n }\n };\n \n return {\n async next(): Promise<IteratorResult<ServerSentEvent>> {\n if (error) {\n throw error;\n }\n \n if (eventQueue.length > 0) {\n return { value: eventQueue.shift()!, done: false };\n }\n \n if (done) {\n return { value: undefined as unknown as ServerSentEvent, done: true };\n }\n \n return new Promise((resolve, reject) => {\n resolveNext = resolve;\n rejectNext = reject;\n });\n },\n \n async return(): Promise<IteratorResult<ServerSentEvent>> {\n cleanup();\n return { value: undefined as unknown as ServerSentEvent, done: true };\n },\n \n async throw(e: Error): Promise<IteratorResult<ServerSentEvent>> {\n cleanup();\n throw e;\n },\n };\n },\n };\n }\n}\n\n/**\n * Fetch-based stream adapter for React Native and Node.js\n * Uses ReadableStream to parse SSE from fetch response\n */\nexport class FetchStreamAdapter implements StreamAdapter {\n constructor(private fetchFn: typeof fetch = fetch) {}\n \n supportsNativeSSE(): boolean {\n return false;\n }\n \n createEventSource(url: string, options?: EventSourceOptions): AsyncIterable<ServerSentEvent> {\n const fetchFn = this.fetchFn;\n \n return {\n [Symbol.asyncIterator]() {\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n let buffer = '';\n let done = false;\n const eventQueue: ServerSentEvent[] = [];\n \n const cleanup = () => {\n if (reader) {\n reader.cancel().catch(() => {});\n reader = null;\n }\n done = true;\n };\n \n // Handle abort signal\n options?.signal?.addEventListener('abort', cleanup);\n \n // Start fetch\n const fetchPromise = fetchFn(url, {\n method: 'GET',\n headers: {\n 'Accept': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n ...options?.headers,\n },\n signal: options?.signal,\n }).then((response) => {\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n \n if (!response.body) {\n throw new Error('Response body is not available');\n }\n \n options?.onOpen?.();\n reader = response.body.getReader();\n return reader;\n }).catch((err) => {\n options?.onError?.(err);\n throw err;\n });\n \n const decoder = new TextDecoder();\n \n return {\n async next(): Promise<IteratorResult<ServerSentEvent>> {\n // Return queued events first\n if (eventQueue.length > 0) {\n return { value: eventQueue.shift()!, done: false };\n }\n \n if (done) {\n return { value: undefined as unknown as ServerSentEvent, done: true };\n }\n \n // Ensure reader is initialized\n if (!reader) {\n reader = await fetchPromise;\n }\n \n // Read until we have at least one event\n while (eventQueue.length === 0 && !done) {\n const { value, done: readerDone } = await reader.read();\n \n if (readerDone) {\n done = true;\n // Parse any remaining buffer\n if (buffer.trim()) {\n const { parsed } = parseSSE(buffer + '\\n\\n');\n eventQueue.push(...parsed);\n }\n break;\n }\n \n buffer += decoder.decode(value, { stream: true });\n const { parsed, remaining } = parseSSE(buffer);\n buffer = remaining;\n eventQueue.push(...parsed);\n }\n \n if (eventQueue.length > 0) {\n return { value: eventQueue.shift()!, done: false };\n }\n \n return { value: undefined as unknown as ServerSentEvent, done: true };\n },\n \n async return(): Promise<IteratorResult<ServerSentEvent>> {\n cleanup();\n return { value: undefined as unknown as ServerSentEvent, done: true };\n },\n \n async throw(e: Error): Promise<IteratorResult<ServerSentEvent>> {\n cleanup();\n throw e;\n },\n };\n },\n };\n }\n}\n\n/**\n * Auto-detect and create the best stream adapter for the current environment\n */\nexport function createStreamAdapter(fetchFn?: typeof fetch): StreamAdapter {\n // Check for native EventSource (browser)\n if (typeof EventSource !== 'undefined') {\n return new WebStreamAdapter();\n }\n \n // Fall back to fetch-based adapter\n return new FetchStreamAdapter(fetchFn ?? fetch);\n}\n","/**\n * Base HTTP client for SaferCity SDK\n */\n\nimport type { SaferCityConfig, RequestOptions, ApiResponse } from './types';\nimport { SaferCityApiError } from './types';\nimport { createAuthHeader } from './auth';\n\nexport interface BaseClientOptions extends SaferCityConfig {}\n\n/**\n * Create base headers for requests\n */\nfunction createHeaders(\n config: SaferCityConfig,\n options?: RequestOptions\n): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n ...config.headers,\n ...options?.headers,\n };\n\n if (config.token) {\n headers['Authorization'] = createAuthHeader(config.token);\n }\n\n if (config.tenantId) {\n headers['X-Tenant-ID'] = config.tenantId;\n }\n\n return headers;\n}\n\n/**\n * Create a timeout signal\n */\nfunction createTimeoutSignal(timeout: number, existingSignal?: AbortSignal): AbortSignal {\n const controller = new AbortController();\n \n const timeoutId = setTimeout(() => {\n controller.abort(new Error(`Request timeout after ${timeout}ms`));\n }, timeout);\n \n // If there's an existing signal, abort when it aborts\n if (existingSignal) {\n existingSignal.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n controller.abort(existingSignal.reason);\n });\n }\n \n return controller.signal;\n}\n\n/**\n * Base HTTP client with common functionality\n */\nexport class BaseClient {\n protected config: Required<Pick<SaferCityConfig, 'baseUrl' | 'timeout'>> & SaferCityConfig;\n protected fetchFn: typeof fetch;\n\n constructor(options: BaseClientOptions) {\n this.config = {\n timeout: 30000,\n ...options,\n baseUrl: options.baseUrl.replace(/\\/$/, ''), // Remove trailing slash\n };\n this.fetchFn = options.fetch ?? fetch;\n }\n\n /**\n * Update authentication token\n */\n setToken(token: string | undefined): void {\n this.config.token = token;\n }\n\n /**\n * Update tenant ID\n */\n setTenantId(tenantId: string | undefined): void {\n this.config.tenantId = tenantId;\n }\n\n /**\n * Get current configuration (read-only)\n */\n getConfig(): Readonly<SaferCityConfig> {\n return { ...this.config };\n }\n\n /**\n * Build full URL from path\n */\n protected buildUrl(path: string, query?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(path.startsWith('/') ? path : `/${path}`, this.config.baseUrl);\n \n if (query) {\n Object.entries(query).forEach(([key, value]) => {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n });\n }\n \n return url.toString();\n }\n\n /**\n * Make HTTP request\n */\n protected async request<T>(\n method: string,\n path: string,\n options?: RequestOptions & {\n body?: unknown;\n query?: Record<string, string | number | boolean | undefined>;\n }\n ): Promise<ApiResponse<T>> {\n const url = this.buildUrl(path, options?.query);\n const headers = createHeaders(this.config, options);\n const timeout = options?.timeout ?? this.config.timeout;\n const signal = createTimeoutSignal(timeout, options?.signal);\n\n const response = await this.fetchFn(url, {\n method,\n headers,\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal,\n });\n\n let data: unknown;\n const contentType = response.headers.get('content-type');\n \n if (contentType?.includes('application/json')) {\n data = await response.json();\n } else {\n data = await response.text();\n }\n\n if (!response.ok) {\n throw SaferCityApiError.fromResponse(response, data);\n }\n\n return {\n data: data as T,\n status: response.status,\n headers: response.headers,\n };\n }\n\n /**\n * GET request\n */\n async get<T>(\n path: string,\n options?: RequestOptions & { query?: Record<string, string | number | boolean | undefined> }\n ): Promise<ApiResponse<T>> {\n return this.request<T>('GET', path, options);\n }\n\n /**\n * POST request\n */\n async post<T>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>('POST', path, { ...options, body });\n }\n\n /**\n * PUT request\n */\n async put<T>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>('PUT', path, { ...options, body });\n }\n\n /**\n * PATCH request\n */\n async patch<T>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>('PATCH', path, { ...options, body });\n }\n\n /**\n * DELETE request\n */\n async delete<T>(\n path: string,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>('DELETE', path, options);\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@safercity/sdk-core",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Core utilities for SaferCity SDK - fetch abstraction, streaming, and authentication",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "SaferCity"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/safercity/safercity-v2.git",
|
|
12
|
+
"directory": "packages/sdk/core"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["safercity", "sdk", "core", "streaming", "sse"],
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"import": "./dist/index.js",
|
|
24
|
+
"require": "./dist/index.cjs"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"README.md",
|
|
31
|
+
"CHANGELOG.md"
|
|
32
|
+
],
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=18.0.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"dev": "tsup --watch",
|
|
39
|
+
"check-types": "tsc --noEmit",
|
|
40
|
+
"clean": "rm -rf dist"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"tsup": "^8.0.0",
|
|
44
|
+
"typescript": "^5.8.0"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public",
|
|
48
|
+
"registry": "https://registry.npmjs.org/"
|
|
49
|
+
}
|
|
50
|
+
}
|