rxome-generator 1.0.3 → 1.0.4-beta.2
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/JSON_INPUT_FORMAT.md +380 -0
- package/README.html +834 -0
- package/README.md +50 -19
- package/demos/demo_overfull.json +144 -0
- package/index.js +3 -3
- package/lib/package-info.js +4 -0
- package/lib/phenopackets.js +1951 -0
- package/lib/rxome-api-demo.js +26 -0
- package/lib/rxome-api.js +257 -0
- package/lib/rxome-api.test.js +9 -9
- package/lib/{rxome-generator.cjs → rxome-generator.js} +94 -80
- package/lib/rxome-generator.test.js +26 -17
- package/package.json +11 -2
- package/rxcode.js +12 -5
- package/rxcode.test.js +38 -39
- package/lib/rxome-api-demo.cjs +0 -60
- package/lib/rxome-api.cjs +0 -192
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const CRYPT_PRIVATE_KEY = '-----BEGIN PGP PRIVATE KEY BLOCK-----\n\nxYYEY0gdfxYJKwYBBAHaRw8BAQdAYqNejQxT4gE+w2nwBvP+pe19P152F6LV\n8sqM/Qhut2D+CQMIlk6wdgn1LOXgt14o00FVGL49l+pQB8umf27PPnWrJ9IS\nIElQjaCsRYA3ZD/rnDUZiBGVS9++PaegYL339QT2bDp8l6VVtvcxG77svZ2n\na80Xcnh0ZXN0IDxpbmZvQHJ4b21lLm5ldD7CjAQQFgoAHQUCY0gdfwQLCQcI\nAxUICgQWAAIBAhkBAhsDAh4BACEJEGvtGF4xeK/wFiEEP1IE7WS9w5iQJRD8\na+0YXjF4r/CW7AD7BlIn1BHx8kOdyrt6E0L1EKIUi88Q3jQghmvlQomsIzIB\nAKW3e7gYkQJufFTlTWmD5dYmP4v3DfAGvkmFljOvHfwGx4sEY0gdfxIKKwYB\nBAGXVQEFAQEHQNBoBiWwb9t6WCMulp6/opgVJ88iKOY9MpAoZ5dyEbJwAwEI\nB/4JAwhZhPonkWKqteBKH35kf07JpJVMX8LWmZCqdFqXw8tmsU81LtCxVRl8\nexJ0vJor/6LmBUnzMrVSG3S0PCVLw0hAfCH4nN9HxT5gEc1mFFfSwngEGBYI\nAAkFAmNIHX8CGwwAIQkQa+0YXjF4r/AWIQQ/UgTtZL3DmJAlEPxr7RheMXiv\n8FlgAP9S+Oc82N6iSA4gj9hOt6dz7E4YE/3XGf+7uVQb3xVYSQD6A4X+cisS\nfqCVd5bPCMqAQQHjHgGBQawPK/PXyk9JxQQ=\n=idJc\n-----END PGP PRIVATE KEY BLOCK-----\n';
|
|
2
|
+
|
|
3
|
+
const CRYPT_PUBLIC_KEY = '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxjMEY0gdfxYJKwYBBAHaRw8BAQdAYqNejQxT4gE+w2nwBvP+pe19P152F6LV\n8sqM/Qhut2DNF3J4dGVzdCA8aW5mb0ByeG9tZS5uZXQ+wowEEBYKAB0FAmNI\nHX8ECwkHCAMVCAoEFgACAQIZAQIbAwIeAQAhCRBr7RheMXiv8BYhBD9SBO1k\nvcOYkCUQ/GvtGF4xeK/wluwA+wZSJ9QR8fJDncq7ehNC9RCiFIvPEN40IIZr\n5UKJrCMyAQClt3u4GJECbnxU5U1pg+XWJj+L9w3wBr5JhZYzrx38Bs44BGNI\nHX8SCisGAQQBl1UBBQEBB0DQaAYlsG/belgjLpaev6KYFSfPIijmPTKQKGeX\nchGycAMBCAfCeAQYFggACQUCY0gdfwIbDAAhCRBr7RheMXiv8BYhBD9SBO1k\nvcOYkCUQ/GvtGF4xeK/wWWAA/1L45zzY3qJIDiCP2E63p3PsThgT/dcZ/7u5\nVBvfFVhJAPoDhf5yKxJ+oJV3ls8IyoBBAeMeAYFBrA8r89fKT0nFBA==\n=odkw\n-----END PGP PUBLIC KEY BLOCK-----\n';
|
|
4
|
+
|
|
5
|
+
const R_ID = 'rxome';
|
|
6
|
+
const R_PUBLIC_KEY = '4OShAt7RQ/RJA0qAvoaOQ2jx7SFBYef70XPIz7r9924=';
|
|
7
|
+
const R_PRIVATE_KEY = 'Rf7VbeUBQmjvAagwsWx6riaZYc7h4OBD4CuxYyZ5bgA=';
|
|
8
|
+
// const R_PUBLIC_KEY = '60uReCXTn7KTEIExM4KveKstBGI3TaSrQss4biaesNs=';
|
|
9
|
+
// const R_PRIVATE_KEY = 'lBSkSxe/+UBWOeF5OJdQgf9qZhiI85hYE6yJCuWjCNk=';
|
|
10
|
+
// const R_PRIVATE_KEY = 'NamaTB+xwDFxtkQyBBkjRr5GEaXNtCw/G4qydnhQk5Y=';
|
|
11
|
+
// const R_PUBLIC_KEY = 'XvbhLWKbA1wfKsx3B7FKQuDQsZTZ/dMXWiD1MehBxZg=';
|
|
12
|
+
|
|
13
|
+
const J_ID = 'rxomej';
|
|
14
|
+
const J_PRIVATE_KEY = 'QhcoRruGBVP39XCh8BujCE+q42qCRy/tu2CQ4YmRBgg=';
|
|
15
|
+
const J_PUBLIC_KEY = 'XL/i8jrJC55AdOV3zYHIIa095De5eYbDqWDPDW2r8tk=';
|
|
16
|
+
|
|
17
|
+
const DEMO_CREDENTIALS = {
|
|
18
|
+
id: R_ID,
|
|
19
|
+
user: 'test@rxome.net',
|
|
20
|
+
key: R_PRIVATE_KEY
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { CRYPT_PRIVATE_KEY, CRYPT_PUBLIC_KEY, DEMO_CREDENTIALS };
|
|
24
|
+
export const DEMO_API_PRIVATE_KEY = R_PRIVATE_KEY;
|
|
25
|
+
export const DEMO_API_PUBLIC_KEY = R_PUBLIC_KEY;
|
|
26
|
+
export const DEMO_API_ID = R_ID;
|
package/lib/rxome-api.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Using native fetch API instead of axios for better webpack compatibility
|
|
2
|
+
import * as ED from 'noble-ed25519';
|
|
3
|
+
import Protobuf from 'protobufjs';
|
|
4
|
+
import { stringify } from 'querystring';
|
|
5
|
+
|
|
6
|
+
// Environment-aware fetch function
|
|
7
|
+
export async function getFetch() {
|
|
8
|
+
if (typeof window !== 'undefined') {
|
|
9
|
+
return window.fetch;
|
|
10
|
+
}
|
|
11
|
+
return (await import('node-fetch')).default;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const API = 'https://app.findme2care.de';
|
|
15
|
+
const TESTAPI = 'https://stage.findme2care.de';
|
|
16
|
+
const APIENTRY = 'api/v1';
|
|
17
|
+
const VSTR = 'API1.0'
|
|
18
|
+
const IS_DEMO = '_DEMO_PSEUDONYM_';
|
|
19
|
+
|
|
20
|
+
export { API, TESTAPI, APIENTRY, VSTR, IS_DEMO };
|
|
21
|
+
|
|
22
|
+
/************************************************************************************
|
|
23
|
+
* Helper functions
|
|
24
|
+
************************************************************************************/
|
|
25
|
+
|
|
26
|
+
// have issues:
|
|
27
|
+
const base64ToBufferVintage = data => atob(data.toString());
|
|
28
|
+
//const bufferToBase64 = data => Buffer.from(data).toString('base64');
|
|
29
|
+
|
|
30
|
+
const unpack = (arr) => Uint8Array.from( arr.map( c => c.charCodeAt(0) ));
|
|
31
|
+
|
|
32
|
+
let readSigKey = name => { console.log("Error: not supported") }
|
|
33
|
+
|
|
34
|
+
const base64ToBuffer = enc => {
|
|
35
|
+
const len = Protobuf.util.base64.length(enc);
|
|
36
|
+
let buf = new Protobuf.util.Array( len );
|
|
37
|
+
Protobuf.util.base64.decode(enc, buf, 0);
|
|
38
|
+
return buf;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const bufferToBase64 = buf => {
|
|
42
|
+
return Protobuf.util.base64.encode(buf, 0, buf.length);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { base64ToBuffer, bufferToBase64, unpack };
|
|
46
|
+
|
|
47
|
+
/************************************************************************************
|
|
48
|
+
* Functions for node.js only
|
|
49
|
+
************************************************************************************/
|
|
50
|
+
|
|
51
|
+
// Node.js specific functions that require fs module
|
|
52
|
+
readSigKey = async name => {
|
|
53
|
+
try {
|
|
54
|
+
const FS = await import('fs');
|
|
55
|
+
if (!FS.existsSync( name )) {
|
|
56
|
+
throw 'Key file not found!';
|
|
57
|
+
}
|
|
58
|
+
return FS.readFileSync( name ).slice(0,44);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
if (e instanceof Error && e.code !== "MODULE_NOT_FOUND") {
|
|
61
|
+
throw e;
|
|
62
|
+
}
|
|
63
|
+
throw new Error('fs module not available - cannot read key file');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const generateApiKeys = async () => {
|
|
68
|
+
const privateKey = ED.utils.randomPrivateKey();
|
|
69
|
+
const publicKey = await ED.getPublicKey(privateKey);
|
|
70
|
+
return {
|
|
71
|
+
privateKey: privateKey,
|
|
72
|
+
publicKey: publicKey
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const writeApiKeys = async (name = 'rxome', dir = '.') => {
|
|
77
|
+
try {
|
|
78
|
+
const FS = await import('fs');
|
|
79
|
+
const key = await generateApiKeys();
|
|
80
|
+
await Promise.all([
|
|
81
|
+
FS.writeFile( `${dir}/${name}.private.apikey`, bufferToBase64( key.privateKey ), { mode: 0o600 }, err => { if (err) throw err; } ),
|
|
82
|
+
FS.writeFile( `${dir}/${name}.public.apikey`, bufferToBase64( key.publicKey ), { mode: 0o600 }, err => { if (err) throw err; } )
|
|
83
|
+
]);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
if (e instanceof Error && e.code !== "MODULE_NOT_FOUND") {
|
|
86
|
+
throw e;
|
|
87
|
+
}
|
|
88
|
+
throw new Error('fs module not available - cannot write API keys');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { readSigKey };
|
|
93
|
+
|
|
94
|
+
/************************************************************************************/
|
|
95
|
+
|
|
96
|
+
export const signData = async( keyId, user, keyB64, created, debug = false ) => {
|
|
97
|
+
const message = `x-date: ${created}\nx-rxome-user: ${user}`
|
|
98
|
+
const messageUi8 = unpack( Array.from(message) );
|
|
99
|
+
|
|
100
|
+
const key = unpack( [...base64ToBufferVintage(keyB64)] );
|
|
101
|
+
debug && console.log('Base 64 key: ', keyB64);
|
|
102
|
+
debug && console.log('Binary key: ', key, " Key length: ", key.length);
|
|
103
|
+
|
|
104
|
+
const signature = await ED.sign( messageUi8, key);
|
|
105
|
+
const sigB64 = bufferToBase64( signature );
|
|
106
|
+
const auth=`Signature keyId="${keyId}",algorithm="ed25519",headers="x-date x-rxome-user",signature="${sigB64}",created="${created}"`
|
|
107
|
+
debug && console.log('Auth string: ', auth);
|
|
108
|
+
|
|
109
|
+
return auth;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const fetchData = async ( url, credentials, pseudonym = '', debug = false ) => {
|
|
113
|
+
debug && console.log( 'Fetching from', url )
|
|
114
|
+
|
|
115
|
+
const created = Date.now();
|
|
116
|
+
const keyId = credentials.keyId || "rxome";
|
|
117
|
+
const user = credentials.user || `${keyId}@rxome.net`;
|
|
118
|
+
const keyB64 = credentials.key || await readSigKey( process.cwd()+'/'+credentials.keyFile );
|
|
119
|
+
|
|
120
|
+
const auth = await signData( keyId, user, keyB64, created, debug );
|
|
121
|
+
|
|
122
|
+
// Build URL with query parameters
|
|
123
|
+
const urlParams = new URLSearchParams({
|
|
124
|
+
pslab: !!pseudonym.trim(),
|
|
125
|
+
demo: pseudonym === IS_DEMO
|
|
126
|
+
});
|
|
127
|
+
const fullUrl = `${url}?${urlParams}`;
|
|
128
|
+
|
|
129
|
+
// Create AbortController for timeout
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
132
|
+
|
|
133
|
+
const fetch = await getFetch();
|
|
134
|
+
return fetch(fullUrl, {
|
|
135
|
+
method: 'GET',
|
|
136
|
+
headers: {
|
|
137
|
+
Authorization: auth,
|
|
138
|
+
'x-date': created,
|
|
139
|
+
'x-rxome-user': user
|
|
140
|
+
},
|
|
141
|
+
signal: controller.signal
|
|
142
|
+
})
|
|
143
|
+
.then(async response => {
|
|
144
|
+
clearTimeout(timeoutId);
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
let errorData = null;
|
|
147
|
+
try {
|
|
148
|
+
errorData = await response.json();
|
|
149
|
+
} catch (e) {
|
|
150
|
+
// If response can't be parsed as JSON, use default error
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const error = new Error(`HTTP error! status: ${response.status}`);
|
|
154
|
+
error.response = {
|
|
155
|
+
status: response.status,
|
|
156
|
+
statusText: response.statusText,
|
|
157
|
+
headers: response.headers,
|
|
158
|
+
data: errorData
|
|
159
|
+
};
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
return response.json();
|
|
163
|
+
})
|
|
164
|
+
.then(data => {
|
|
165
|
+
debug && console.log( "Result Data= ", data );
|
|
166
|
+
return data;
|
|
167
|
+
})
|
|
168
|
+
.catch(error => {
|
|
169
|
+
clearTimeout(timeoutId);
|
|
170
|
+
if (error.name === 'AbortError') {
|
|
171
|
+
const timeoutError = new Error('Request timeout');
|
|
172
|
+
timeoutError.response = {
|
|
173
|
+
status: 408,
|
|
174
|
+
statusText: 'Request Timeout',
|
|
175
|
+
data: { message: 'Request timeout' }
|
|
176
|
+
};
|
|
177
|
+
throw timeoutError;
|
|
178
|
+
}
|
|
179
|
+
throw error;
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const pushData = async ( url, credentials, msg, debug = false ) => {
|
|
184
|
+
debug && console.log( 'Pushing to ', url )
|
|
185
|
+
console.log( 'Pushing to ', url )
|
|
186
|
+
|
|
187
|
+
const created = Date.now();
|
|
188
|
+
const keyId = credentials.keyId || "rxome";
|
|
189
|
+
const user = credentials.user || `${keyId}@rxome.net`;
|
|
190
|
+
const keyB64 = credentials.key || await readSigKey( process.cwd()+'/'+credentials.keyFile );
|
|
191
|
+
|
|
192
|
+
const auth = await signData( keyId, user, keyB64, created, debug );
|
|
193
|
+
|
|
194
|
+
// Create AbortController for timeout
|
|
195
|
+
const controller = new AbortController();
|
|
196
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
197
|
+
|
|
198
|
+
const fetch = await getFetch();
|
|
199
|
+
return fetch(url, {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers: {
|
|
202
|
+
'Content-Type': 'application/json',
|
|
203
|
+
Authorization: auth,
|
|
204
|
+
'x-date': created,
|
|
205
|
+
'x-rxome-user': user
|
|
206
|
+
},
|
|
207
|
+
body: JSON.stringify(msg),
|
|
208
|
+
signal: controller.signal
|
|
209
|
+
})
|
|
210
|
+
.then(response => {
|
|
211
|
+
clearTimeout(timeoutId);
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
// Create error object similar to axios
|
|
214
|
+
const error = new Error(`HTTP error! status: ${response.status}`);
|
|
215
|
+
error.response = {
|
|
216
|
+
status: response.status,
|
|
217
|
+
statusText: response.statusText,
|
|
218
|
+
headers: response.headers,
|
|
219
|
+
data: null
|
|
220
|
+
};
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
return response.json();
|
|
224
|
+
})
|
|
225
|
+
.then(data => {
|
|
226
|
+
debug && console.log( "Result Data= ", data );
|
|
227
|
+
return data;
|
|
228
|
+
})
|
|
229
|
+
.catch( (error) => {
|
|
230
|
+
clearTimeout(timeoutId);
|
|
231
|
+
let msg = null;
|
|
232
|
+
if (error.response) {
|
|
233
|
+
// Request made and server responded
|
|
234
|
+
if (error.response && error.response.data && error.response.data.message){
|
|
235
|
+
console.log( '[SendData Response Error] ', error.response.data.message );
|
|
236
|
+
msg = `Response Error: ${error.response.data.message}`;
|
|
237
|
+
console.log(error.response.data);
|
|
238
|
+
} else {
|
|
239
|
+
console.log( '[SendData Response Error] no message' );
|
|
240
|
+
msg = 'Unknown response error';
|
|
241
|
+
}
|
|
242
|
+
console.log(error.response.status);
|
|
243
|
+
console.log(error.response.headers);
|
|
244
|
+
} else if (error.name === 'AbortError') {
|
|
245
|
+
// Request was aborted (timeout)
|
|
246
|
+
console.log('[SendData Timeout Error]');
|
|
247
|
+
msg = 'Request timeout';
|
|
248
|
+
} else {
|
|
249
|
+
// Something happened in setting up the request that triggered an Error
|
|
250
|
+
console.log('[SendData Request Error] ', error.message);
|
|
251
|
+
msg = `Request Error: ${error.message}`;
|
|
252
|
+
}
|
|
253
|
+
const err = new Error( `Error sending data: ${msg}` );
|
|
254
|
+
console.log(err.name + ': ' + err.message);
|
|
255
|
+
throw err;
|
|
256
|
+
})
|
|
257
|
+
}
|
package/lib/rxome-api.test.js
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import * as RxAPI from './rxome-api.js';
|
|
2
|
+
import { readFileSync, existsSync, statSync } from 'fs';
|
|
3
|
+
import * as ED from 'noble-ed25519';
|
|
4
4
|
|
|
5
5
|
describe('API access', () => {
|
|
6
6
|
test.skip('generates valid API access keys', async () => {
|
|
7
7
|
await RxAPI.writeApiKeys( '__TESTSUITE_jesttest' );
|
|
8
|
-
expect(
|
|
9
|
-
expect(
|
|
10
|
-
expect(
|
|
11
|
-
expect(
|
|
8
|
+
expect( existsSync('__TESTSUITE_jesttest.private.apikey') ).toBe( true );
|
|
9
|
+
expect( existsSync('__TESTSUITE_jesttest.public.apikey') ).toBe( true );
|
|
10
|
+
expect( statSync('__TESTSUITE_jesttest.private.apikey').size - 44 ).toBeLessThan( 2 );
|
|
11
|
+
expect( statSync('__TESTSUITE_jesttest.public.apikey').size - 44 ).toBeLessThan( 2 );
|
|
12
12
|
|
|
13
13
|
const message='Answer to life the universe and everything';
|
|
14
14
|
const messageUi8 = RxAPI.unpack(Array.from(message));
|
|
15
|
-
const privKey = RxAPI.unpack([...RxAPI.base64ToBuffer(
|
|
16
|
-
const pubKey = RxAPI.unpack([...RxAPI.base64ToBuffer(
|
|
15
|
+
const privKey = RxAPI.unpack([...RxAPI.base64ToBuffer( readFileSync('__TESTSUITE_jesttest.private.apikey'))])
|
|
16
|
+
const pubKey = RxAPI.unpack([...RxAPI.base64ToBuffer( readFileSync('__TESTSUITE_jesttest.public.apikey'))])
|
|
17
17
|
const signature = await ED.sign(messageUi8, privKey);
|
|
18
18
|
const isValid = await ED.verify(signature, messageUi8, pubKey);
|
|
19
19
|
expect( isValid ).toBeTruthy;
|