@oesp/sync-http 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/README.md +69 -0
- package/dist/index.cjs +133 -0
- package/dist/index.d.cts +35 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +104 -0
- package/package.json +29 -0
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @oesp/sync-http
|
|
2
|
+
|
|
3
|
+
Client de synchronisation HTTP pour OESP avec support des uploads fragmentés et vérification d'intégrité.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @oesp/sync-http
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Fonctionnalités
|
|
12
|
+
|
|
13
|
+
- **Upload par Session** : Création de session pour gérer les gros volumes de données.
|
|
14
|
+
- **Envoi Fragmenté (Chunked)** : Découpage automatique des données pour éviter les timeouts et respecter les limites du serveur.
|
|
15
|
+
- **Vérification d'Intégrité** : Calcul du hash SHA-256 pour chaque fragment envoyé.
|
|
16
|
+
- **Opérations Asynchrones** : Gestion non-bloquante des requêtes réseau via `fetch`.
|
|
17
|
+
- **Crypto Agnostique** : Injection du provider SHA-256.
|
|
18
|
+
|
|
19
|
+
## Utilisation
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { OESPSyncClient } from '@oesp/sync-http';
|
|
23
|
+
import { sha256 } from 'js-sha256'; // Exemple de lib externe
|
|
24
|
+
|
|
25
|
+
// 1. Initialiser le client avec l'URL du serveur et le provider SHA-256
|
|
26
|
+
const client = new OESPSyncClient({
|
|
27
|
+
baseUrl: 'https://mon-serveur-oesp.com',
|
|
28
|
+
sha256: async (data) => new Uint8Array(sha256.arrayBuffer(data))
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 2. Préparer les tokens à synchroniser (format string base64url ou raw)
|
|
32
|
+
const tokensToSync = [
|
|
33
|
+
'OESP1.token1...',
|
|
34
|
+
'OESP1.token2...'
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const deviceDid = 'oesp:did:my_device';
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// 3. Lancer la synchronisation asynchrone
|
|
41
|
+
// La méthode gère automatiquement la création de session, le découpage et l'upload
|
|
42
|
+
const result = await client.syncTokens(
|
|
43
|
+
tokensToSync,
|
|
44
|
+
deviceDid
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
if (result.success) {
|
|
48
|
+
console.log(`Synchronisation réussie !`);
|
|
49
|
+
console.log(`Tokens envoyés : ${result.uploadedCount}`);
|
|
50
|
+
console.log(`ID de session : ${result.sessionId}`);
|
|
51
|
+
} else {
|
|
52
|
+
console.error(`Erreur de synchro : ${result.error}`);
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error("Erreur réseau ou client:", err);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration Avancée
|
|
60
|
+
|
|
61
|
+
Vous pouvez ajuster la taille des chunks lors de l'initialisation :
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const client = new OESPSyncClient({
|
|
65
|
+
baseUrl: 'https://api.oesp.com',
|
|
66
|
+
sha256: mySha256Provider,
|
|
67
|
+
chunkSize: 1024 * 1024 // 1MB par chunk (défaut: 512KB)
|
|
68
|
+
});
|
|
69
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
OESPSyncClient: () => OESPSyncClient,
|
|
24
|
+
getSyncConfig: () => getSyncConfig
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/SyncClient.ts
|
|
29
|
+
var import_core = require("@oesp/core");
|
|
30
|
+
|
|
31
|
+
// src/env.ts
|
|
32
|
+
var import_meta = {};
|
|
33
|
+
function getSyncConfig(overrides = {}) {
|
|
34
|
+
const defaultBaseUrl = "http://oesp-sync-server:8000";
|
|
35
|
+
let envBaseUrl;
|
|
36
|
+
if (typeof process !== "undefined" && process.env?.OESP_SYNC_BASE_URL) {
|
|
37
|
+
envBaseUrl = process.env.OESP_SYNC_BASE_URL;
|
|
38
|
+
}
|
|
39
|
+
if (typeof import_meta !== "undefined" && import_meta.env?.VITE_OESP_SYNC_BASE_URL) {
|
|
40
|
+
envBaseUrl = import_meta.env.VITE_OESP_SYNC_BASE_URL;
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
baseUrl: overrides.baseUrl || envBaseUrl || defaultBaseUrl,
|
|
44
|
+
apiKey: overrides.apiKey,
|
|
45
|
+
timeoutMs: overrides.timeoutMs || 3e4,
|
|
46
|
+
maxChunkBytes: overrides.maxChunkBytes || 5e5
|
|
47
|
+
// 500KB
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/SyncClient.ts
|
|
52
|
+
var OESPSyncClient = class {
|
|
53
|
+
constructor(opts) {
|
|
54
|
+
this.config = getSyncConfig(opts);
|
|
55
|
+
this.sha256 = opts.sha256;
|
|
56
|
+
}
|
|
57
|
+
setBaseUrl(url) {
|
|
58
|
+
this.config.baseUrl = url;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Synchronise une liste de tokens vers le serveur
|
|
62
|
+
*/
|
|
63
|
+
async syncTokens(tokens, deviceDid, opts = {}) {
|
|
64
|
+
try {
|
|
65
|
+
const startRes = await fetch(`${this.config.baseUrl}/sync/start`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "Content-Type": "application/json" },
|
|
68
|
+
body: JSON.stringify({
|
|
69
|
+
did: deviceDid,
|
|
70
|
+
pub: opts.devicePubB64,
|
|
71
|
+
meta: opts.clientMeta
|
|
72
|
+
})
|
|
73
|
+
});
|
|
74
|
+
if (!startRes.ok) throw new Error(`Failed to start sync: ${startRes.statusText}`);
|
|
75
|
+
const { session_id } = await startRes.json();
|
|
76
|
+
let uploadedCount = 0;
|
|
77
|
+
let totalBytes = 0;
|
|
78
|
+
const jsonlData = tokens.map((t) => JSON.stringify({ token: t })).join("\n");
|
|
79
|
+
const dataBytes = new TextEncoder().encode(jsonlData);
|
|
80
|
+
const chunks = this.chunkBytes(dataBytes, this.config.maxChunkBytes);
|
|
81
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
82
|
+
const chunkRes = await fetch(`${this.config.baseUrl}/sync/upload`, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: {
|
|
85
|
+
"X-Session-ID": session_id,
|
|
86
|
+
"X-Chunk-Index": i.toString(),
|
|
87
|
+
"Content-Type": "application/octet-stream"
|
|
88
|
+
},
|
|
89
|
+
body: chunks[i]
|
|
90
|
+
});
|
|
91
|
+
if (!chunkRes.ok) throw new Error(`Failed to upload chunk ${i}: ${chunkRes.statusText}`);
|
|
92
|
+
totalBytes += chunks[i].length;
|
|
93
|
+
}
|
|
94
|
+
const finalHashBytes = await this.sha256(dataBytes);
|
|
95
|
+
const finalHash = (0, import_core.base64Encode)(finalHashBytes);
|
|
96
|
+
const commitRes = await fetch(`${this.config.baseUrl}/sync/commit`, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
session_id,
|
|
101
|
+
final_hash: finalHash,
|
|
102
|
+
allow_expired: opts.allowExpired ?? true
|
|
103
|
+
})
|
|
104
|
+
});
|
|
105
|
+
if (!commitRes.ok) throw new Error(`Failed to commit: ${commitRes.statusText}`);
|
|
106
|
+
return {
|
|
107
|
+
success: true,
|
|
108
|
+
uploadedCount: tokens.length,
|
|
109
|
+
totalBytes,
|
|
110
|
+
sessionId: session_id
|
|
111
|
+
};
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
uploadedCount: 0,
|
|
116
|
+
totalBytes: 0,
|
|
117
|
+
error: e.message
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
chunkBytes(data, maxSize) {
|
|
122
|
+
const chunks = [];
|
|
123
|
+
for (let i = 0; i < data.length; i += maxSize) {
|
|
124
|
+
chunks.push(data.slice(i, i + maxSize));
|
|
125
|
+
}
|
|
126
|
+
return chunks;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
130
|
+
0 && (module.exports = {
|
|
131
|
+
OESPSyncClient,
|
|
132
|
+
getSyncConfig
|
|
133
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
interface SyncConfig {
|
|
2
|
+
baseUrl: string;
|
|
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 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
interface SyncConfig {
|
|
2
|
+
baseUrl: string;
|
|
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 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// src/SyncClient.ts
|
|
2
|
+
import { base64Encode } from "@oesp/core";
|
|
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
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oesp/sync-http",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "OESP HTTP Sync Client",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup src/index.ts --dts --format esm,cjs",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"lint": "tsc -p tsconfig.json --noEmit"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@oesp/core": "workspace:*"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"tsup": "^8.0.1",
|
|
26
|
+
"typescript": "^5.6.3",
|
|
27
|
+
"vitest": "^1.6.0"
|
|
28
|
+
}
|
|
29
|
+
}
|