@session-foundation/qa-seeder 0.1.21
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/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +1043 -0
- package/dist/index.mjs +1012 -0
- package/package.json +34 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1012 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { isArray, isEmpty as isEmpty3 } from "lodash";
|
|
3
|
+
|
|
4
|
+
// src/requests/seedRequest.ts
|
|
5
|
+
import { sample } from "lodash";
|
|
6
|
+
var fetchedSnodesFromSeed = {};
|
|
7
|
+
async function getAllSnodesFromSeed(seedNodeUrl) {
|
|
8
|
+
if (!seedNodeUrl.startsWith("http")) {
|
|
9
|
+
throw new Error("Invalid seed node URL, must start with http");
|
|
10
|
+
}
|
|
11
|
+
if (seedNodeUrl.endsWith("/json_rpc")) {
|
|
12
|
+
throw new Error("Invalid seed node URL, must NOT finish with /json_rpc");
|
|
13
|
+
}
|
|
14
|
+
if (fetchedSnodesFromSeed[seedNodeUrl]?.length) {
|
|
15
|
+
return fetchedSnodesFromSeed[seedNodeUrl];
|
|
16
|
+
}
|
|
17
|
+
const getAll = new GetSnodesFromSeed();
|
|
18
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
19
|
+
console.info(`Fetching snodes from seed node: "${seedNodeUrl}/json_rpc"`);
|
|
20
|
+
try {
|
|
21
|
+
const result = await fetch(`${seedNodeUrl}/json_rpc`, {
|
|
22
|
+
body: JSON.stringify(getAll.build()),
|
|
23
|
+
method: "POST"
|
|
24
|
+
});
|
|
25
|
+
console.info("snode from list result status:", result.status);
|
|
26
|
+
const json = await result.json();
|
|
27
|
+
fetchedSnodesFromSeed[seedNodeUrl] = json.result.service_node_states;
|
|
28
|
+
return fetchedSnodesFromSeed[seedNodeUrl];
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.warn(e);
|
|
31
|
+
throw e;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function randomSnodeFromNetwork(seedNodeUrl) {
|
|
35
|
+
const snodes = await getAllSnodesFromSeed(seedNodeUrl);
|
|
36
|
+
const snode = sample(snodes);
|
|
37
|
+
if (!snode) {
|
|
38
|
+
throw new Error(`No random snode found on seed node: ${seedNodeUrl}`);
|
|
39
|
+
}
|
|
40
|
+
return snode;
|
|
41
|
+
}
|
|
42
|
+
var GetSnodesFromSeed = class {
|
|
43
|
+
build() {
|
|
44
|
+
return {
|
|
45
|
+
jsonrpc: "2.0",
|
|
46
|
+
id: "0",
|
|
47
|
+
method: "get_n_service_nodes",
|
|
48
|
+
params: {
|
|
49
|
+
active_only: true,
|
|
50
|
+
limit: 20,
|
|
51
|
+
fields: {
|
|
52
|
+
public_ip: true,
|
|
53
|
+
storage_port: true,
|
|
54
|
+
pubkey_x25519: true,
|
|
55
|
+
pubkey_ed25519: true
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/sessionTools/index.ts
|
|
63
|
+
import sessionToolsPromise from "@session-foundation/libsession-wasm/";
|
|
64
|
+
async function loadSessionTools() {
|
|
65
|
+
const loaded = await sessionToolsPromise();
|
|
66
|
+
return loaded;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/requests/snodeRequests.ts
|
|
70
|
+
import { isEmpty } from "lodash";
|
|
71
|
+
var SwarmForSubRequest = class {
|
|
72
|
+
method = "get_swarm";
|
|
73
|
+
pubkey;
|
|
74
|
+
constructor(pubkey) {
|
|
75
|
+
this.pubkey = pubkey;
|
|
76
|
+
}
|
|
77
|
+
async build() {
|
|
78
|
+
return {
|
|
79
|
+
method: this.method,
|
|
80
|
+
params: {
|
|
81
|
+
pubkey: this.pubkey,
|
|
82
|
+
params: {
|
|
83
|
+
active_only: true,
|
|
84
|
+
fields: {
|
|
85
|
+
public_ip: true,
|
|
86
|
+
storage_port: true,
|
|
87
|
+
pubkey_x25519: true,
|
|
88
|
+
pubkey_ed25519: true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
loggingId() {
|
|
95
|
+
return `${this.method}`;
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
var StoreUserConfigSubRequest = class {
|
|
99
|
+
method = "store";
|
|
100
|
+
namespace;
|
|
101
|
+
ttlMs;
|
|
102
|
+
encryptedData;
|
|
103
|
+
destination;
|
|
104
|
+
userSigner;
|
|
105
|
+
constructor(args) {
|
|
106
|
+
this.namespace = args.namespace;
|
|
107
|
+
this.ttlMs = args.ttlMs;
|
|
108
|
+
this.encryptedData = args.encryptedData;
|
|
109
|
+
this.destination = args.sessionId;
|
|
110
|
+
this.userSigner = args.userSigner;
|
|
111
|
+
if (isEmpty(this.encryptedData)) {
|
|
112
|
+
throw new Error("this.encryptedData cannot be empty");
|
|
113
|
+
}
|
|
114
|
+
if (isEmpty(this.destination)) {
|
|
115
|
+
throw new Error("this.destination cannot be empty");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async build() {
|
|
119
|
+
const encryptedDataBase64 = this.userSigner.sodium.to_base64(
|
|
120
|
+
this.encryptedData,
|
|
121
|
+
this.userSigner.sodium.base64_variants.ORIGINAL
|
|
122
|
+
);
|
|
123
|
+
const signDetails = await this.userSigner.getSnodeSignatureParams({
|
|
124
|
+
getNow: () => Date.now(),
|
|
125
|
+
method: this.method,
|
|
126
|
+
namespace: this.namespace
|
|
127
|
+
});
|
|
128
|
+
if (!signDetails) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`[StoreUserConfigSubRequest] signing returned an empty result`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
const toRet = {
|
|
134
|
+
method: this.method,
|
|
135
|
+
params: {
|
|
136
|
+
namespace: this.namespace,
|
|
137
|
+
ttl: this.ttlMs,
|
|
138
|
+
data: encryptedDataBase64,
|
|
139
|
+
...signDetails
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
return toRet;
|
|
143
|
+
}
|
|
144
|
+
loggingId() {
|
|
145
|
+
return `${this.method}-${this.destination}-${this.namespace}`;
|
|
146
|
+
}
|
|
147
|
+
getDestination() {
|
|
148
|
+
return this.destination;
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
var StoreGroupConfigSubRequest = class {
|
|
152
|
+
method = "store";
|
|
153
|
+
namespace;
|
|
154
|
+
ttlMs;
|
|
155
|
+
encryptedData;
|
|
156
|
+
destination;
|
|
157
|
+
adminGroupSigner;
|
|
158
|
+
constructor(args) {
|
|
159
|
+
this.namespace = args.namespace;
|
|
160
|
+
this.ttlMs = args.ttlMs;
|
|
161
|
+
this.encryptedData = args.encryptedData;
|
|
162
|
+
this.destination = args.groupPk;
|
|
163
|
+
this.adminGroupSigner = args.adminGroupSigner;
|
|
164
|
+
if (isEmpty(this.encryptedData)) {
|
|
165
|
+
throw new Error("this.encryptedData cannot be empty");
|
|
166
|
+
}
|
|
167
|
+
if (isEmpty(this.destination)) {
|
|
168
|
+
throw new Error("this.destination cannot be empty");
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async build() {
|
|
172
|
+
const encryptedDataBase64 = this.adminGroupSigner.sodium.to_base64(
|
|
173
|
+
this.encryptedData,
|
|
174
|
+
this.adminGroupSigner.sodium.base64_variants.ORIGINAL
|
|
175
|
+
);
|
|
176
|
+
const signDetails = await this.adminGroupSigner.getSnodeSignatureParams({
|
|
177
|
+
getNow: () => Date.now(),
|
|
178
|
+
method: this.method,
|
|
179
|
+
namespace: this.namespace
|
|
180
|
+
});
|
|
181
|
+
if (!signDetails) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`[StoreGroupConfigSubRequest] signing returned an empty result`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
const toRet = {
|
|
187
|
+
method: this.method,
|
|
188
|
+
params: {
|
|
189
|
+
namespace: this.namespace,
|
|
190
|
+
ttl: this.ttlMs,
|
|
191
|
+
data: encryptedDataBase64,
|
|
192
|
+
...signDetails
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
return toRet;
|
|
196
|
+
}
|
|
197
|
+
loggingId() {
|
|
198
|
+
return `${this.method}-${this.destination}-${this.namespace}`;
|
|
199
|
+
}
|
|
200
|
+
getDestination() {
|
|
201
|
+
return this.destination;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// src/sessionUser.ts
|
|
206
|
+
import { mnDecode, mnEncode } from "@session-foundation/mnemonic";
|
|
207
|
+
|
|
208
|
+
// src/signer/userSigner.ts
|
|
209
|
+
import {
|
|
210
|
+
getSodium
|
|
211
|
+
} from "@session-foundation/sodium";
|
|
212
|
+
function getVerificationDataForStoreRetrieve(params) {
|
|
213
|
+
const signatureTimestamp = params.getNow();
|
|
214
|
+
const verificationString = `${params.method}${params.namespace === 0 ? "" : params.namespace}${signatureTimestamp}`;
|
|
215
|
+
const verificationData = params.sodium.from_string(verificationString);
|
|
216
|
+
return {
|
|
217
|
+
toSign: new Uint8Array(verificationData),
|
|
218
|
+
signatureTimestamp
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
async function getSnodeSignatureShared(params) {
|
|
222
|
+
const { signatureTimestamp, toSign } = getVerificationDataForStoreRetrieve(params);
|
|
223
|
+
const sodium = await getSodium();
|
|
224
|
+
const signature = sodium.crypto_sign_detached(toSign, params.privKey);
|
|
225
|
+
const signatureBase64 = params.sodium.to_base64(
|
|
226
|
+
signature,
|
|
227
|
+
params.sodium.base64_variants.ORIGINAL
|
|
228
|
+
);
|
|
229
|
+
return {
|
|
230
|
+
timestamp: signatureTimestamp,
|
|
231
|
+
signature: signatureBase64
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
var UserSigner = class {
|
|
235
|
+
ed25519PubKey;
|
|
236
|
+
ed25519PrivKey;
|
|
237
|
+
sodium;
|
|
238
|
+
sessionId;
|
|
239
|
+
constructor({
|
|
240
|
+
ed25519PrivKey,
|
|
241
|
+
ed25519PubKey,
|
|
242
|
+
sessionId,
|
|
243
|
+
sodium
|
|
244
|
+
}) {
|
|
245
|
+
this.ed25519PubKey = ed25519PubKey;
|
|
246
|
+
if (this.ed25519PubKey.length !== 64) {
|
|
247
|
+
console.warn("ed25519PubKey length", ed25519PubKey.length);
|
|
248
|
+
throw new Error("ed25519PubKey not 64 long");
|
|
249
|
+
}
|
|
250
|
+
this.ed25519PrivKey = ed25519PrivKey;
|
|
251
|
+
if (this.ed25519PrivKey.length !== 64) {
|
|
252
|
+
console.warn("ed25519PrivKey length", ed25519PrivKey.length);
|
|
253
|
+
throw new Error("ed25519PrivKey not 64 long");
|
|
254
|
+
}
|
|
255
|
+
this.sessionId = sessionId;
|
|
256
|
+
this.sodium = sodium;
|
|
257
|
+
}
|
|
258
|
+
async getSnodeSignatureParams({
|
|
259
|
+
method,
|
|
260
|
+
namespace,
|
|
261
|
+
getNow
|
|
262
|
+
}) {
|
|
263
|
+
if (!this.ed25519PrivKey || !this.ed25519PubKey) {
|
|
264
|
+
const err = `getSnodeSignatureParams "${method}": User has no getUserED25519KeyPairBytes()`;
|
|
265
|
+
throw new Error(err);
|
|
266
|
+
}
|
|
267
|
+
const sigData = await getSnodeSignatureShared({
|
|
268
|
+
pubKey: this.sessionId,
|
|
269
|
+
method,
|
|
270
|
+
namespace,
|
|
271
|
+
privKey: this.ed25519PrivKey,
|
|
272
|
+
getNow,
|
|
273
|
+
sodium: this.sodium
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
...sigData,
|
|
277
|
+
pubkey_ed25519: this.ed25519PubKey,
|
|
278
|
+
pubkey: this.sessionId
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/sessionUser.ts
|
|
284
|
+
function buildUserSigner(user, sodium) {
|
|
285
|
+
const userSigner = new UserSigner({
|
|
286
|
+
sessionId: user.sessionId,
|
|
287
|
+
ed25519PrivKey: user.ed25519Sk,
|
|
288
|
+
ed25519PubKey: sodium.to_hex(user.ed25519Pk),
|
|
289
|
+
sodium
|
|
290
|
+
});
|
|
291
|
+
return userSigner;
|
|
292
|
+
}
|
|
293
|
+
function generateMnemonic(opts) {
|
|
294
|
+
const seedSize = 16;
|
|
295
|
+
const seed = opts.sodium.randombytes_buf(seedSize);
|
|
296
|
+
const hex = opts.sodium.to_hex(seed);
|
|
297
|
+
return mnEncode(hex);
|
|
298
|
+
}
|
|
299
|
+
function mnemonicToRawSeed(mnemonic, sodium) {
|
|
300
|
+
let seedHex = mnDecode(mnemonic);
|
|
301
|
+
const privKeyHexLength = 32 * 2;
|
|
302
|
+
if (seedHex.length !== privKeyHexLength) {
|
|
303
|
+
seedHex = seedHex.concat("0".repeat(32));
|
|
304
|
+
seedHex = seedHex.substring(0, privKeyHexLength);
|
|
305
|
+
}
|
|
306
|
+
const seed = sodium.from_hex(seedHex);
|
|
307
|
+
return seed;
|
|
308
|
+
}
|
|
309
|
+
function sessionGenerateKeyPair(opts) {
|
|
310
|
+
const ed25519KeyPair = opts.sodium.crypto_sign_seed_keypair(
|
|
311
|
+
new Uint8Array(opts.seed)
|
|
312
|
+
);
|
|
313
|
+
const x25519PublicKey = opts.sodium.crypto_sign_ed25519_pk_to_curve25519(
|
|
314
|
+
ed25519KeyPair.publicKey
|
|
315
|
+
);
|
|
316
|
+
const origPub = new Uint8Array(x25519PublicKey);
|
|
317
|
+
const prependedX25519PublicKey = new Uint8Array(33);
|
|
318
|
+
prependedX25519PublicKey.set(origPub, 1);
|
|
319
|
+
prependedX25519PublicKey[0] = 5;
|
|
320
|
+
const x25519SecretKey = opts.sodium.crypto_sign_ed25519_sk_to_curve25519(
|
|
321
|
+
ed25519KeyPair.privateKey
|
|
322
|
+
);
|
|
323
|
+
return {
|
|
324
|
+
x25519PublicKeyWith05: prependedX25519PublicKey,
|
|
325
|
+
x25519SecretKey: x25519SecretKey.buffer,
|
|
326
|
+
ed25519KeyPair
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
var SessionUser = class {
|
|
330
|
+
sessionId;
|
|
331
|
+
ed25519Pk;
|
|
332
|
+
ed25519Sk;
|
|
333
|
+
seed;
|
|
334
|
+
seedPhrase;
|
|
335
|
+
wrappers;
|
|
336
|
+
userProfile;
|
|
337
|
+
contacts;
|
|
338
|
+
userGroups;
|
|
339
|
+
userSigner;
|
|
340
|
+
sodium;
|
|
341
|
+
constructor({ sessionTools, sodium }) {
|
|
342
|
+
const mnemonic = generateMnemonic({ sodium });
|
|
343
|
+
const seed = mnemonicToRawSeed(mnemonic, sodium);
|
|
344
|
+
const userKeys = sessionGenerateKeyPair({ seed, sodium });
|
|
345
|
+
const userProfile = new sessionTools.UserProfileW(
|
|
346
|
+
userKeys.ed25519KeyPair.privateKey,
|
|
347
|
+
void 0
|
|
348
|
+
);
|
|
349
|
+
const contacts = new sessionTools.ContactsW(
|
|
350
|
+
userKeys.ed25519KeyPair.privateKey,
|
|
351
|
+
void 0
|
|
352
|
+
);
|
|
353
|
+
const userGroups = new sessionTools.UserGroupsW(
|
|
354
|
+
userKeys.ed25519KeyPair.privateKey,
|
|
355
|
+
void 0
|
|
356
|
+
);
|
|
357
|
+
const wrappers = [userProfile, contacts, userGroups];
|
|
358
|
+
this.sessionId = sodium.to_hex(
|
|
359
|
+
userKeys.x25519PublicKeyWith05
|
|
360
|
+
);
|
|
361
|
+
this.ed25519Pk = userKeys.ed25519KeyPair.publicKey;
|
|
362
|
+
this.ed25519Sk = userKeys.ed25519KeyPair.privateKey;
|
|
363
|
+
this.seed = seed;
|
|
364
|
+
this.seedPhrase = mnEncode(sodium.to_hex(seed).slice(0, 32));
|
|
365
|
+
this.wrappers = wrappers;
|
|
366
|
+
this.userProfile = userProfile;
|
|
367
|
+
this.contacts = contacts;
|
|
368
|
+
this.userGroups = userGroups;
|
|
369
|
+
this.sodium = sodium;
|
|
370
|
+
this.userSigner = buildUserSigner(this, this.sodium);
|
|
371
|
+
}
|
|
372
|
+
async pushChangesToSwarm(snode) {
|
|
373
|
+
const storeRequests = this.wrappers.map(
|
|
374
|
+
(wrapper) => new StoreUserConfigSubRequest({
|
|
375
|
+
namespace: wrapper.storageNamespace().value,
|
|
376
|
+
encryptedData: this.sodium.from_hex(
|
|
377
|
+
wrapper.makePushHex().get(0)?.toString() ?? ""
|
|
378
|
+
),
|
|
379
|
+
sessionId: this.sessionId,
|
|
380
|
+
ttlMs: 1e3 * 3600 * 24,
|
|
381
|
+
// 1 day should be enough for testing and debugging a test?
|
|
382
|
+
userSigner: this.userSigner
|
|
383
|
+
})
|
|
384
|
+
);
|
|
385
|
+
const storeResult = await Promise.all(
|
|
386
|
+
storeRequests.map(async (request) => {
|
|
387
|
+
const builtRequest = await request.build();
|
|
388
|
+
console.info(
|
|
389
|
+
"storing to snode",
|
|
390
|
+
`https://${snode.ip}:${snode.port}/storage_rpc/v1`
|
|
391
|
+
);
|
|
392
|
+
const ret = await fetch(
|
|
393
|
+
`https://${snode.ip}:${snode.port}/storage_rpc/v1`,
|
|
394
|
+
{
|
|
395
|
+
body: JSON.stringify(builtRequest),
|
|
396
|
+
method: "POST"
|
|
397
|
+
}
|
|
398
|
+
);
|
|
399
|
+
return ret.status;
|
|
400
|
+
})
|
|
401
|
+
);
|
|
402
|
+
console.log(`storeStatus for ${this.userProfile.getName()}:`, storeResult);
|
|
403
|
+
}
|
|
404
|
+
freeMemory() {
|
|
405
|
+
this.wrappers.map((wrapper) => wrapper.delete());
|
|
406
|
+
}
|
|
407
|
+
toString() {
|
|
408
|
+
const name = this.userProfile.getName();
|
|
409
|
+
const sessionId = this.sessionId;
|
|
410
|
+
const seedPhrase = this.seedPhrase;
|
|
411
|
+
return `SessionUser: ${JSON.stringify({ name, sessionId, seedPhrase })}`;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
function createRandomUser(details) {
|
|
415
|
+
return new SessionUser(details);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/signer/groupSigner.ts
|
|
419
|
+
import {
|
|
420
|
+
getSodium as getSodium2
|
|
421
|
+
} from "@session-foundation/sodium";
|
|
422
|
+
function getVerificationDataForStoreRetrieve2(params) {
|
|
423
|
+
const signatureTimestamp = params.getNow();
|
|
424
|
+
const verificationString = `${params.method}${params.namespace === 0 ? "" : params.namespace}${signatureTimestamp}`;
|
|
425
|
+
const verificationData = params.sodium.from_string(verificationString);
|
|
426
|
+
return {
|
|
427
|
+
toSign: new Uint8Array(verificationData),
|
|
428
|
+
signatureTimestamp
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
async function getSnodeSignatureShared2(params) {
|
|
432
|
+
const { signatureTimestamp, toSign } = getVerificationDataForStoreRetrieve2(params);
|
|
433
|
+
const sodium = await getSodium2();
|
|
434
|
+
const signature = sodium.crypto_sign_detached(toSign, params.privKey);
|
|
435
|
+
const signatureBase64 = params.sodium.to_base64(
|
|
436
|
+
signature,
|
|
437
|
+
params.sodium.base64_variants.ORIGINAL
|
|
438
|
+
);
|
|
439
|
+
return {
|
|
440
|
+
timestamp: signatureTimestamp,
|
|
441
|
+
signature: signatureBase64
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
var GroupAdminSigner = class {
|
|
445
|
+
groupPk;
|
|
446
|
+
sodium;
|
|
447
|
+
groupSecretKey;
|
|
448
|
+
constructor({
|
|
449
|
+
groupSecretKey,
|
|
450
|
+
groupPk,
|
|
451
|
+
sodium
|
|
452
|
+
}) {
|
|
453
|
+
this.groupSecretKey = groupSecretKey;
|
|
454
|
+
if (this.groupSecretKey.length !== 64) {
|
|
455
|
+
console.warn("groupSecretKey length", groupSecretKey.length);
|
|
456
|
+
throw new Error("groupSecretKey not 64 long");
|
|
457
|
+
}
|
|
458
|
+
this.groupPk = groupPk;
|
|
459
|
+
this.sodium = sodium;
|
|
460
|
+
}
|
|
461
|
+
async getSnodeSignatureParams({
|
|
462
|
+
method,
|
|
463
|
+
namespace,
|
|
464
|
+
getNow
|
|
465
|
+
}) {
|
|
466
|
+
const sigData = await getSnodeSignatureShared2({
|
|
467
|
+
pubKey: this.groupPk,
|
|
468
|
+
method,
|
|
469
|
+
namespace,
|
|
470
|
+
privKey: this.groupSecretKey,
|
|
471
|
+
getNow,
|
|
472
|
+
sodium: this.sodium
|
|
473
|
+
});
|
|
474
|
+
return {
|
|
475
|
+
...sigData,
|
|
476
|
+
pubkey: this.groupPk
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// src/sessionGroup.ts
|
|
482
|
+
import { compact } from "lodash";
|
|
483
|
+
function buildGroupSigner(group, sodium) {
|
|
484
|
+
if (!group.groupSecretKey) {
|
|
485
|
+
throw new Error(
|
|
486
|
+
"only group admin signer (with admin key) is supported currently"
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
return new GroupAdminSigner({
|
|
490
|
+
groupPk: group.groupPk,
|
|
491
|
+
groupSecretKey: group.groupSecretKey,
|
|
492
|
+
sodium
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
var SessionGroup = class {
|
|
496
|
+
groupPk;
|
|
497
|
+
groupSecretKey;
|
|
498
|
+
metagroupW;
|
|
499
|
+
adminGroupSigner;
|
|
500
|
+
groupName;
|
|
501
|
+
sodium;
|
|
502
|
+
constructor({
|
|
503
|
+
sessionTools,
|
|
504
|
+
groupName,
|
|
505
|
+
members,
|
|
506
|
+
sodium
|
|
507
|
+
}) {
|
|
508
|
+
if (!members.length) {
|
|
509
|
+
throw new Error("Excepted at least one creator/member");
|
|
510
|
+
}
|
|
511
|
+
this.groupName = groupName;
|
|
512
|
+
const [creator, ...otherMembers] = members;
|
|
513
|
+
if (!creator) {
|
|
514
|
+
throw new Error("Expected at least the creator");
|
|
515
|
+
}
|
|
516
|
+
const newGroup = creator.userGroups.createGroup();
|
|
517
|
+
newGroup.name = groupName;
|
|
518
|
+
newGroup.joinedAtSeconds = BigInt(Math.floor(Date.now() / 1e3));
|
|
519
|
+
this.groupSecretKey = sodium.from_hex(newGroup.adminSecretKey.toString());
|
|
520
|
+
this.metagroupW = new sessionTools.MetaGroupW(
|
|
521
|
+
creator.ed25519Sk,
|
|
522
|
+
sodium.from_hex(newGroup.groupPk.toString().slice(2)).buffer,
|
|
523
|
+
this.groupSecretKey,
|
|
524
|
+
void 0
|
|
525
|
+
);
|
|
526
|
+
[creator, ...otherMembers].forEach((member) => {
|
|
527
|
+
if (member.sessionId !== creator.sessionId) {
|
|
528
|
+
const authDataHex = this.metagroupW.makeSubaccountHex(
|
|
529
|
+
member.sessionId,
|
|
530
|
+
true,
|
|
531
|
+
false
|
|
532
|
+
);
|
|
533
|
+
const groupForUser = member.userGroups.getOrConstructGroup(
|
|
534
|
+
newGroup.groupPk
|
|
535
|
+
);
|
|
536
|
+
groupForUser.name = newGroup.name;
|
|
537
|
+
groupForUser.joinedAtSeconds = newGroup.joinedAtSeconds;
|
|
538
|
+
groupForUser.invited = false;
|
|
539
|
+
groupForUser.priority = 0;
|
|
540
|
+
groupForUser.authData = sodium.from_hex(authDataHex);
|
|
541
|
+
member.userGroups.setGroup(groupForUser);
|
|
542
|
+
} else {
|
|
543
|
+
member.userGroups.setGroup(newGroup);
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
this.metagroupW.setNameTruncated(groupName);
|
|
547
|
+
[creator, ...otherMembers].forEach((u) => {
|
|
548
|
+
const member = this.metagroupW.membersGetOrConstruct(u.sessionId);
|
|
549
|
+
if (u.sessionId === creator.sessionId) {
|
|
550
|
+
member.setPromotionAccepted();
|
|
551
|
+
} else {
|
|
552
|
+
member.setInviteAccepted();
|
|
553
|
+
}
|
|
554
|
+
member.setNameTruncated(u.userProfile.getName()?.toString() || "");
|
|
555
|
+
this.metagroupW.membersSet(member);
|
|
556
|
+
});
|
|
557
|
+
this.metagroupW.rekeyHex();
|
|
558
|
+
this.groupPk = newGroup.groupPk.toString();
|
|
559
|
+
this.sodium = sodium;
|
|
560
|
+
this.adminGroupSigner = buildGroupSigner(this, this.sodium);
|
|
561
|
+
}
|
|
562
|
+
async pushChangesToSwarm(snode) {
|
|
563
|
+
const pushHex = this.metagroupW.pushHex();
|
|
564
|
+
const toPush = compact([
|
|
565
|
+
pushHex.keysPush,
|
|
566
|
+
pushHex.membersPush,
|
|
567
|
+
pushHex.infosPush
|
|
568
|
+
]);
|
|
569
|
+
const storeRequests = toPush.map((m) => {
|
|
570
|
+
return new StoreGroupConfigSubRequest({
|
|
571
|
+
namespace: m.storageNamespace.value,
|
|
572
|
+
encryptedData: this.sodium.from_hex(m.dataHex.toString()),
|
|
573
|
+
groupPk: this.groupPk,
|
|
574
|
+
ttlMs: 1e3 * 3600 * 24,
|
|
575
|
+
// 1 day should be enough for testing and debugging a test?
|
|
576
|
+
adminGroupSigner: this.adminGroupSigner
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
const storeResult = await Promise.all(
|
|
580
|
+
storeRequests.map(async (request) => {
|
|
581
|
+
const builtRequest = await request.build();
|
|
582
|
+
console.info(
|
|
583
|
+
"storing to snode",
|
|
584
|
+
`https://${snode.ip}:${snode.port}/storage_rpc/v1`
|
|
585
|
+
);
|
|
586
|
+
const ret = await fetch(
|
|
587
|
+
`https://${snode.ip}:${snode.port}/storage_rpc/v1`,
|
|
588
|
+
{
|
|
589
|
+
body: JSON.stringify(builtRequest),
|
|
590
|
+
method: "POST"
|
|
591
|
+
}
|
|
592
|
+
);
|
|
593
|
+
return ret.status;
|
|
594
|
+
})
|
|
595
|
+
);
|
|
596
|
+
console.log(`storeStatus for (group) ${this.groupPk}:`, storeResult);
|
|
597
|
+
}
|
|
598
|
+
freeMemory() {
|
|
599
|
+
this.metagroupW.delete();
|
|
600
|
+
}
|
|
601
|
+
toString() {
|
|
602
|
+
const groupName = this.groupName;
|
|
603
|
+
const groupPk = this.groupPk;
|
|
604
|
+
const groupSecretKeyHex = this.sodium.to_hex(this.groupSecretKey);
|
|
605
|
+
return `SessionGroup: ${JSON.stringify({ groupPk, groupName, groupSecretKeyHex })}`;
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
// src/index.ts
|
|
610
|
+
import {
|
|
611
|
+
assertUnreachable
|
|
612
|
+
} from "@session-foundation/basic-types";
|
|
613
|
+
import { getSodium as getSodium3 } from "@session-foundation/sodium";
|
|
614
|
+
|
|
615
|
+
// src/actions/fetchSwarmOf.ts
|
|
616
|
+
import { isEmpty as isEmpty2, sample as sample2 } from "lodash";
|
|
617
|
+
var fetchedSwarms = {};
|
|
618
|
+
async function getSwarmOfUser(sessionId, snode) {
|
|
619
|
+
const swarmRequest = new SwarmForSubRequest(sessionId);
|
|
620
|
+
const swarmResult = await fetch(
|
|
621
|
+
`https://${snode.public_ip}:${snode.storage_port}/storage_rpc/v1`,
|
|
622
|
+
{
|
|
623
|
+
body: JSON.stringify(await swarmRequest.build()),
|
|
624
|
+
method: "POST"
|
|
625
|
+
}
|
|
626
|
+
);
|
|
627
|
+
const swarm = await swarmResult.json();
|
|
628
|
+
if (isEmpty2(fetchedSwarms[sessionId])) {
|
|
629
|
+
fetchedSwarms[sessionId] = swarm;
|
|
630
|
+
}
|
|
631
|
+
return swarm.snodes;
|
|
632
|
+
}
|
|
633
|
+
async function randomSnodeOnUserSwarm(sessionId, snode) {
|
|
634
|
+
const userSwarm = fetchedSwarms[sessionId] || await getSwarmOfUser(sessionId, snode);
|
|
635
|
+
const randomSnodeOnSwarm = sample2(userSwarm);
|
|
636
|
+
if (!randomSnodeOnSwarm) {
|
|
637
|
+
throw new Error(`did not find a snode for user: ${sessionId}`);
|
|
638
|
+
}
|
|
639
|
+
console.info(
|
|
640
|
+
`random snode for user: ${sessionId} is snode: ${randomSnodeOnSwarm.pubkey_ed25519}`
|
|
641
|
+
);
|
|
642
|
+
return randomSnodeOnSwarm;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/index.ts
|
|
646
|
+
var networks = {
|
|
647
|
+
mainnet: "https://seed2.getsession.org:4443",
|
|
648
|
+
testnet: "http://seed2.getsession.org:38157"
|
|
649
|
+
};
|
|
650
|
+
function getSeedNodeUrl(network) {
|
|
651
|
+
if (network === "mainnet" || network === "testnet") {
|
|
652
|
+
return networks[network];
|
|
653
|
+
}
|
|
654
|
+
return network;
|
|
655
|
+
}
|
|
656
|
+
function makeFriendsAndKnown(users) {
|
|
657
|
+
if (users.length < 2) {
|
|
658
|
+
throw new Error("needs at least two users to make them friends");
|
|
659
|
+
}
|
|
660
|
+
console.info(
|
|
661
|
+
`makeFriendsAndKnown: users: [${users.map((m) => m.toString()).join(",\n")}]`
|
|
662
|
+
);
|
|
663
|
+
users.forEach((user1) => {
|
|
664
|
+
users.forEach((user2) => {
|
|
665
|
+
if (user1.sessionId === user2.sessionId) {
|
|
666
|
+
console.info(
|
|
667
|
+
"makeFriendsAndKnown: user1 === user2. Skipping",
|
|
668
|
+
user1.toString()
|
|
669
|
+
);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
console.info("makeFriendsAndKnown: user1", user1.toString());
|
|
673
|
+
if (user2) {
|
|
674
|
+
console.info("makeFriendsAndKnown: user2", user2.toString());
|
|
675
|
+
user1.contacts.setApproved(user2.sessionId, true);
|
|
676
|
+
user1.contacts.setApprovedMe(user2.sessionId, true);
|
|
677
|
+
user1.contacts.setName(
|
|
678
|
+
user2.sessionId,
|
|
679
|
+
user2.userProfile.getName() || ""
|
|
680
|
+
);
|
|
681
|
+
user2.contacts.setApproved(user1.sessionId, true);
|
|
682
|
+
user2.contacts.setApprovedMe(user1.sessionId, true);
|
|
683
|
+
user2.contacts.setName(
|
|
684
|
+
user1.sessionId,
|
|
685
|
+
user1.userProfile.getName() || ""
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
function makeGroupWithMembers({
|
|
692
|
+
members,
|
|
693
|
+
groupName,
|
|
694
|
+
sessionTools,
|
|
695
|
+
sodium
|
|
696
|
+
}) {
|
|
697
|
+
return new SessionGroup({ sessionTools, groupName, members, sodium });
|
|
698
|
+
}
|
|
699
|
+
var PrebuiltStateWithWrappers = class {
|
|
700
|
+
userWrappers;
|
|
701
|
+
groupWrapper;
|
|
702
|
+
constructor(args) {
|
|
703
|
+
this.userWrappers = args.users;
|
|
704
|
+
this.groupWrapper = args.group;
|
|
705
|
+
}
|
|
706
|
+
async pushChangesToSwarms({
|
|
707
|
+
seedNodeUrl
|
|
708
|
+
}) {
|
|
709
|
+
console.info(`Pushing changes to swarms on network "${seedNodeUrl}"`);
|
|
710
|
+
const promises = [];
|
|
711
|
+
if (this.userWrappers && isArray(this.userWrappers) && this.userWrappers?.length) {
|
|
712
|
+
const users = this.userWrappers;
|
|
713
|
+
const userPromise = pushUsersChangesToSwarm({ seedNodeUrl, users });
|
|
714
|
+
promises.push(userPromise);
|
|
715
|
+
}
|
|
716
|
+
if (this.groupWrapper && this.groupWrapper instanceof SessionGroup) {
|
|
717
|
+
const randomNetworkSnode = await randomSnodeFromNetwork(seedNodeUrl);
|
|
718
|
+
const groupWrapper = this.groupWrapper;
|
|
719
|
+
const swarmSnode = await randomSnodeOnUserSwarm(
|
|
720
|
+
groupWrapper.groupPk,
|
|
721
|
+
randomNetworkSnode
|
|
722
|
+
);
|
|
723
|
+
const groupPromise = groupWrapper.pushChangesToSwarm(swarmSnode);
|
|
724
|
+
promises.push(groupPromise);
|
|
725
|
+
}
|
|
726
|
+
console.info(`promises`, promises);
|
|
727
|
+
await Promise.all(promises);
|
|
728
|
+
console.info(`Pushed changes to swarms on network "${seedNodeUrl}"`);
|
|
729
|
+
}
|
|
730
|
+
};
|
|
731
|
+
var usernames = ["Alice", "Bob", "Charlie", "Dracula"];
|
|
732
|
+
var USERNAME = {
|
|
733
|
+
ALICE: "Alice",
|
|
734
|
+
BOB: "Bob",
|
|
735
|
+
CHARLIE: "Charlie",
|
|
736
|
+
DRACULA: "Dracula"
|
|
737
|
+
};
|
|
738
|
+
function assertUserCountIsValid(count) {
|
|
739
|
+
if (count > usernames.length) {
|
|
740
|
+
throw new Error(`count should be less than ${usernames.length} currently`);
|
|
741
|
+
}
|
|
742
|
+
if (count < 1) {
|
|
743
|
+
throw new Error("count should be at least 1");
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function createCountOfUsers({
|
|
747
|
+
count,
|
|
748
|
+
sessionTools,
|
|
749
|
+
sodium
|
|
750
|
+
}) {
|
|
751
|
+
return Array.from(
|
|
752
|
+
{ length: count },
|
|
753
|
+
() => createRandomUser({ sodium, sessionTools })
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
function assignNamesToUsers(users) {
|
|
757
|
+
users.forEach((user, index) => {
|
|
758
|
+
const name = usernames[index];
|
|
759
|
+
if (!name) {
|
|
760
|
+
throw new Error(
|
|
761
|
+
`assignNamesToUsers: username at index ${index} is empty`
|
|
762
|
+
);
|
|
763
|
+
}
|
|
764
|
+
user.userProfile.setName(name);
|
|
765
|
+
return user;
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
function createGroupWithMembers({
|
|
769
|
+
groupName,
|
|
770
|
+
users,
|
|
771
|
+
sodium,
|
|
772
|
+
sessionTools
|
|
773
|
+
}) {
|
|
774
|
+
if (!groupName) {
|
|
775
|
+
throw new Error("groupName should be provided");
|
|
776
|
+
}
|
|
777
|
+
const group = makeGroupWithMembers({
|
|
778
|
+
groupName,
|
|
779
|
+
members: users,
|
|
780
|
+
sessionTools,
|
|
781
|
+
sodium
|
|
782
|
+
});
|
|
783
|
+
console.log("creatorMetagroupW info:", group.metagroupW.makeInfoDumpHex());
|
|
784
|
+
console.log(
|
|
785
|
+
"creatorMetagroupW members:",
|
|
786
|
+
group.metagroupW.makeMembersDumpHex()
|
|
787
|
+
);
|
|
788
|
+
console.log("creatorMetagroupW keys:", group.metagroupW.makeKeysDumpHex());
|
|
789
|
+
return {
|
|
790
|
+
group
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
async function pushUsersChangesToSwarm({
|
|
794
|
+
users,
|
|
795
|
+
seedNodeUrl
|
|
796
|
+
}) {
|
|
797
|
+
await Promise.all(
|
|
798
|
+
users.map(async (user) => {
|
|
799
|
+
const randomNetworkSnode = await randomSnodeFromNetwork(seedNodeUrl);
|
|
800
|
+
const swarmSnode = await randomSnodeOnUserSwarm(
|
|
801
|
+
user.sessionId,
|
|
802
|
+
randomNetworkSnode
|
|
803
|
+
);
|
|
804
|
+
await user.pushChangesToSwarm(swarmSnode);
|
|
805
|
+
})
|
|
806
|
+
);
|
|
807
|
+
console.info(
|
|
808
|
+
`seed of users:
|
|
809
|
+
${users.map((u) => `"${u.userProfile.getName()}": "${u.seedPhrase}"`).join("\n ")} `
|
|
810
|
+
);
|
|
811
|
+
}
|
|
812
|
+
function toStateUsers(users) {
|
|
813
|
+
return users.map((user, index) => {
|
|
814
|
+
const userName = user.userProfile.getName()?.toString();
|
|
815
|
+
if (!userName) {
|
|
816
|
+
throw new Error(`userName should be defined for user at index: ${index}`);
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
seed: user.seed,
|
|
820
|
+
seedPhrase: user.seedPhrase,
|
|
821
|
+
sessionId: user.sessionId,
|
|
822
|
+
userName
|
|
823
|
+
};
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
function toStateGroup(group) {
|
|
827
|
+
if (!group.groupSecretKey || isEmpty3(group.groupSecretKey)) {
|
|
828
|
+
throw new Error("groupSecretKey should be defined");
|
|
829
|
+
}
|
|
830
|
+
return {
|
|
831
|
+
groupPk: group.groupPk,
|
|
832
|
+
groupName: group.groupName,
|
|
833
|
+
adminSecretKey: group.groupSecretKey
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
function prepareUsers({
|
|
837
|
+
sessionTools,
|
|
838
|
+
sodium,
|
|
839
|
+
usersCount
|
|
840
|
+
}) {
|
|
841
|
+
const users = createCountOfUsers({
|
|
842
|
+
count: usersCount,
|
|
843
|
+
sodium,
|
|
844
|
+
sessionTools
|
|
845
|
+
});
|
|
846
|
+
assignNamesToUsers(users);
|
|
847
|
+
return users;
|
|
848
|
+
}
|
|
849
|
+
function prepareFriends({
|
|
850
|
+
friendsCount,
|
|
851
|
+
sessionTools,
|
|
852
|
+
sodium
|
|
853
|
+
}) {
|
|
854
|
+
assertUserCountIsValid(friendsCount);
|
|
855
|
+
const users = prepareUsers({
|
|
856
|
+
usersCount: friendsCount,
|
|
857
|
+
sessionTools,
|
|
858
|
+
sodium
|
|
859
|
+
});
|
|
860
|
+
makeFriendsAndKnown(users);
|
|
861
|
+
return {
|
|
862
|
+
stateUsers: toStateUsers(users),
|
|
863
|
+
usersWrapper: users
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
function prepareFriendsInGroup({
|
|
867
|
+
friendsCount,
|
|
868
|
+
groupName,
|
|
869
|
+
sessionTools,
|
|
870
|
+
sodium
|
|
871
|
+
}) {
|
|
872
|
+
if (!groupName) {
|
|
873
|
+
throw new Error("groupName should be provided");
|
|
874
|
+
}
|
|
875
|
+
const { stateUsers, usersWrapper } = prepareFriends({
|
|
876
|
+
friendsCount,
|
|
877
|
+
sessionTools,
|
|
878
|
+
sodium
|
|
879
|
+
});
|
|
880
|
+
const { group } = createGroupWithMembers({
|
|
881
|
+
groupName,
|
|
882
|
+
users: usersWrapper,
|
|
883
|
+
sodium,
|
|
884
|
+
sessionTools
|
|
885
|
+
});
|
|
886
|
+
return {
|
|
887
|
+
stateUsers,
|
|
888
|
+
usersWrapper,
|
|
889
|
+
stateGroup: toStateGroup(group),
|
|
890
|
+
groupWrapper: group
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
async function buildStateForTest(stateToBuild, groupName, network) {
|
|
894
|
+
const seedNodeUrl = getSeedNodeUrl(network);
|
|
895
|
+
console.info(
|
|
896
|
+
`[buildStateForTest] stateToBuild:"${stateToBuild}". seedNodeUrl: "${seedNodeUrl}" (${network})`
|
|
897
|
+
);
|
|
898
|
+
const sodium = await getSodium3();
|
|
899
|
+
const sessionTools = await loadSessionTools();
|
|
900
|
+
switch (stateToBuild) {
|
|
901
|
+
case "none": {
|
|
902
|
+
return { users: {} };
|
|
903
|
+
}
|
|
904
|
+
case "1user": {
|
|
905
|
+
const users = prepareUsers({ usersCount: 1, sessionTools, sodium });
|
|
906
|
+
await new PrebuiltStateWithWrappers({
|
|
907
|
+
users,
|
|
908
|
+
group: null
|
|
909
|
+
}).pushChangesToSwarms({ seedNodeUrl });
|
|
910
|
+
return {
|
|
911
|
+
users: toStateUsers(users)
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
case "2users": {
|
|
915
|
+
const users = prepareUsers({ usersCount: 2, sessionTools, sodium });
|
|
916
|
+
await new PrebuiltStateWithWrappers({
|
|
917
|
+
users,
|
|
918
|
+
group: null
|
|
919
|
+
}).pushChangesToSwarms({ seedNodeUrl });
|
|
920
|
+
return {
|
|
921
|
+
users: toStateUsers(users)
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
case "3users": {
|
|
925
|
+
const users = prepareUsers({ usersCount: 3, sessionTools, sodium });
|
|
926
|
+
await new PrebuiltStateWithWrappers({
|
|
927
|
+
users,
|
|
928
|
+
group: null
|
|
929
|
+
}).pushChangesToSwarms({ seedNodeUrl });
|
|
930
|
+
return {
|
|
931
|
+
users: toStateUsers(users)
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
case "2friends": {
|
|
935
|
+
const state = prepareFriends({
|
|
936
|
+
friendsCount: 2,
|
|
937
|
+
sessionTools,
|
|
938
|
+
sodium
|
|
939
|
+
});
|
|
940
|
+
await new PrebuiltStateWithWrappers({
|
|
941
|
+
users: state.usersWrapper,
|
|
942
|
+
group: null
|
|
943
|
+
}).pushChangesToSwarms({ seedNodeUrl });
|
|
944
|
+
return {
|
|
945
|
+
users: state.stateUsers
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
case "2friendsInGroup": {
|
|
949
|
+
if (!groupName) {
|
|
950
|
+
throw new Error("state 2friendsInGroup needs a groupName");
|
|
951
|
+
}
|
|
952
|
+
const state = prepareFriendsInGroup({
|
|
953
|
+
friendsCount: 2,
|
|
954
|
+
sessionTools,
|
|
955
|
+
sodium,
|
|
956
|
+
groupName
|
|
957
|
+
});
|
|
958
|
+
await new PrebuiltStateWithWrappers({
|
|
959
|
+
users: state.usersWrapper,
|
|
960
|
+
group: state.groupWrapper
|
|
961
|
+
}).pushChangesToSwarms({ seedNodeUrl });
|
|
962
|
+
return {
|
|
963
|
+
users: state.stateUsers,
|
|
964
|
+
group: state.stateGroup
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
case "3friends": {
|
|
968
|
+
const state = prepareFriends({
|
|
969
|
+
friendsCount: 3,
|
|
970
|
+
sessionTools,
|
|
971
|
+
sodium
|
|
972
|
+
});
|
|
973
|
+
await new PrebuiltStateWithWrappers({
|
|
974
|
+
users: state.usersWrapper,
|
|
975
|
+
group: null
|
|
976
|
+
}).pushChangesToSwarms({ seedNodeUrl });
|
|
977
|
+
return {
|
|
978
|
+
users: state.stateUsers
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
case "3friendsInGroup": {
|
|
982
|
+
if (!groupName) {
|
|
983
|
+
throw new Error("state 3friendsInGroup needs a groupName");
|
|
984
|
+
}
|
|
985
|
+
const state = prepareFriendsInGroup({
|
|
986
|
+
friendsCount: 3,
|
|
987
|
+
sessionTools,
|
|
988
|
+
sodium,
|
|
989
|
+
groupName
|
|
990
|
+
});
|
|
991
|
+
await new PrebuiltStateWithWrappers({
|
|
992
|
+
users: state.usersWrapper,
|
|
993
|
+
group: state.groupWrapper
|
|
994
|
+
}).pushChangesToSwarms({ seedNodeUrl });
|
|
995
|
+
return {
|
|
996
|
+
users: state.stateUsers,
|
|
997
|
+
group: state.stateGroup
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
default:
|
|
1001
|
+
assertUnreachable(
|
|
1002
|
+
stateToBuild,
|
|
1003
|
+
`buildStateForTest of "${stateToBuild}" not implemented`
|
|
1004
|
+
);
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
export {
|
|
1009
|
+
USERNAME,
|
|
1010
|
+
buildStateForTest,
|
|
1011
|
+
usernames
|
|
1012
|
+
};
|