@oesp/sync-http 2.0.0 → 5.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/dist/SyncClient.d.ts +26 -0
- package/dist/SyncClient.js +86 -0
- package/dist/env.d.ts +7 -0
- package/dist/env.js +20 -0
- package/dist/index.d.ts +2 -35
- package/dist/index.js +2 -104
- package/package.json +11 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SyncConfig } from "./env";
|
|
2
|
+
export interface SyncSummary {
|
|
3
|
+
success: boolean;
|
|
4
|
+
uploadedCount: number;
|
|
5
|
+
totalBytes: number;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface SyncOpts extends Partial<SyncConfig> {
|
|
10
|
+
sha256: (data: Uint8Array) => Uint8Array | Promise<Uint8Array>;
|
|
11
|
+
}
|
|
12
|
+
export declare class OESPSyncClient {
|
|
13
|
+
private config;
|
|
14
|
+
private sha256;
|
|
15
|
+
constructor(opts: SyncOpts);
|
|
16
|
+
setBaseUrl(url: string): void;
|
|
17
|
+
/**
|
|
18
|
+
* Synchronise une liste de tokens vers le serveur
|
|
19
|
+
*/
|
|
20
|
+
syncTokens(tokens: string[], deviceDid: string, opts?: {
|
|
21
|
+
devicePubB64?: string;
|
|
22
|
+
clientMeta?: any;
|
|
23
|
+
allowExpired?: boolean;
|
|
24
|
+
}): Promise<SyncSummary>;
|
|
25
|
+
private chunkBytes;
|
|
26
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { base64Encode } from "@oesp/core";
|
|
2
|
+
import { getSyncConfig } from "./env";
|
|
3
|
+
export class OESPSyncClient {
|
|
4
|
+
constructor(opts) {
|
|
5
|
+
this.config = getSyncConfig(opts);
|
|
6
|
+
this.sha256 = opts.sha256;
|
|
7
|
+
}
|
|
8
|
+
setBaseUrl(url) {
|
|
9
|
+
this.config.baseUrl = url;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Synchronise une liste de tokens vers le serveur
|
|
13
|
+
*/
|
|
14
|
+
async syncTokens(tokens, deviceDid, opts = {}) {
|
|
15
|
+
try {
|
|
16
|
+
// 1. Démarrer la session
|
|
17
|
+
const startRes = await fetch(`${this.config.baseUrl}/sync/start`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: { "Content-Type": "application/json" },
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
did: deviceDid,
|
|
22
|
+
pub: opts.devicePubB64,
|
|
23
|
+
meta: opts.clientMeta
|
|
24
|
+
})
|
|
25
|
+
});
|
|
26
|
+
if (!startRes.ok)
|
|
27
|
+
throw new Error(`Failed to start sync: ${startRes.statusText}`);
|
|
28
|
+
const { session_id } = await startRes.json();
|
|
29
|
+
// 2. Chunker et Upload
|
|
30
|
+
let uploadedCount = 0;
|
|
31
|
+
let totalBytes = 0;
|
|
32
|
+
const jsonlData = tokens.map(t => JSON.stringify({ token: t })).join("\n");
|
|
33
|
+
const dataBytes = new TextEncoder().encode(jsonlData);
|
|
34
|
+
const chunks = this.chunkBytes(dataBytes, this.config.maxChunkBytes);
|
|
35
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
36
|
+
const chunkRes = await fetch(`${this.config.baseUrl}/sync/upload`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: {
|
|
39
|
+
"X-Session-ID": session_id,
|
|
40
|
+
"X-Chunk-Index": i.toString(),
|
|
41
|
+
"Content-Type": "application/octet-stream"
|
|
42
|
+
},
|
|
43
|
+
body: chunks[i]
|
|
44
|
+
});
|
|
45
|
+
if (!chunkRes.ok)
|
|
46
|
+
throw new Error(`Failed to upload chunk ${i}: ${chunkRes.statusText}`);
|
|
47
|
+
totalBytes += chunks[i].length;
|
|
48
|
+
}
|
|
49
|
+
// 3. Commit
|
|
50
|
+
const finalHashBytes = await this.sha256(dataBytes);
|
|
51
|
+
const finalHash = base64Encode(finalHashBytes);
|
|
52
|
+
const commitRes = await fetch(`${this.config.baseUrl}/sync/commit`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
session_id,
|
|
57
|
+
final_hash: finalHash,
|
|
58
|
+
allow_expired: opts.allowExpired ?? true
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
if (!commitRes.ok)
|
|
62
|
+
throw new Error(`Failed to commit: ${commitRes.statusText}`);
|
|
63
|
+
return {
|
|
64
|
+
success: true,
|
|
65
|
+
uploadedCount: tokens.length,
|
|
66
|
+
totalBytes,
|
|
67
|
+
sessionId: session_id
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
uploadedCount: 0,
|
|
74
|
+
totalBytes: 0,
|
|
75
|
+
error: e.message
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
chunkBytes(data, maxSize) {
|
|
80
|
+
const chunks = [];
|
|
81
|
+
for (let i = 0; i < data.length; i += maxSize) {
|
|
82
|
+
chunks.push(data.slice(i, i + maxSize));
|
|
83
|
+
}
|
|
84
|
+
return chunks;
|
|
85
|
+
}
|
|
86
|
+
}
|
package/dist/env.d.ts
ADDED
package/dist/env.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function getSyncConfig(overrides = {}) {
|
|
2
|
+
const defaultBaseUrl = "http://oesp-sync-server:8000";
|
|
3
|
+
let envBaseUrl;
|
|
4
|
+
// Node.js
|
|
5
|
+
if (typeof process !== "undefined" && process.env?.OESP_SYNC_BASE_URL) {
|
|
6
|
+
envBaseUrl = process.env.OESP_SYNC_BASE_URL;
|
|
7
|
+
}
|
|
8
|
+
// Vite / Web
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
if (typeof import.meta !== "undefined" && import.meta.env?.VITE_OESP_SYNC_BASE_URL) {
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
envBaseUrl = import.meta.env.VITE_OESP_SYNC_BASE_URL;
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
baseUrl: overrides.baseUrl || envBaseUrl || defaultBaseUrl,
|
|
16
|
+
apiKey: overrides.apiKey,
|
|
17
|
+
timeoutMs: overrides.timeoutMs || 30000,
|
|
18
|
+
maxChunkBytes: overrides.maxChunkBytes || 500000, // 500KB
|
|
19
|
+
};
|
|
20
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,35 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
apiKey?: string;
|
|
4
|
-
timeoutMs: number;
|
|
5
|
-
maxChunkBytes: number;
|
|
6
|
-
}
|
|
7
|
-
declare function getSyncConfig(overrides?: Partial<SyncConfig>): SyncConfig;
|
|
8
|
-
|
|
9
|
-
interface SyncSummary {
|
|
10
|
-
success: boolean;
|
|
11
|
-
uploadedCount: number;
|
|
12
|
-
totalBytes: number;
|
|
13
|
-
sessionId?: string;
|
|
14
|
-
error?: string;
|
|
15
|
-
}
|
|
16
|
-
interface SyncOpts extends Partial<SyncConfig> {
|
|
17
|
-
sha256: (data: Uint8Array) => Uint8Array | Promise<Uint8Array>;
|
|
18
|
-
}
|
|
19
|
-
declare class OESPSyncClient {
|
|
20
|
-
private config;
|
|
21
|
-
private sha256;
|
|
22
|
-
constructor(opts: SyncOpts);
|
|
23
|
-
setBaseUrl(url: string): void;
|
|
24
|
-
/**
|
|
25
|
-
* Synchronise une liste de tokens vers le serveur
|
|
26
|
-
*/
|
|
27
|
-
syncTokens(tokens: string[], deviceDid: string, opts?: {
|
|
28
|
-
devicePubB64?: string;
|
|
29
|
-
clientMeta?: any;
|
|
30
|
-
allowExpired?: boolean;
|
|
31
|
-
}): Promise<SyncSummary>;
|
|
32
|
-
private chunkBytes;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export { OESPSyncClient, type SyncConfig, type SyncOpts, type SyncSummary, getSyncConfig };
|
|
1
|
+
export * from "./SyncClient";
|
|
2
|
+
export * from "./env";
|
package/dist/index.js
CHANGED
|
@@ -1,104 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// src/env.ts
|
|
5
|
-
function getSyncConfig(overrides = {}) {
|
|
6
|
-
const defaultBaseUrl = "http://oesp-sync-server:8000";
|
|
7
|
-
let envBaseUrl;
|
|
8
|
-
if (typeof process !== "undefined" && process.env?.OESP_SYNC_BASE_URL) {
|
|
9
|
-
envBaseUrl = process.env.OESP_SYNC_BASE_URL;
|
|
10
|
-
}
|
|
11
|
-
if (typeof import.meta !== "undefined" && import.meta.env?.VITE_OESP_SYNC_BASE_URL) {
|
|
12
|
-
envBaseUrl = import.meta.env.VITE_OESP_SYNC_BASE_URL;
|
|
13
|
-
}
|
|
14
|
-
return {
|
|
15
|
-
baseUrl: overrides.baseUrl || envBaseUrl || defaultBaseUrl,
|
|
16
|
-
apiKey: overrides.apiKey,
|
|
17
|
-
timeoutMs: overrides.timeoutMs || 3e4,
|
|
18
|
-
maxChunkBytes: overrides.maxChunkBytes || 5e5
|
|
19
|
-
// 500KB
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// src/SyncClient.ts
|
|
24
|
-
var OESPSyncClient = class {
|
|
25
|
-
constructor(opts) {
|
|
26
|
-
this.config = getSyncConfig(opts);
|
|
27
|
-
this.sha256 = opts.sha256;
|
|
28
|
-
}
|
|
29
|
-
setBaseUrl(url) {
|
|
30
|
-
this.config.baseUrl = url;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Synchronise une liste de tokens vers le serveur
|
|
34
|
-
*/
|
|
35
|
-
async syncTokens(tokens, deviceDid, opts = {}) {
|
|
36
|
-
try {
|
|
37
|
-
const startRes = await fetch(`${this.config.baseUrl}/sync/start`, {
|
|
38
|
-
method: "POST",
|
|
39
|
-
headers: { "Content-Type": "application/json" },
|
|
40
|
-
body: JSON.stringify({
|
|
41
|
-
did: deviceDid,
|
|
42
|
-
pub: opts.devicePubB64,
|
|
43
|
-
meta: opts.clientMeta
|
|
44
|
-
})
|
|
45
|
-
});
|
|
46
|
-
if (!startRes.ok) throw new Error(`Failed to start sync: ${startRes.statusText}`);
|
|
47
|
-
const { session_id } = await startRes.json();
|
|
48
|
-
let uploadedCount = 0;
|
|
49
|
-
let totalBytes = 0;
|
|
50
|
-
const jsonlData = tokens.map((t) => JSON.stringify({ token: t })).join("\n");
|
|
51
|
-
const dataBytes = new TextEncoder().encode(jsonlData);
|
|
52
|
-
const chunks = this.chunkBytes(dataBytes, this.config.maxChunkBytes);
|
|
53
|
-
for (let i = 0; i < chunks.length; i++) {
|
|
54
|
-
const chunkRes = await fetch(`${this.config.baseUrl}/sync/upload`, {
|
|
55
|
-
method: "POST",
|
|
56
|
-
headers: {
|
|
57
|
-
"X-Session-ID": session_id,
|
|
58
|
-
"X-Chunk-Index": i.toString(),
|
|
59
|
-
"Content-Type": "application/octet-stream"
|
|
60
|
-
},
|
|
61
|
-
body: chunks[i]
|
|
62
|
-
});
|
|
63
|
-
if (!chunkRes.ok) throw new Error(`Failed to upload chunk ${i}: ${chunkRes.statusText}`);
|
|
64
|
-
totalBytes += chunks[i].length;
|
|
65
|
-
}
|
|
66
|
-
const finalHashBytes = await this.sha256(dataBytes);
|
|
67
|
-
const finalHash = base64Encode(finalHashBytes);
|
|
68
|
-
const commitRes = await fetch(`${this.config.baseUrl}/sync/commit`, {
|
|
69
|
-
method: "POST",
|
|
70
|
-
headers: { "Content-Type": "application/json" },
|
|
71
|
-
body: JSON.stringify({
|
|
72
|
-
session_id,
|
|
73
|
-
final_hash: finalHash,
|
|
74
|
-
allow_expired: opts.allowExpired ?? true
|
|
75
|
-
})
|
|
76
|
-
});
|
|
77
|
-
if (!commitRes.ok) throw new Error(`Failed to commit: ${commitRes.statusText}`);
|
|
78
|
-
return {
|
|
79
|
-
success: true,
|
|
80
|
-
uploadedCount: tokens.length,
|
|
81
|
-
totalBytes,
|
|
82
|
-
sessionId: session_id
|
|
83
|
-
};
|
|
84
|
-
} catch (e) {
|
|
85
|
-
return {
|
|
86
|
-
success: false,
|
|
87
|
-
uploadedCount: 0,
|
|
88
|
-
totalBytes: 0,
|
|
89
|
-
error: e.message
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
chunkBytes(data, maxSize) {
|
|
94
|
-
const chunks = [];
|
|
95
|
-
for (let i = 0; i < data.length; i += maxSize) {
|
|
96
|
-
chunks.push(data.slice(i, i + maxSize));
|
|
97
|
-
}
|
|
98
|
-
return chunks;
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
|
-
export {
|
|
102
|
-
OESPSyncClient,
|
|
103
|
-
getSyncConfig
|
|
104
|
-
};
|
|
1
|
+
export * from "./SyncClient";
|
|
2
|
+
export * from "./env";
|
package/package.json
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oesp/sync-http",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OESP HTTP Sync Client",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"sideEffects": false,
|
|
10
18
|
"files": [
|
|
11
19
|
"dist"
|
|
12
20
|
],
|
|
@@ -14,7 +22,7 @@
|
|
|
14
22
|
"access": "public"
|
|
15
23
|
},
|
|
16
24
|
"dependencies": {
|
|
17
|
-
"@oesp/core": "
|
|
25
|
+
"@oesp/core": "5.0.0"
|
|
18
26
|
},
|
|
19
27
|
"devDependencies": {
|
|
20
28
|
"tsup": "^8.0.1",
|
|
@@ -22,7 +30,7 @@
|
|
|
22
30
|
"vitest": "^1.6.0"
|
|
23
31
|
},
|
|
24
32
|
"scripts": {
|
|
25
|
-
"build": "
|
|
33
|
+
"build": "tsc -p tsconfig.build.json",
|
|
26
34
|
"test": "vitest run",
|
|
27
35
|
"lint": "tsc -p tsconfig.json --noEmit"
|
|
28
36
|
}
|