@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.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
+ };