Compare commits
193 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7686c60333 | |||
| 98c03ffa56 | |||
| 0a3a31dedc | |||
| 79ae9b69ef | |||
| 549b58311d | |||
| 279b494a60 | |||
| 3be0a6cf65 | |||
| 9b8add1daa | |||
| 2193378d42 | |||
| c57263e91b | |||
| 685f26792f | |||
| 109659152d | |||
| 73a75f7aef | |||
| 06956efa3f | |||
| f130ff73ad | |||
| ddaabdc856 | |||
| 65de9d146d | |||
| ec3aa7d8eb | |||
| 805e1b8759 | |||
| 53db649e84 | |||
| f76092e8e7 | |||
| c75fda0eb1 | |||
| 9dfbc8c9c8 | |||
| 0171791188 | |||
| f6a9d0b843 | |||
| 3c750f61e5 | |||
| a8e7c3ca49 | |||
| 4f39630861 | |||
| 63714092df | |||
| cd89f4866a | |||
| a94c4c0895 | |||
| 63b0658c65 | |||
| 6b161700f6 | |||
| dcfe5f7c5b | |||
| cd89e26b74 | |||
| 4f9df2a8b1 | |||
| f6a4fc3b6f | |||
| 899db660ce | |||
| ec0ac56b8a | |||
| 8981ffdcf9 | |||
| df25c01771 | |||
| a11dead84a | |||
| 5aa999643f | |||
| 96b54db060 | |||
| 5bd4295164 | |||
| b07627d57e | |||
| c4667a6792 | |||
| 2e2d258e53 | |||
| 843c74db63 | |||
| 142443b87e | |||
| ec4765c9c6 | |||
| 77a56600f0 | |||
| 90027dc525 | |||
| 6a39e19f2e | |||
| 48140647b7 | |||
| 7d1cb6ca19 | |||
| d1f7066eb5 | |||
| 2789951d9a | |||
| faab815c79 | |||
| a205fecece | |||
| f3116993d8 | |||
| 4fae075dae | |||
| da46955817 | |||
| f2fc837811 | |||
| e7929d6f6b | |||
| e8f99cfdbe | |||
| b4ccd16d8b | |||
| f7974c5db8 | |||
| 90a925ee62 | |||
| 4890c528bc | |||
| d96de448d0 | |||
| 74cd6f0198 | |||
| e71ae7198f | |||
| 273f612d46 | |||
| 2e8d318587 | |||
| 17bd44f840 | |||
| c1076f81a6 | |||
| 26507ee7d3 | |||
| 4677312cad | |||
| 07eddc8187 | |||
| b9a9a407e9 | |||
| 3c482eff6e | |||
| 2880049196 | |||
| 8d2e8b3d26 | |||
| 33bf344d63 | |||
| 0efa78c4a8 | |||
| 32ee16bf0b | |||
| a8f70fe4a2 | |||
| f6fe5d41ea | |||
| 2a87533b12 | |||
| 499fd8a91d | |||
| e336b9948e | |||
| 1cf2b10e31 | |||
| b3cd42de58 | |||
| f16498915a | |||
| d364510400 | |||
| 96f9e39e1d | |||
| e083bfcfe4 | |||
| 22c59be82c | |||
| f17e7378f7 | |||
| 1bb455675e | |||
| 73076a2b26 | |||
| 1e1bf25514 | |||
| 4df29535dc | |||
| 6f08b60789 | |||
| 9ec0f4a88e | |||
| 6b4b7f5cdd | |||
| 581cba9365 | |||
| c7d99bb951 | |||
| ee938e6a0c | |||
| 00501f7073 | |||
| db80666271 | |||
| 3f9fdac9ec | |||
| 3257315676 | |||
| 2f874a66de | |||
| defbcea227 | |||
| 58f79a972c | |||
| de531d41ed | |||
| 15df2cfbe5 | |||
| 987401c320 | |||
| 81cbc48521 | |||
| 8935eb125b | |||
| ca2aad778b | |||
| 584c902f93 | |||
| 2b15d53f45 | |||
| ce98ce72d8 | |||
| 5a8bad4503 | |||
| 0e37084f34 | |||
| 8af83ceac6 | |||
| ee6b6ecc7e | |||
| 27a9557c7b | |||
| c143fef912 | |||
| e7e48f0e53 | |||
| c62b344349 | |||
| 441fbf8255 | |||
| b5cde68e62 | |||
| c3d38fb904 | |||
| 2922306e25 | |||
| 204b2e1101 | |||
| c022486e63 | |||
| 2a7a681b7d | |||
| a50b4ad211 | |||
| ca613ad3aa | |||
| f518c8377b | |||
| e1a30ea01a | |||
| 9f5a0a7ca6 | |||
| c7de97d6dd | |||
| 85a7ec9f02 | |||
| 8d105cf4dd | |||
| 6131d000e6 | |||
| 972a220209 | |||
| b7cf7e06d2 | |||
| 2df42e222c | |||
| 83a0a6455f | |||
| f53e5c42c3 | |||
| b537a7c2c7 | |||
| 9fb8b1d7c0 | |||
| a1482a2887 | |||
| b57df35f8c | |||
| 7f0a02f6ec | |||
| cfc86ba9f5 | |||
| 9c68de64a0 | |||
| 2fdd09deee | |||
| 5f9a514bc7 | |||
| 0812a0f599 | |||
| 7741a3fea1 | |||
| 017d536d35 | |||
| eb444f73ce | |||
| d108919cf6 | |||
| 5d454f2efc | |||
| ffac0a1f92 | |||
| 8004d54d5e | |||
| b6febc51a3 | |||
| 5a0255fd01 | |||
| 5b86646bd8 | |||
| 067a501d98 | |||
| fa9908413b | |||
| d6369ea784 | |||
| e49c8588c6 | |||
| e504def9e4 | |||
| 3ea4e0bf7c | |||
| 4681c0b275 | |||
| 50acec575f | |||
| ffb9ab9019 | |||
| 1a195d151d | |||
| dd4f4c44c3 | |||
| c04993e49c | |||
| 464984a83c | |||
| 40ffb6b65d | |||
| bb263f8c5e | |||
| 82872ae02a | |||
| 6db396e877 | |||
| d8925fe234 |
@@ -0,0 +1,2 @@
|
||||
.tmp
|
||||
hashes.json
|
||||
@@ -0,0 +1,25 @@
|
||||
name: 'Nym Hash Release'
|
||||
author: 'Nym Technologies SA'
|
||||
description: 'Generate hashes and signatures for assets in Nym releases'
|
||||
inputs:
|
||||
hash-type:
|
||||
description: 'Type of hash to generate (md5, sha1, sha256, sha512)'
|
||||
required: false
|
||||
default: 'sha256'
|
||||
file-name:
|
||||
description: 'File name to save as if desired'
|
||||
required: false
|
||||
default: 'hashes.json'
|
||||
release-tag-or-name-or-id:
|
||||
description: 'The tag/release to process. Uses the release id when trigger from a release.'
|
||||
required: false
|
||||
default: ''
|
||||
outputs:
|
||||
hashes:
|
||||
description: 'A string containing JSON with the release asset hashes and signatures'
|
||||
runs:
|
||||
using: 'node16'
|
||||
main: 'index.js'
|
||||
branding:
|
||||
icon: 'hash'
|
||||
color: 'green'
|
||||
@@ -0,0 +1,259 @@
|
||||
import hasha from "hasha";
|
||||
import fetch from "node-fetch";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
async function run(assets, algorithm, filename, cache) {
|
||||
try {
|
||||
fs.mkdirSync('.tmp');
|
||||
} catch(e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const hashes = {};
|
||||
let numAwaiting = 0;
|
||||
for (const asset of assets) {
|
||||
if (filename === "" || asset.name !== filename) { // don't hash the hash file (if the file has the same name)
|
||||
numAwaiting++;
|
||||
|
||||
let buffer = null;
|
||||
let sig = null;
|
||||
if(cache) {
|
||||
// cache in `${WORKING_DIR}/.tmp/`
|
||||
const cacheFilename = path.resolve(`.tmp/${asset.name}`);
|
||||
if(!fs.existsSync(cacheFilename)) {
|
||||
console.log(`Downloading ${asset.browser_download_url}... to ${cacheFilename}`);
|
||||
buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer()));
|
||||
fs.writeFileSync(cacheFilename, buffer);
|
||||
} else {
|
||||
console.log(`Loading from ${cacheFilename}`);
|
||||
buffer = Buffer.from(fs.readFileSync(cacheFilename));
|
||||
|
||||
// console.log('Reading signature from content');
|
||||
// if(asset.name.endsWith('.sig')) {
|
||||
// sig = fs.readFileSync(cacheFilename).toString();
|
||||
// }
|
||||
}
|
||||
} else {
|
||||
// fetch always
|
||||
buffer = Buffer.from(await fetch(asset.browser_download_url).then(res => res.arrayBuffer()));
|
||||
}
|
||||
if(!hashes[asset.name]) {
|
||||
hashes[asset.name] = {};
|
||||
}
|
||||
|
||||
if(asset.name.endsWith('.sig')) {
|
||||
sig = buffer.toString();
|
||||
}
|
||||
|
||||
hashes[asset.name][algorithm] = hasha(new Uint8Array(buffer), {algorithm: algorithm});
|
||||
|
||||
let platform;
|
||||
let kind;
|
||||
if(asset.name.endsWith('.sig')) {
|
||||
kind = 'signature';
|
||||
}
|
||||
if(asset.name.endsWith('.app.tar.gz')) {
|
||||
platform = 'MacOS';
|
||||
kind = 'auto-updater';
|
||||
}
|
||||
if(asset.name.endsWith('.app.tar.gz.sig')) {
|
||||
platform = 'MacOS';
|
||||
kind = 'auto-updater-signature';
|
||||
}
|
||||
if(asset.name.endsWith('.dmg')) {
|
||||
platform = 'MacOS';
|
||||
kind = 'installer';
|
||||
}
|
||||
if(asset.name.endsWith('.msi.zip')) {
|
||||
platform = 'Windows';
|
||||
kind = 'auto-updater';
|
||||
}
|
||||
if(asset.name.endsWith('.msi.zip.sig')) {
|
||||
platform = 'Windows';
|
||||
kind = 'auto-updater-signature';
|
||||
}
|
||||
if(asset.name.endsWith('.msi')) {
|
||||
platform = 'Windows';
|
||||
kind = 'installer';
|
||||
}
|
||||
if(asset.name.endsWith('.AppImage.tar.gz')) {
|
||||
platform = 'Linux';
|
||||
kind = 'auto-updater';
|
||||
}
|
||||
if(asset.name.endsWith('.AppImage.tar.gz.sig')) {
|
||||
platform = 'Linux';
|
||||
kind = 'auto-updater-signature';
|
||||
}
|
||||
if(asset.name.endsWith('.AppImage')) {
|
||||
platform = 'Linux';
|
||||
kind = 'installer';
|
||||
}
|
||||
|
||||
hashes[asset.name].downloadUrl = asset.browser_download_url;
|
||||
|
||||
if(platform) {
|
||||
hashes[asset.name].platform = platform;
|
||||
}
|
||||
if(kind) {
|
||||
hashes[asset.name].kind = kind;
|
||||
}
|
||||
|
||||
// process Tauri signature files
|
||||
if(asset.name.endsWith('.sig')) {
|
||||
const otherFilename = asset.name.replace('.sig', '');
|
||||
if(!hashes[otherFilename]) {
|
||||
hashes[otherFilename] = {};
|
||||
}
|
||||
hashes[otherFilename].signature = sig;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
export async function createHashes({ assets, algorithm, filename, cache }) {
|
||||
const output = await run(assets, algorithm, filename, cache);
|
||||
if(filename?.length) {
|
||||
fs.writeFileSync(filename, JSON.stringify(output, null, 2));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export async function createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm = 'sha256', filename = 'hashes.json', cache = false, upload = true }) {
|
||||
console.log("🚀🚀🚀 Getting releases");
|
||||
|
||||
let auth;
|
||||
let authStrategy;
|
||||
if(process.env.GITHUB_TOKEN) {
|
||||
console.log('Using GITHUB_TOKEN for auth');
|
||||
// authStrategy = createActionAuth();
|
||||
// auth = await authStrategy();
|
||||
}
|
||||
|
||||
const octokit = new Octokit({
|
||||
auth: process.env.GITHUB_TOKEN,
|
||||
request: { fetch }
|
||||
});
|
||||
const owner = "nymtech";
|
||||
const repo = "nym";
|
||||
|
||||
let releases;
|
||||
if(cache) {
|
||||
const cacheFilename = path.resolve(`.tmp/releases.json`);
|
||||
if(!fs.existsSync(cacheFilename)) {
|
||||
releases = await octokit.paginate(
|
||||
octokit.rest.repos.listReleases,
|
||||
{
|
||||
owner,
|
||||
repo,
|
||||
per_page: 100,
|
||||
},
|
||||
(response) => response.data
|
||||
);
|
||||
fs.writeFileSync(cacheFilename, JSON.stringify(releases, null, 2));
|
||||
} else {
|
||||
console.log('Loading releases from cache...');
|
||||
releases = JSON.parse(fs.readFileSync(cacheFilename));
|
||||
}
|
||||
} else {
|
||||
releases = await octokit.paginate(
|
||||
octokit.rest.repos.listReleases,
|
||||
{
|
||||
owner,
|
||||
repo,
|
||||
per_page: 100,
|
||||
},
|
||||
(response) => response.data
|
||||
)
|
||||
}
|
||||
|
||||
// process all releases by default
|
||||
let releasesToProcess = releases;
|
||||
|
||||
// process a single release
|
||||
if(releaseTagOrNameOrId) {
|
||||
releasesToProcess = releases.filter(r => {
|
||||
if (r.tag_name === releaseTagOrNameOrId) {
|
||||
return true;
|
||||
}
|
||||
if (`${r.id}` === `${releaseTagOrNameOrId}`) {
|
||||
return true;
|
||||
}
|
||||
if (r.name === releaseTagOrNameOrId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
releasesToProcess.forEach(release => {
|
||||
const {tag_name, name} = release;
|
||||
const tagComponents = tag_name.split('-v');
|
||||
const componentName = tagComponents[0];
|
||||
const componentVersion = 'v' + tagComponents[1];
|
||||
|
||||
if(!tagComponents[1] || !name) {
|
||||
return;
|
||||
}
|
||||
|
||||
release.componentName = componentName;
|
||||
release.componentVersion = componentVersion;
|
||||
})
|
||||
|
||||
releasesToProcess = releasesToProcess.filter(release =>
|
||||
!!release.name && !!release.componentVersion
|
||||
);
|
||||
|
||||
console.log('Releases to process:');
|
||||
console.table(releasesToProcess.map(r => {
|
||||
const { id, name, tag_name, componentName, componentVersion, assets } = r;
|
||||
return { id, name, tag_name, componentName, componentVersion, assetCount: assets.length };
|
||||
}));
|
||||
|
||||
for(const release of releasesToProcess) {
|
||||
const {id, name, tag_name, html_url, componentName, componentVersion} = release;
|
||||
|
||||
const hashes = await createHashes({ assets: release.assets, algorithm, filename, cache });
|
||||
|
||||
const output = {
|
||||
id, name, tag_name, html_url,
|
||||
componentName,
|
||||
componentVersion,
|
||||
assets: hashes,
|
||||
};
|
||||
|
||||
if(upload) {
|
||||
console.log(`🚚 Uploading ${filename} to release name="${release.name}" id=${release.id} (${release.upload_url})...`);
|
||||
|
||||
const exists = (await octokit.repos.listReleaseAssets({ owner, repo, release_id: release.id })).data.find(a => a.name === filename)
|
||||
if (exists) {
|
||||
console.log(`Deleting existing asset ${filename}...`);
|
||||
await octokit.repos.deleteReleaseAsset({ owner, repo, asset_id: exists.id })
|
||||
console.log('Deleted existing asset');
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.stringify(output, null, 2);
|
||||
await octokit.rest.repos.uploadReleaseAsset({
|
||||
owner,
|
||||
repo,
|
||||
release_id: release.id,
|
||||
headers: {
|
||||
'X-GitHub-Api-Version': '2022-11-28'
|
||||
},
|
||||
name: filename,
|
||||
data,
|
||||
});
|
||||
console.log('✅ Upload to release is complete.');
|
||||
} catch(e) {
|
||||
console.log('❌ failed to upload:', e.message, e.status, e.response.data);
|
||||
console.log(e);
|
||||
process.exit(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import core from "@actions/core";
|
||||
import github from "@actions/github";
|
||||
import { createHashesFromReleaseTagOrNameOrId } from './create-hashes.mjs';
|
||||
|
||||
const algorithm = core.getInput('hash-type');
|
||||
const filename = core.getInput("file-name");
|
||||
|
||||
// use the release id from the payload if it is set
|
||||
const releaseTagOrNameOrId = core.getInput("release-tag-or-name-or-id") || github.context.payload.release?.id;
|
||||
|
||||
try {
|
||||
await createHashesFromReleaseTagOrNameOrId({ releaseTagOrNameOrId, algorithm, filename })
|
||||
} catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
+536
@@ -0,0 +1,536 @@
|
||||
{
|
||||
"name": "ghaction-generate-release-hashes",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ghaction-generate-release-hashes",
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/auth-action": "^4.0.0",
|
||||
"@octokit/rest": "^20.0.1",
|
||||
"hasha": "^5.2.0",
|
||||
"node-fetch": "^3.2.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
||||
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz",
|
||||
"integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-action": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-action/-/auth-action-4.0.0.tgz",
|
||||
"integrity": "sha512-sMm9lWZdiX6e89YFaLrgE9EFs94k58BwIkvjOtozNWUqyTmsrnWFr/M5LolaRzZ7Kmb5FbhF9hi7FEeE274SoQ==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/types": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-action/node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-action/node_modules/@octokit/openapi-types": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
|
||||
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
|
||||
},
|
||||
"node_modules/@octokit/auth-action/node_modules/@octokit/types": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
|
||||
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
|
||||
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.6.3",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^5.6.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "12.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
|
||||
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "2.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
|
||||
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.40.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
|
||||
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.39.0",
|
||||
"deprecation": "^2.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
|
||||
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@octokit/types": "^6.16.1",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request/node_modules/node-fetch": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest": {
|
||||
"version": "20.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-20.0.1.tgz",
|
||||
"integrity": "sha512-wROV21RwHQIMNb2Dgd4+pY+dVy1Dwmp85pBrgr6YRRDYRBu9Gb+D73f4Bl2EukZSj5hInq2Tui9o7gAQpc2k2Q==",
|
||||
"dependencies": {
|
||||
"@octokit/core": "^5.0.0",
|
||||
"@octokit/plugin-paginate-rest": "^8.0.0",
|
||||
"@octokit/plugin-request-log": "^4.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/auth-token": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/core": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.0.tgz",
|
||||
"integrity": "sha512-YbAtMWIrbZ9FCXbLwT9wWB8TyLjq9mxpKdgB3dUNxQcIVTf9hJ70gRPwAcqGZdY6WdJPZ0I7jLaaNDCiloGN2A==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.0.0",
|
||||
"@octokit/request": "^8.0.2",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^11.0.0",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/endpoint": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.0.tgz",
|
||||
"integrity": "sha512-szrQhiqJ88gghWY2Htt8MqUDO6++E/EIXqJ2ZEp5ma3uGS46o7LZAzSLt49myB7rT+Hfw5Y6gO3LmOxGzHijAQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/graphql": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.1.tgz",
|
||||
"integrity": "sha512-T5S3oZ1JOE58gom6MIcrgwZXzTaxRnxBso58xhozxHpOqSTgDS6YNeEUvZ/kRvXgPrRz/KHnZhtb7jUMRi9E6w==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^8.0.1",
|
||||
"@octokit/types": "^11.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/openapi-types": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-18.0.0.tgz",
|
||||
"integrity": "sha512-V8GImKs3TeQRxRtXFpG2wl19V7444NIOTDF24AWuIbmNaNYOQMWRbjcGDXV5B+0n887fgDcuMNOmlul+k+oJtw=="
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-8.0.0.tgz",
|
||||
"integrity": "sha512-2xZ+baZWUg+qudVXnnvXz7qfrTmDeYPCzangBVq/1gXxii/OiS//4shJp9dnCCvj1x+JAm9ji1Egwm1BA47lPQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-request-log": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-4.0.0.tgz",
|
||||
"integrity": "sha512-2uJI1COtYCq8Z4yNSnM231TgH50bRkheQ9+aH8TnZanB6QilOnx8RMD2qsnamSOXtDj0ilxvevf5fGsBhBBzKA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-9.0.0.tgz",
|
||||
"integrity": "sha512-KquMF/VB1IkKNiVnzJKspY5mFgGyLd7HzdJfVEGTJFzqu9BRFNWt+nwTCMuUiWc72gLQhRWYubTwOkQj+w/1PA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/request": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.1.tgz",
|
||||
"integrity": "sha512-8N+tdUz4aCqQmXl8FpHYfKG9GelDFd7XGVzyN8rc6WxVlYcfpHECnuRkgquzz+WzvHTK62co5di8gSXnzASZPQ==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^9.0.0",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^11.1.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/request-error": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.0.tgz",
|
||||
"integrity": "sha512-1ue0DH0Lif5iEqT52+Rf/hf0RmGO9NWFjrzmrkArpG9trFfDM/efx00BJHdLGuro4BR/gECxCU2Twf5OKrRFsQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^11.0.0",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/rest/node_modules/@octokit/types": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-11.1.0.tgz",
|
||||
"integrity": "sha512-Fz0+7GyLm/bHt8fwEqgvRBWwIV1S6wRRyq+V6exRKLVWaKGsuy6H9QFYeBVDV7rK6fO3XwHgQOPxv+cLj2zpXQ==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "6.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
|
||||
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^12.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
|
||||
"integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ=="
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/deprecation": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
|
||||
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hasha": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
|
||||
"integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
|
||||
"dependencies": {
|
||||
"is-stream": "^2.0.0",
|
||||
"type-fest": "^0.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-plain-object": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz",
|
||||
"integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "nym-hash-release",
|
||||
"version": "1.0.0",
|
||||
"description": "Generate hashes and signatures for assets in Nym releases",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"local": "node run-local.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/github": "^5.1.1",
|
||||
"@octokit/auth-action": "^4.0.0",
|
||||
"@octokit/rest": "^20.0.1",
|
||||
"hasha": "^5.2.0",
|
||||
"node-fetch": "^3.2.10"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import {createHashesFromReleaseTagOrNameOrId} from './create-hashes.mjs';
|
||||
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 119065724, cache: true, upload: false});
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: '119065724', cache: true, upload: false});
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 'nym-connect-v1.1.19-snickers', cache: true, upload: false});
|
||||
await createHashesFromReleaseTagOrNameOrId({releaseTagOrNameOrId: 'Nym Connect v1.1.19-snickers', cache: true, upload: false});
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
run: cargo install --version 0.112.0 wasm-opt
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
run: make contracts-wasm
|
||||
|
||||
- name: Prepare build output
|
||||
shell: bash
|
||||
|
||||
@@ -38,6 +38,7 @@ jobs:
|
||||
- name: Build all projects in documentation/ & move to ~/dist/docs/
|
||||
run: cd documentation && ./build_all_to_dist.sh
|
||||
continue-on-error: false
|
||||
|
||||
- name: Deploy branch master to dev
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
@@ -49,6 +50,7 @@ jobs:
|
||||
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
|
||||
EXCLUDE: "/node_modules/"
|
||||
|
||||
- name: Deploy branch master to prod
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
@@ -60,6 +62,49 @@ jobs:
|
||||
REMOTE_USER: ${{ secrets.CD_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CD_WWW_REMOTE_TARGET }}/
|
||||
EXCLUDE: "/node_modules/"
|
||||
|
||||
- name: Post process
|
||||
run: cd documentation && ./post_process.sh
|
||||
continue-on-error: false
|
||||
|
||||
- name: Create Vercel project file
|
||||
uses: mobiledevops/secret-to-file-action@v1
|
||||
with:
|
||||
base64-encoded-secret: ${{ secrets.VERCEL_PROJECT_JSON_BASE64 }}
|
||||
filename: "project.json"
|
||||
is-executable: true
|
||||
working-directory: "./dist/docs/.vercel"
|
||||
|
||||
- name: Install Vercel CLI
|
||||
run: npm install --global vercel@latest
|
||||
|
||||
- name: Pull Vercel Environment Information (preview)
|
||||
if: github.ref != 'refs/heads/master'
|
||||
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
- name: Pull Vercel Environment Information (production)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
|
||||
- name: Build Project Artifacts (preview)
|
||||
if: github.ref != 'refs/heads/master'
|
||||
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
- name: Build Project Artifacts (production)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
|
||||
- name: Deploy Project Artifacts to Vercel (preview)
|
||||
if: github.ref != 'refs/heads/master'
|
||||
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
- name: Deploy Project Artifacts to Vercel (master)
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
|
||||
working-directory: dist/docs
|
||||
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
run: cargo install --version 0.112.0 wasm-opt
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
run: make contracts-wasm
|
||||
|
||||
- name: Upload Mixnet Contract Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
name: Nightly builds on latest release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '14 2 * * *'
|
||||
jobs:
|
||||
matrix_prep:
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
# creates the matrix strategy from nightly_build_matrix_includes.json
|
||||
- uses: actions/checkout@v3
|
||||
- id: set-matrix
|
||||
uses: JoshuaTheMiller/conditional-build-matrix@main
|
||||
with:
|
||||
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
|
||||
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
|
||||
get_release:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: matrix_prep
|
||||
outputs:
|
||||
output1: ${{ steps.step2.outputs.latest_release }}
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- name: Fetch all branches
|
||||
run: git fetch --all
|
||||
- name: Set output variable to latest release branch
|
||||
id: step2
|
||||
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
|
||||
build:
|
||||
needs: [get_release,matrix_prep]
|
||||
strategy:
|
||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||
continue-on-error: true
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
|
||||
- name: Check out latest release branch
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{needs.get_release.outputs.output1}}
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace
|
||||
|
||||
- name: Reclaim some disk space
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Build all examples
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --examples
|
||||
|
||||
- name: Reclaim some disk space
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace
|
||||
|
||||
- name: Reclaim some disk space
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run expensive tests
|
||||
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace -- --ignored
|
||||
|
||||
- name: Reclaim some disk space
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
continue-on-error: true
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
- name: Reclaim some disk space
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
# nym-wallet (the rust part)
|
||||
- name: Build nym-wallet rust code
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Run nym-wallet tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Check nym-wallet formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
|
||||
|
||||
- name: Run clippy for nym-wallet
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
|
||||
|
||||
notification:
|
||||
needs: [build,get_release]
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- name: Collect jobs status
|
||||
uses: technote-space/workflow-conclusion-action@v2
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- name: install npm
|
||||
uses: actions/setup-node@v3
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Matrix - Node Install
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nightly
|
||||
NYM_PROJECT_NAME: "Nym nightly build on latest release"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH_NAME: "${{needs.get_release.outputs.output1}}"
|
||||
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -1,191 +0,0 @@
|
||||
name: Nightly builds on second latest release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '24 2 * * *'
|
||||
jobs:
|
||||
matrix_prep:
|
||||
runs-on: ubuntu-20.04
|
||||
outputs:
|
||||
matrix: ${{ steps.set-matrix.outputs.matrix }}
|
||||
steps:
|
||||
# creates the matrix strategy from nightly_build_matrix_includes.json
|
||||
- uses: actions/checkout@v3
|
||||
- id: set-matrix
|
||||
uses: JoshuaTheMiller/conditional-build-matrix@main
|
||||
with:
|
||||
inputFile: '.github/workflows/nightly_build_matrix_includes.json'
|
||||
filter: '[?runOnEvent==`${{ github.event_name }}` || runOnEvent==`always`]'
|
||||
get_release:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: matrix_prep
|
||||
outputs:
|
||||
output1: ${{ steps.step2.outputs.latest_release }}
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- name: Fetch all branches
|
||||
run: git fetch --all
|
||||
- name: Set output variable to latest release branch
|
||||
id: step2
|
||||
run: echo "latest_release=$(git branch -r | grep -E 'release/v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 2 | head -n 1 | sed 's/ origin\///')" >> $GITHUB_OUTPUT
|
||||
build:
|
||||
needs: [get_release,matrix_prep]
|
||||
strategy:
|
||||
matrix: ${{fromJson(needs.matrix_prep.outputs.matrix)}}
|
||||
runs-on: ${{ matrix.os }}
|
||||
continue-on-error: ${{ matrix.rust == 'nightly' || matrix.rust == 'beta' || matrix.rust == 'stable' }}
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools
|
||||
continue-on-error: true
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
|
||||
- name: Check out latest release branch
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{needs.get_release.outputs.output1}}
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: ${{ matrix.rust }}
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Build all examples
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --examples
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- name: Run expensive tests
|
||||
if: github.ref == 'refs/heads/develop' || github.event.pull_request.base.ref == 'develop' || github.event.pull_request.base.ref == 'master'
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace -- --ignored
|
||||
|
||||
- name: Reclaim some disk space (because Windows is being annoying)
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
continue-on-error: true
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets -- -D warnings
|
||||
|
||||
- name: Reclaim some disk space
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.os == 'windows-latest' || matrix.os == 'ubuntu-20.04' }}
|
||||
with:
|
||||
command: clean
|
||||
|
||||
# nym-wallet (the rust part)
|
||||
- name: Build nym-wallet rust code
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Run nym-wallet tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Check nym-wallet formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
|
||||
|
||||
- name: Run clippy for nym-wallet
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
|
||||
|
||||
notification:
|
||||
needs: [build,get_release]
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- name: Collect jobs status
|
||||
uses: technote-space/workflow-conclusion-action@v2
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- name: install npm
|
||||
uses: actions/setup-node@v3
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Matrix - Node Install
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
if: env.WORKFLOW_CONCLUSION == 'failure'
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: nightly
|
||||
NYM_PROJECT_NAME: "Nym nightly build on latest release"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH_NAME: "${{needs.get_release.outputs.output1}}"
|
||||
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
|
||||
MATRIX_SERVER: "${{ secrets.MATRIX_SERVER }}"
|
||||
MATRIX_ROOM: "${{ secrets.MATRIX_ROOM_NIGHTLY }}"
|
||||
MATRIX_USER_ID: "${{ secrets.MATRIX_USER_ID }}"
|
||||
MATRIX_TOKEN: "${{ secrets.MATRIX_TOKEN }}"
|
||||
MATRIX_DEVICE_ID: "${{ secrets.MATRIX_DEVICE_ID }}"
|
||||
uses: docker://keybaseio/client:stable-node
|
||||
with:
|
||||
args: .github/workflows/support-files/notifications/entry_point.sh
|
||||
@@ -112,31 +112,11 @@ jobs:
|
||||
files: |
|
||||
nym-connect/desktop/target/release/bundle/dmg/*.dmg
|
||||
nym-connect/desktop/target/release/bundle/macos/*.app.tar.gz*
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${semver}_x64.dmg " >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/dmg/nym-connect_*_x64.dmg') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect.app.tar.gz.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: NymConnect
|
||||
category: connect
|
||||
platform: MacOS
|
||||
secrets: inherit
|
||||
|
||||
@@ -79,31 +79,11 @@ jobs:
|
||||
files: |
|
||||
nym-connect/desktop/target/release/bundle/appimage/*.AppImage
|
||||
nym-connect/desktop/target/release/bundle/appimage/*.AppImage.tar.gz*
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${semver}_amd64.AppImage" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/appimage/nym-connect_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: NymConnect
|
||||
category: connect
|
||||
platform: Ubuntu
|
||||
secrets: inherit
|
||||
|
||||
@@ -98,32 +98,11 @@ jobs:
|
||||
files: |
|
||||
nym-connect/desktop/target/release/bundle/msi/*.msi
|
||||
nym-connect/desktop/target/release/bundle/msi/*.msi.zip*
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
shell: bash
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-connect-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-connect_${semver}_x64_en-US.msi" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-connect/desktop/target/release/bundle/msi/nym-connect_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-connect-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-connect/desktop/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-connect_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: NymConnect
|
||||
category: connect
|
||||
platform: Windows
|
||||
secrets: inherit
|
||||
|
||||
@@ -96,149 +96,10 @@ jobs:
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
semver="${${{ github.ref_name }}##nym-binaries-}" && semver="${semver##v}"
|
||||
echo "version=$semver" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- id: binary-hashes
|
||||
name: Generate binary hashes
|
||||
run: |
|
||||
echo "client_hash=${{ hashFiles('target/release/nym-client') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "mixnode_hash=${{ hashFiles('target/release/nym-mixnode') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "gateway_hash=${{ hashFiles('target/release/nym-gateway') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "socks5_hash=${{ hashFiles('target/release/nym-socks5-client') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "netreq_hash=${{ hashFiles('target/release/nym-network-requester') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "cli_hash=${{ hashFiles('target/release/nym-cli') }}" >> "$GITHUB_OUTPUT"
|
||||
echo "netstat_hash=${{ hashFiles('target/release/nym-network-statistics') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- id: binary-versions
|
||||
name: Get binary versions
|
||||
run: |
|
||||
v=$(rg '^version = "(.*)"' -or '$1' clients/native/Cargo.toml) && echo "client_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' mixnode/Cargo.toml) && echo "mixnode_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' gateway/Cargo.toml) && echo "gateway_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' clients/socks5/Cargo.toml) && echo "socks5_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-requester/Cargo.toml) && echo "netreq_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' tools/nym-cli/Cargo.toml) && echo "cli_version=$v" >> "$GITHUB_OUTPUT"
|
||||
v=$(rg '^version = "(.*)"' -or '$1' service-providers/network-statistics/Cargo.toml) && echo "netstat_version=$v" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data-client:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.client_version }}
|
||||
filename: nym-client
|
||||
file_hash: ${{ needs.publish-nym.outputs.client_hash }}
|
||||
name: Client
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-mixnode:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.mixnode_version }}
|
||||
filename: nym-mixnode
|
||||
file_hash: ${{ needs.publish-nym.outputs.mixnode_hash }}
|
||||
name: Mixnode
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-gateway:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.gateway_version }}
|
||||
filename: nym-gateway
|
||||
file_hash: ${{ needs.publish-nym.outputs.gateway_hash }}
|
||||
name: Gateway
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-socks5:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.socks5_version }}
|
||||
filename: nym-socks5-client
|
||||
file_hash: ${{ needs.publish-nym.outputs.socks5_hash }}
|
||||
name: Socks5 Client
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-network-requester:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.netreq_version }}
|
||||
filename: nym-network-requester
|
||||
file_hash: ${{ needs.publish-nym.outputs.netreq_hash }}
|
||||
name: Network Requester
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-cli:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.cli_version }}
|
||||
filename: nym-cli
|
||||
file_hash: ${{ needs.publish-nym.outputs.cli_hash }}
|
||||
name: Cli
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
push-release-data-network-stat:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
needs: publish-nym
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-nym.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-nym.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/CHANGELOG.md
|
||||
version: ${{ needs.publish-nym.outputs.netstat_version }}
|
||||
filename: nym-network-statistics
|
||||
file_hash: ${{ needs.publish-nym.outputs.netstat_hash }}
|
||||
name: Network Statistics
|
||||
category: binaries
|
||||
secrets: inherit
|
||||
|
||||
@@ -100,31 +100,10 @@ jobs:
|
||||
nym-wallet/target/release/bundle/dmg/*.dmg
|
||||
nym-wallet/target/release/bundle/macos/*.app.tar.gz*
|
||||
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-wallet-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-wallet_${semver}_x64.dmg" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/dmg/nym-wallet_*_x64.dmg') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet.app.tar.gz.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: Wallet
|
||||
category: wallet
|
||||
platform: MacOS
|
||||
secrets: inherit
|
||||
|
||||
@@ -77,31 +77,10 @@ jobs:
|
||||
nym-wallet/target/release/bundle/appimage/*.AppImage
|
||||
nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz*
|
||||
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-wallet-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-wallet_${semver}_amd64.AppImage" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/appimage/nym-wallet_*_amd64.AppImage') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_amd64.AppImage.tar.gz.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: Wallet
|
||||
category: wallet
|
||||
platform: Ubuntu
|
||||
secrets: inherit
|
||||
|
||||
@@ -97,31 +97,10 @@ jobs:
|
||||
nym-wallet/target/release/bundle/msi/*.msi
|
||||
nym-wallet/target/release/bundle/msi/*.msi.zip*
|
||||
|
||||
- id: release-info
|
||||
name: Prepare release info
|
||||
run: |
|
||||
ref=${{ github.ref_name }}
|
||||
semver="${ref##nym-wallet-}" && semver="${semver##v}"
|
||||
echo "version=${semver}" >> "$GITHUB_OUTPUT"
|
||||
echo "filename=nym-wallet_${semver}_x64_en-US.msi" >> "$GITHUB_OUTPUT"
|
||||
echo "file_hash=${{ hashFiles('nym-wallet/target/release/bundle/msi/nym-wallet_*_x64_en-US.msi') }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
push-release-data:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-wallet-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
uses: ./.github/workflows/push-release-data.yml
|
||||
uses: ./.github/workflows/release-calculate-hash.yml
|
||||
needs: publish-tauri
|
||||
with:
|
||||
release_tag: ${{ github.ref_name }}
|
||||
release_id: ${{ needs.publish-tauri.outputs.release_id }}
|
||||
release_date: ${{ needs.publish-tauri.outputs.release_date }}
|
||||
download_base_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}
|
||||
changelog_url: https://github.com/nymtech/nym/blob/${{ github.ref_name }}/nym-wallet/CHANGELOG.md
|
||||
archive_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip
|
||||
sig_url: https://github.com/nymtech/nym/releases/download/${{ github.ref_name }}/nym-wallet_${{ needs.publish-tauri.outputs.version }}_x64_en-US.msi.zip.sig
|
||||
version: ${{ needs.publish-tauri.outputs.version }}
|
||||
filename: ${{ needs.publish-tauri.outputs.filename }}
|
||||
file_hash: ${{ needs.publish-tauri.outputs.file_hash }}
|
||||
name: Wallet
|
||||
category: wallet
|
||||
platform: Windows
|
||||
secrets: inherit
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
name: Push release data
|
||||
|
||||
env:
|
||||
strapi_download_url: 'https://strapi.feat-nym-update-nym-web.websites.dev.nymte.ch/api/downloaders'
|
||||
strapi_updater_url: 'https://strapi.feat-nym-update-nym-web.websites.dev.nymte.ch/api/updaters'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
required: true
|
||||
description: Release tag
|
||||
type: string
|
||||
release_id:
|
||||
required: true
|
||||
description: Release ID
|
||||
type: string
|
||||
release_date:
|
||||
required: true
|
||||
description: Release date
|
||||
type: string
|
||||
download_base_url:
|
||||
required: true
|
||||
description: Download base URL
|
||||
type: string
|
||||
changelog_url:
|
||||
required: true
|
||||
description: Changelog URL
|
||||
type: string
|
||||
archive_url:
|
||||
required: false
|
||||
description: Binary archive URL
|
||||
type: string
|
||||
sig_url:
|
||||
required: false
|
||||
description: Archive signature URL
|
||||
type: string
|
||||
version:
|
||||
required: true
|
||||
description: Release version (semver)
|
||||
type: string
|
||||
filename:
|
||||
required: true
|
||||
description: Binary file name
|
||||
type: string
|
||||
file_hash:
|
||||
required: true
|
||||
description: Binary hash (sha256)
|
||||
type: string
|
||||
name:
|
||||
required: true
|
||||
description: Name
|
||||
type: string
|
||||
category:
|
||||
required: true
|
||||
description: Category
|
||||
type: string
|
||||
platform:
|
||||
required: false
|
||||
description: Platform
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
# ⚠ since inputs are limited to 10 max for workflow_dispatch
|
||||
# some properties were omitted
|
||||
version:
|
||||
required: true
|
||||
description: Release version (semver)
|
||||
type: string
|
||||
default: '1.0.0'
|
||||
release_id:
|
||||
required: true
|
||||
description: Release ID
|
||||
type: string
|
||||
default: '1234'
|
||||
release_date:
|
||||
required: true
|
||||
description: Release date
|
||||
type: string
|
||||
default: '2023-06-26T10:09:16Z'
|
||||
download_base_url:
|
||||
required: true
|
||||
description: Download base URL
|
||||
type: string
|
||||
default: 'https://github.com/nymtech/nym/releases/download/nym-wallet-v1.0.0'
|
||||
changelog_url:
|
||||
required: true
|
||||
description: Changelog URL
|
||||
type: string
|
||||
default: 'https://github.com/nymtech/nym/blob/nym-wallet-v1.0.0/nym-wallet/CHANGELOG.md'
|
||||
filename:
|
||||
required: true
|
||||
description: Binary file name
|
||||
type: string
|
||||
default: 'nym-wallet_1.0.0_amd64.AppImage'
|
||||
file_hash:
|
||||
required: true
|
||||
description: Binary hash (sha256)
|
||||
type: string
|
||||
default: 'xxx'
|
||||
name:
|
||||
required: true
|
||||
description: Name
|
||||
type: string
|
||||
default: 'Wallet'
|
||||
category:
|
||||
required: true
|
||||
description: Category
|
||||
default: 'wallet'
|
||||
type: choice
|
||||
options:
|
||||
- wallet
|
||||
- connect
|
||||
- binaries
|
||||
platform:
|
||||
required: false
|
||||
description: Platform
|
||||
default: 'Ubuntu'
|
||||
type: choice
|
||||
options:
|
||||
- Ubuntu
|
||||
- Windows
|
||||
- MacOS
|
||||
|
||||
jobs:
|
||||
push-download-data:
|
||||
name: Push download data to Strapi
|
||||
runs-on: custom-runner-linux
|
||||
|
||||
steps:
|
||||
- name: Release info
|
||||
run: |
|
||||
echo "version: ${{ inputs.version }}"
|
||||
echo "tag: ${{ inputs.release_tag }}"
|
||||
|
||||
- id: get_sig
|
||||
name: Get sig
|
||||
if: ${{ inputs.sig_url != null }}
|
||||
run: |
|
||||
output=$(curl -LsSf ${{ inputs.sig_url }})
|
||||
echo "sig=$output" >> "$GITHUB_OUTPUT"
|
||||
- id: strapi-request
|
||||
name: Strapi request
|
||||
uses: fjogeleit/http-request-action@v1
|
||||
with:
|
||||
url: ${{ env.strapi_download_url }}
|
||||
method: 'POST'
|
||||
bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }}
|
||||
customHeaders: '{"Content-Type": "application/json"}'
|
||||
data: |
|
||||
{
|
||||
"data": {
|
||||
"releaseId": "${{ inputs.release_id }}",
|
||||
"releaseDate": "${{ inputs.release_date }}",
|
||||
"downloadBaseUrl": "${{ inputs.download_base_url }}",
|
||||
"changelogUrl": "${{ inputs.changelog_url }}",
|
||||
"version": "${{ inputs.version }}",
|
||||
"filename": "${{ inputs.filename }}",
|
||||
"name": "${{ inputs.name }}",
|
||||
"category": "${{ inputs.category }}",
|
||||
"platform": "${{ inputs.platform }}",
|
||||
"sha256": "${{ inputs.file_hash }}",
|
||||
"sig": "${{ steps.get_sig.outputs.sig }}"
|
||||
}
|
||||
}
|
||||
- name: Strapi Response
|
||||
run: |
|
||||
echo ${{ steps.strapi-request.outputs.response }}
|
||||
|
||||
push-update-data:
|
||||
name: Push update data to Strapi
|
||||
runs-on: custom-runner-linux
|
||||
# only push update data for tauri apps (desktop wallet and NC)
|
||||
if: ${{ inputs.category == 'wallet' || inputs.category == 'connect' }}
|
||||
|
||||
steps:
|
||||
- name: Release info
|
||||
run: |
|
||||
echo "version: ${{ inputs.version }}"
|
||||
echo "tag: ${{ inputs.release_tag }}"
|
||||
- id: get_sig
|
||||
name: Get sig
|
||||
if: ${{ inputs.sig_url != null }}
|
||||
run: |
|
||||
output=$(curl -LsSf ${{ inputs.sig_url }})
|
||||
echo "sig=$output" >> "$GITHUB_OUTPUT"
|
||||
- id: strapi-request
|
||||
name: Strapi request
|
||||
uses: fjogeleit/http-request-action@v1
|
||||
with:
|
||||
url: ${{ env.strapi_updater_url }}
|
||||
method: 'POST'
|
||||
bearerToken: ${{ secrets.STRAPI_API_TOKEN_RELEASES }}
|
||||
customHeaders: '{"Content-Type": "application/json"}'
|
||||
data: |
|
||||
{
|
||||
"data": {
|
||||
"releaseId": "${{ inputs.release_id }}",
|
||||
"releaseDate": "${{ inputs.release_date }}",
|
||||
"downloadUrl": "${{ inputs.archive_url }}",
|
||||
"changelog": "See ${{ inputs.changelog_url }} for the changelog",
|
||||
"version": "${{ inputs.version }}",
|
||||
"filename": "${{ inputs.filename }}",
|
||||
"category": "${{ inputs.category }}",
|
||||
"platform": "${{ inputs.platform }}",
|
||||
"sha256": "${{ inputs.file_hash }}",
|
||||
"sig": "${{ steps.get_sig.outputs.sig }}"
|
||||
}
|
||||
}
|
||||
- name: Strapi Response
|
||||
run: |
|
||||
echo ${{ steps.strapi-request.outputs.response }}
|
||||
@@ -0,0 +1,39 @@
|
||||
name: Releases - calculate file hashes
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
release_tag:
|
||||
description: 'Release tag'
|
||||
required: true
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
release_tag:
|
||||
tag:
|
||||
description: 'Release tag'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Calculate hash for assets in release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Install packages
|
||||
run: cd ./.github/actions/nym-hash-releases && npm i
|
||||
|
||||
- uses: ./.github/actions/nym-hash-releases
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
release-tag-or-name-or-id: ${{ inputs.release_tag }}
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: Asset Hashes
|
||||
path: hashes.json
|
||||
@@ -4,9 +4,11 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- "sdk/typescript/**"
|
||||
- "wasm/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- "sdk/typescript/**"
|
||||
- "wasm/**"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -26,17 +28,39 @@ jobs:
|
||||
toolchain: stable
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Build branch WASM packages
|
||||
run: make sdk-wasm-build
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
- name: Build
|
||||
run: yarn docs:prod:build
|
||||
- name: Deploy branch to CI www (docs)
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "dist/ts/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
- name: Matrix - Send Notification
|
||||
env:
|
||||
NYM_NOTIFICATION_KIND: ts-packages
|
||||
NYM_PROJECT_NAME: "ts-packages"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "ts-${{ env.GITHUB_REF_SLUG }}"
|
||||
NYM_PROJECT_NAME: "sdk-ts-docs"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}/docs/sdk/typescript"
|
||||
NYM_CI_WWW_LOCATION: "sdk-ts-docs-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
IS_SUCCESS: "${{ job.status == 'success' }}"
|
||||
|
||||
@@ -25,27 +25,37 @@ jobs:
|
||||
runs-on: custom-runner-linux
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install rsync
|
||||
run: sudo apt-get install rsync
|
||||
continue-on-error: true
|
||||
- uses: rlespinasse/github-slug-action@v3.x
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
- name: Setup yarn
|
||||
run: npm install -g yarn
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
- name: Install
|
||||
run: yarn
|
||||
|
||||
- name: Build packages
|
||||
run: yarn build
|
||||
run: yarn build:ci:sdk
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint && yarn tsc
|
||||
run: yarn lint
|
||||
- name: Typecheck with tsc
|
||||
run: yarn tsc
|
||||
|
||||
- name: Matrix - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
name: Wasm Client
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'clients/webassembly/**'
|
||||
- 'clients/client-core/**'
|
||||
- 'common/**'
|
||||
- 'contracts/**'
|
||||
- 'gateway/gateway-requests/**'
|
||||
- 'nym-api/nym-api-requests/**'
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path clients/webassembly/Cargo.toml -- --check
|
||||
|
||||
- uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown -- -D warnings
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Wasm Client
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'wasm/**'
|
||||
- 'clients/client-core/**'
|
||||
- 'common/**'
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
target: wasm32-unknown-unknown
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20'
|
||||
|
||||
|
||||
- name: Install wasm-pack
|
||||
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
- name: Install wasm-opt
|
||||
run: cargo install wasm-opt
|
||||
|
||||
- name: Install wasm-bindgen-cli
|
||||
run: cargo install wasm-bindgen-cli
|
||||
|
||||
- name: "Build"
|
||||
run: make sdk-wasm-build
|
||||
|
||||
- name: "Test"
|
||||
run: make sdk-wasm-test
|
||||
|
||||
- name: "Lint"
|
||||
run: make sdk-wasm-lint
|
||||
+3
-1
@@ -43,4 +43,6 @@ envs/qwerty.env
|
||||
.parcel-cache
|
||||
**/.DS_Store
|
||||
cpu-cycles/libcpucycles/build
|
||||
foxyfox.env
|
||||
foxyfox.env
|
||||
|
||||
.next
|
||||
+52
-2
@@ -4,7 +4,57 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.1.28] (2023-08-22)
|
||||
## [v1.1.31-kitkat] (2023-09-12)
|
||||
|
||||
- feat: add name to `TaskClient` ([#3844])
|
||||
- added 'open_proxy', 'enabled_statistics' and 'statistics_recipient' to NR config ([#3839])
|
||||
- MixFetch: initial prototype for insecure HTTP ([#3645])
|
||||
- MixFetch: prototype implementing TLS in WASM for HTTPS ([#3644])
|
||||
- SDK: build package for NodeJS ([#3558])
|
||||
- [Issue] There is already an open connection to this client ([#2845])
|
||||
|
||||
[#3844]: https://github.com/nymtech/nym/pull/3844
|
||||
[#3839]: https://github.com/nymtech/nym/pull/3839
|
||||
[#3645]: https://github.com/nymtech/nym/issues/3645
|
||||
[#3644]: https://github.com/nymtech/nym/issues/3644
|
||||
[#3558]: https://github.com/nymtech/nym/issues/3558
|
||||
[#2845]: https://github.com/nymtech/nym/issues/2845
|
||||
|
||||
## [v1.1.30-twix] (2023-09-05)
|
||||
|
||||
- geo_aware_provider: fix too much filtering of gateways ([#3826])
|
||||
- network-requester: add description to config ([#3799])
|
||||
- Speedy mode - selects gateway based on latency in medium / speedy mode ([#3770])
|
||||
- Chore/enable versioning ([#3768])
|
||||
- Create explorer-client and use in geo aware provider ([#3824])
|
||||
|
||||
[#3826]: https://github.com/nymtech/nym/pull/3826
|
||||
[#3799]: https://github.com/nymtech/nym/pull/3799
|
||||
[#3770]: https://github.com/nymtech/nym/issues/3770
|
||||
[#3768]: https://github.com/nymtech/nym/pull/3768
|
||||
[#3824]: https://github.com/nymtech/nym/pull/3824
|
||||
|
||||
## [v1.1.29-snickers] (2023-08-29)
|
||||
|
||||
- Add EXPLORER_API configurable url ([#3810])
|
||||
- Bugfix/use correct tendermint dialect ([#3802])
|
||||
- Explorer - look up gateways based on geo-location ([#3776])
|
||||
- Speedy mode - select the mixnodes based on the location of the NR ([#3775])
|
||||
- NR - reduce response time by removing poisson delay ([#3774])
|
||||
- [demo] libp2p example with nym-sdk ([#3763])
|
||||
- introduced /network/details endpoint to nym-api to return used network information ([#3758])
|
||||
- Feature/issue credentials ([#3691])
|
||||
|
||||
[#3810]: https://github.com/nymtech/nym/pull/3810
|
||||
[#3802]: https://github.com/nymtech/nym/pull/3802
|
||||
[#3776]: https://github.com/nymtech/nym/issues/3776
|
||||
[#3775]: https://github.com/nymtech/nym/issues/3775
|
||||
[#3774]: https://github.com/nymtech/nym/issues/3774
|
||||
[#3763]: https://github.com/nymtech/nym/pull/3763
|
||||
[#3758]: https://github.com/nymtech/nym/pull/3758
|
||||
[#3691]: https://github.com/nymtech/nym/pull/3691
|
||||
|
||||
## [v1.1.28] (2023-08-22)
|
||||
|
||||
- [final step3]: add [rust] support to nyxd client in wasm ([#3743])
|
||||
- Feature/ephemera upgrade ([#3791])
|
||||
@@ -19,7 +69,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
[#3767]: https://github.com/nymtech/nym/pull/3767
|
||||
|
||||
|
||||
## [1.1.27] (2023-08-16)
|
||||
## [v1.1.27] (2023-08-16)
|
||||
|
||||
- fix serialisation of contract types ([#3752])
|
||||
- Investigate spending credentials from the main API (coconut enabled to a gateway) from feature/ephemera branch ([#3741])
|
||||
|
||||
Generated
+846
-87
File diff suppressed because it is too large
Load Diff
+23
-4
@@ -46,6 +46,7 @@ members = [
|
||||
"common/crypto",
|
||||
"common/dkg",
|
||||
"common/execute",
|
||||
"common/http-requests",
|
||||
"common/inclusion-probability",
|
||||
"common/ledger",
|
||||
"common/mixnode-common",
|
||||
@@ -73,9 +74,13 @@ members = [
|
||||
"common/task",
|
||||
"common/topology",
|
||||
"common/types",
|
||||
"common/wasm-utils",
|
||||
"common/wasm/client-core",
|
||||
"common/wasm/storage",
|
||||
"common/wasm/utils",
|
||||
"common/wireguard",
|
||||
"explorer-api",
|
||||
"explorer-api/explorer-api-requests",
|
||||
"explorer-api/explorer-client",
|
||||
"gateway",
|
||||
"gateway/gateway-requests",
|
||||
"integrations/bity",
|
||||
@@ -86,11 +91,18 @@ members = [
|
||||
"service-providers/network-requester",
|
||||
"service-providers/network-statistics",
|
||||
"nym-api",
|
||||
"nym-browser-extension/storage",
|
||||
"nym-api/nym-api-requests",
|
||||
"nym-outfox",
|
||||
"tools/internal/ssl-inject",
|
||||
"tools/internal/sdk-version-bump",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-nr-query",
|
||||
"tools/ts-rs-cli"
|
||||
"tools/ts-rs-cli",
|
||||
"wasm/client",
|
||||
"wasm/full-nym-wasm",
|
||||
"wasm/mix-fetch",
|
||||
"wasm/node-tester",
|
||||
]
|
||||
|
||||
default-members = [
|
||||
@@ -104,7 +116,7 @@ default-members = [
|
||||
"explorer-api",
|
||||
]
|
||||
|
||||
exclude = ["explorer", "contracts", "clients/webassembly", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "nym-browser-extension/storage", "cpu-cycles"]
|
||||
exclude = ["explorer", "contracts", "nym-wallet", "nym-connect/mobile/src-tauri", "nym-connect/desktop", "cpu-cycles"]
|
||||
|
||||
[workspace.package]
|
||||
authors = ["Nym Technologies SA"]
|
||||
@@ -116,7 +128,7 @@ license = "Apache-2.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.71"
|
||||
async-trait = "0.1.64"
|
||||
async-trait = "0.1.68"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
cfg-if = "1.0.0"
|
||||
cosmwasm-derive = "=1.3.0"
|
||||
@@ -155,4 +167,11 @@ url = "2.4"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
# wasm-related dependencies
|
||||
gloo-utils = "0.1.7"
|
||||
js-sys = "0.3.63"
|
||||
serde-wasm-bindgen = "0.5.0"
|
||||
tsify = "0.4.5"
|
||||
wasm-bindgen = "0.2.86"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
wasmtimer = "0.2.0"
|
||||
web-sys = "0.3.63"
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
# Default target
|
||||
all: test
|
||||
|
||||
test: clippy-all cargo-test wasm fmt
|
||||
test: clippy-all cargo-test contracts-wasm sdk-wasm-test fmt
|
||||
|
||||
test-all: test cargo-test-expensive
|
||||
|
||||
no-clippy: build cargo-test wasm fmt
|
||||
no-clippy: build cargo-test contracts-wasm fmt fmt-browser-extension-storage
|
||||
|
||||
happy: fmt clippy-happy test
|
||||
|
||||
build: sdk-wasm-build build-browser-extension-storage
|
||||
|
||||
# Building release binaries is a little manual as we can't just build --release
|
||||
# on all workspaces.
|
||||
build-release: build-release-main wasm
|
||||
build-release: build-release-main contracts-wasm
|
||||
|
||||
clippy: sdk-wasm-lint clippy-browser-extension-storage
|
||||
|
||||
# Deprecated
|
||||
# For backwards compatibility
|
||||
@@ -43,6 +47,9 @@ test-$(1):
|
||||
test-expensive-$(1):
|
||||
cargo test --manifest-path $(2)/Cargo.toml --workspace -- --ignored
|
||||
|
||||
build-standalone-$(1):
|
||||
cargo build --manifest-path $(2)/Cargo.toml $(3)
|
||||
|
||||
build-$(1):
|
||||
cargo build --manifest-path $(2)/Cargo.toml --workspace $(3)
|
||||
|
||||
@@ -74,7 +81,7 @@ endef
|
||||
|
||||
$(eval $(call add_cargo_workspace,main,.))
|
||||
$(eval $(call add_cargo_workspace,contracts,contracts,--lib --target wasm32-unknown-unknown))
|
||||
$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
|
||||
#$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
|
||||
$(eval $(call add_cargo_workspace,wallet,nym-wallet,))
|
||||
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
|
||||
|
||||
@@ -88,6 +95,67 @@ build-explorer-api:
|
||||
build-nym-cli:
|
||||
cargo build -p nym-cli --release
|
||||
|
||||
build-browser-extension-storage:
|
||||
cargo build -p extension-storage --target wasm32-unknown-unknown
|
||||
|
||||
fmt-browser-extension-storage:
|
||||
cargo fmt -p extension-storage -- --check
|
||||
|
||||
clippy-browser-extension-storage:
|
||||
cargo clippy -p extension-storage --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
sdk-wasm: sdk-wasm-build sdk-wasm-test sdk-wasm-lint
|
||||
|
||||
sdk-wasm-build:
|
||||
# browser storage
|
||||
$(MAKE) -C nym-browser-extension/storage wasm-pack
|
||||
|
||||
# client
|
||||
$(MAKE) -C wasm/client build
|
||||
|
||||
# node-tester
|
||||
$(MAKE) -C wasm/node-tester build
|
||||
|
||||
# mix-fetch
|
||||
$(MAKE) -C wasm/mix-fetch build
|
||||
|
||||
# full
|
||||
$(MAKE) -C wasm/full-nym-wasm build-full
|
||||
|
||||
# run this from npm/yarn to ensure tools are in the path, e.g. yarn build:sdk from root of repo
|
||||
sdk-typescript-build:
|
||||
lerna run --scope @nymproject/sdk build --stream
|
||||
lerna run --scope @nymproject/mix-fetch build --stream
|
||||
lerna run --scope @nymproject/node-tester build --stream
|
||||
|
||||
sdk-wasm-test:
|
||||
# # client
|
||||
# cargo test -p nym-client-wasm --target wasm32-unknown-unknown
|
||||
#
|
||||
# # node-tester
|
||||
# cargo test -p nym-node-tester-wasm --target wasm32-unknown-unknown
|
||||
#
|
||||
# # mix-fetch
|
||||
# #cargo test -p nym-wasm-sdk --target wasm32-unknown-unknown
|
||||
#
|
||||
# # full
|
||||
# cargo test -p nym-wasm-sdk --target wasm32-unknown-unknown
|
||||
|
||||
|
||||
sdk-wasm-lint:
|
||||
# client
|
||||
cargo clippy -p nym-client-wasm --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
# node-tester
|
||||
cargo clippy -p nym-node-tester-wasm --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
# mix-fetch
|
||||
$(MAKE) -C wasm/mix-fetch check-fmt
|
||||
|
||||
# full
|
||||
cargo clippy -p nym-wasm-sdk --target wasm32-unknown-unknown -- -Dwarnings
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Build contracts ready for deploy
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -98,12 +166,12 @@ MIXNET_CONTRACT=$(CONTRACTS_OUT_DIR)/mixnet_contract.wasm
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_service_provider_directory.wasm
|
||||
NAME_SERVICE_CONTRACT=$(CONTRACTS_OUT_DIR)/nym_name_service.wasm
|
||||
|
||||
wasm: wasm-build wasm-opt
|
||||
contracts-wasm: contracts-wasm-build contracts-wasm-opt
|
||||
|
||||
wasm-build:
|
||||
contracts-wasm-build:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --lib --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
|
||||
|
||||
wasm-opt:
|
||||
contracts-wasm-opt:
|
||||
wasm-opt --disable-sign-ext -Os $(VESTING_CONTRACT) -o $(VESTING_CONTRACT)
|
||||
wasm-opt --disable-sign-ext -Os $(MIXNET_CONTRACT) -o $(MIXNET_CONTRACT)
|
||||
wasm-opt --disable-sign-ext -Os $(SERVICE_PROVIDER_DIRECTORY_CONTRACT) -o $(SERVICE_PROVIDER_DIRECTORY_CONTRACT)
|
||||
@@ -117,7 +185,7 @@ contract-schema:
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# NOTE: this seems deprecated an not needed anymore?
|
||||
mixnet-opt: wasm
|
||||
mixnet-opt: contracts-wasm
|
||||
cd contracts/mixnet && make opt
|
||||
|
||||
generate-typescript:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.26"
|
||||
version = "1.1.29"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::persistence::ClientPaths;
|
||||
use crate::client::config::{default_config_filepath, Config, Socket, SocketType};
|
||||
use crate::{
|
||||
client::config::{
|
||||
default_config_filepath, persistence::ClientPaths, Config, Socket, SocketType,
|
||||
},
|
||||
error::ClientError,
|
||||
};
|
||||
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
|
||||
use nym_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as BaseConfigV1_1_20_2;
|
||||
@@ -43,18 +48,18 @@ impl ConfigV1_1_20_2 {
|
||||
|
||||
// in this upgrade, gateway endpoint configuration was moved out of the config file,
|
||||
// so its returned to be stored elsewhere.
|
||||
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
|
||||
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), ClientError> {
|
||||
let gateway_details = self.base.client.gateway_endpoint.clone().into();
|
||||
let config = Config {
|
||||
base: self.base.into(),
|
||||
socket: self.socket.into(),
|
||||
storage_paths: ClientPaths {
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default(),
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
logging: self.logging,
|
||||
};
|
||||
|
||||
(config, gateway_details)
|
||||
Ok((config, gateway_details))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskManager;
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
use tokio::sync::watch::error::SendError;
|
||||
|
||||
pub use nym_sphinx::addressing::clients::Recipient;
|
||||
@@ -34,11 +35,17 @@ pub struct SocketClient {
|
||||
/// Client configuration options, including, among other things, packet sending rates,
|
||||
/// key filepaths, etc.
|
||||
config: Config,
|
||||
|
||||
/// Optional path to a .json file containing standalone network details.
|
||||
custom_mixnet: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl SocketClient {
|
||||
pub fn new(config: Config) -> Self {
|
||||
SocketClient { config }
|
||||
pub fn new(config: Config, custom_mixnet: Option<PathBuf>) -> Self {
|
||||
SocketClient {
|
||||
config,
|
||||
custom_mixnet,
|
||||
}
|
||||
}
|
||||
|
||||
fn start_websocket_listener(
|
||||
@@ -109,7 +116,11 @@ impl SocketClient {
|
||||
|
||||
let storage = self.initialise_storage().await?;
|
||||
|
||||
let base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
|
||||
let mut base_client = BaseClientBuilder::new(&self.config.base, storage, dkg_query_client);
|
||||
|
||||
if let Some(custom_mixnet) = &self.custom_mixnet {
|
||||
base_client = base_client.with_stored_topology(custom_mixnet)?;
|
||||
}
|
||||
|
||||
Ok(base_client)
|
||||
}
|
||||
|
||||
@@ -15,12 +15,15 @@ use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::GatewaySetup;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::NymTopology;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, io};
|
||||
use tap::TapFallible;
|
||||
|
||||
@@ -49,7 +52,12 @@ pub(crate) struct Init {
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
#[clap(long, alias = "api_validators", value_delimiter = ',')]
|
||||
#[clap(
|
||||
long,
|
||||
alias = "api_validators",
|
||||
value_delimiter = ',',
|
||||
group = "network"
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
@@ -65,6 +73,10 @@ pub(crate) struct Init {
|
||||
#[clap(long)]
|
||||
host: Option<IpAddr>,
|
||||
|
||||
/// Path to .json file containing custom network specification.
|
||||
#[clap(long, group = "network", hide = true)]
|
||||
custom_mixnet: Option<PathBuf>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hide = true)]
|
||||
@@ -130,7 +142,7 @@ fn init_paths(id: &str) -> io::Result<()> {
|
||||
fs::create_dir_all(default_config_directory(id))
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
pub(crate) async fn execute(args: Init) -> Result<(), ClientError> {
|
||||
eprintln!("Initialising client...");
|
||||
|
||||
let id = &args.id;
|
||||
@@ -173,12 +185,25 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
|
||||
let details_store =
|
||||
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
|
||||
let init_details = nym_client_core::init::setup_gateway(
|
||||
|
||||
let network_gateways = if let Some(hardcoded_topology) = args
|
||||
.custom_mixnet
|
||||
.map(NymTopology::new_from_file)
|
||||
.transpose()?
|
||||
{
|
||||
// hardcoded_topology
|
||||
hardcoded_topology.get_gateways()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
current_gateways(&mut rng, &config.base.client.nym_api_urls).await?
|
||||
};
|
||||
|
||||
let init_details = nym_client_core::init::setup_gateway_from(
|
||||
gateway_setup,
|
||||
&key_store,
|
||||
&details_store,
|
||||
register_gateway,
|
||||
Some(&config.base.client.nym_api_urls),
|
||||
Some(&network_gateways),
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?
|
||||
|
||||
@@ -84,8 +84,8 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
||||
let bin_name = "nym-native-client";
|
||||
|
||||
match args.command {
|
||||
Commands::Init(m) => init::execute(&m).await?,
|
||||
Commands::Run(m) => run::execute(&m).await?,
|
||||
Commands::Init(m) => init::execute(m).await?,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::BuildInfo(m) => build_info::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
@@ -157,7 +157,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, ClientError> {
|
||||
|
||||
let updated_step1: ConfigV1_1_20 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
|
||||
let (updated, gateway_config) = updated_step2.upgrade();
|
||||
let (updated, gateway_config) = updated_step2.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
@@ -177,7 +177,7 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, ClientError> {
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_20_2 = old_config.into();
|
||||
let (updated, gateway_config) = updated_step1.upgrade();
|
||||
let (updated, gateway_config) = updated_step1.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
@@ -194,7 +194,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, ClientError> {
|
||||
info!("It seems the client is using <= v1.1.20_2 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let (updated, gateway_config) = old_config.upgrade();
|
||||
let (updated, gateway_config) = old_config.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
|
||||
@@ -13,6 +13,7 @@ use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Run {
|
||||
@@ -25,7 +26,12 @@ pub(crate) struct Run {
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the API validators
|
||||
#[clap(long, alias = "api_validators", value_delimiter = ',')]
|
||||
#[clap(
|
||||
long,
|
||||
alias = "api_validators",
|
||||
value_delimiter = ',',
|
||||
group = "network"
|
||||
)]
|
||||
// the alias here is included for backwards compatibility (1.1.4 and before)
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
@@ -46,6 +52,10 @@ pub(crate) struct Run {
|
||||
#[clap(long)]
|
||||
host: Option<IpAddr>,
|
||||
|
||||
/// Path to .json file containing custom network specification.
|
||||
#[clap(long, group = "network", hide = true)]
|
||||
custom_mixnet: Option<PathBuf>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hide = true)]
|
||||
@@ -95,7 +105,7 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
eprintln!("Starting client {}...", args.id);
|
||||
|
||||
let mut config = try_load_current_config(&args.id)?;
|
||||
@@ -106,5 +116,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Syn
|
||||
return Err(Box::new(ClientError::FailedLocalVersionCheck));
|
||||
}
|
||||
|
||||
SocketClient::new(config).run_socket_forever().await
|
||||
SocketClient::new(config, args.custom_mixnet)
|
||||
.run_socket_forever()
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -230,8 +230,7 @@ impl ServerResponse {
|
||||
|
||||
let error_kind = ErrorKind::try_from(b[1])?;
|
||||
|
||||
let message_len =
|
||||
u64::from_be_bytes(b[2..2 + size_of::<u64>()].as_ref().try_into().unwrap());
|
||||
let message_len = u64::from_be_bytes(b[2..2 + size_of::<u64>()].try_into().unwrap());
|
||||
let message = &b[2 + size_of::<u64>()..];
|
||||
if message.len() as u64 != message_len {
|
||||
return Err(error::Error::new(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.26"
|
||||
version = "1.1.29"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
@@ -16,6 +16,7 @@ serde_json = { workspace = true }
|
||||
tap = "1.0.1"
|
||||
thiserror = { workspace = true }
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "net", "signal"] }
|
||||
rand = "0.7.3"
|
||||
url = { workspace = true }
|
||||
|
||||
# internal
|
||||
|
||||
@@ -14,11 +14,14 @@ use nym_bin_common::output_format::OutputFormat;
|
||||
use nym_client_core::client::base_client::storage::gateway_details::OnDiskGatewayDetails;
|
||||
use nym_client_core::client::key_manager::persistence::OnDiskKeys;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::GatewaySetup;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_topology::NymTopology;
|
||||
use serde::Serialize;
|
||||
use std::fmt::Display;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, io};
|
||||
use tap::TapFallible;
|
||||
|
||||
@@ -68,6 +71,10 @@ pub(crate) struct Init {
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Path to .json file containing custom network specification.
|
||||
#[clap(long, group = "network", hide = true)]
|
||||
custom_mixnet: Option<PathBuf>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hide = true)]
|
||||
@@ -138,7 +145,7 @@ fn init_paths(id: &str) -> io::Result<()> {
|
||||
fs::create_dir_all(default_config_directory(id))
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
pub(crate) async fn execute(args: Init) -> Result<(), Socks5ClientError> {
|
||||
eprintln!("Initialising client...");
|
||||
|
||||
let id = &args.id;
|
||||
@@ -185,12 +192,25 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
let key_store = OnDiskKeys::new(config.storage_paths.common_paths.keys.clone());
|
||||
let details_store =
|
||||
OnDiskGatewayDetails::new(&config.storage_paths.common_paths.gateway_details);
|
||||
let init_details = nym_client_core::init::setup_gateway(
|
||||
|
||||
let network_gateways = if let Some(hardcoded_topology) = args
|
||||
.custom_mixnet
|
||||
.map(NymTopology::new_from_file)
|
||||
.transpose()?
|
||||
{
|
||||
// hardcoded_topology
|
||||
hardcoded_topology.get_gateways()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
current_gateways(&mut rng, &config.core.base.client.nym_api_urls).await?
|
||||
};
|
||||
|
||||
let init_details = nym_client_core::init::setup_gateway_from(
|
||||
gateway_setup,
|
||||
&key_store,
|
||||
&details_store,
|
||||
register_gateway,
|
||||
Some(&config.core.base.client.nym_api_urls),
|
||||
Some(&network_gateways),
|
||||
)
|
||||
.await
|
||||
.tap_err(|err| eprintln!("Failed to setup gateway\nError: {err}"))?
|
||||
|
||||
@@ -87,8 +87,8 @@ pub(crate) async fn execute(args: Cli) -> Result<(), Box<dyn Error + Send + Sync
|
||||
let bin_name = "nym-socks5-client";
|
||||
|
||||
match args.command {
|
||||
Commands::Init(m) => init::execute(&m).await?,
|
||||
Commands::Run(m) => run::execute(&m).await?,
|
||||
Commands::Init(m) => init::execute(m).await?,
|
||||
Commands::Run(m) => run::execute(m).await?,
|
||||
Commands::BuildInfo(m) => build_info::execute(m),
|
||||
Commands::Completions(s) => s.generate(&mut Cli::command(), bin_name),
|
||||
Commands::GenerateFigSpec => fig_generate(&mut Cli::command(), bin_name),
|
||||
@@ -199,7 +199,7 @@ fn try_upgrade_v1_1_13_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
|
||||
let updated_step1: ConfigV1_1_20 = old_config.into();
|
||||
let updated_step2: ConfigV1_1_20_2 = updated_step1.into();
|
||||
let (updated, gateway_config) = updated_step2.upgrade();
|
||||
let (updated, gateway_config) = updated_step2.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
@@ -219,7 +219,7 @@ fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated_step1: ConfigV1_1_20_2 = old_config.into();
|
||||
let (updated, gateway_config) = updated_step1.upgrade();
|
||||
let (updated, gateway_config) = updated_step1.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
@@ -236,7 +236,7 @@ fn try_upgrade_v1_1_20_2_config(id: &str) -> Result<bool, Socks5ClientError> {
|
||||
info!("It seems the client is using <= v1.1.20_2 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let (updated, gateway_config) = old_config.upgrade();
|
||||
let (updated, gateway_config) = old_config.upgrade()?;
|
||||
persist_gateway_details(&updated, gateway_config)?;
|
||||
|
||||
updated.save_to_default_location()?;
|
||||
|
||||
@@ -15,6 +15,7 @@ use nym_client_core::client::topology_control::geo_aware_provider::CountryGroup;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_socks5_client_core::NymClient;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Args, Clone)]
|
||||
pub(crate) struct Run {
|
||||
@@ -45,13 +46,17 @@ pub(crate) struct Run {
|
||||
nyxd_urls: Option<Vec<url::Url>>,
|
||||
|
||||
/// Comma separated list of rest endpoints of the Nym APIs
|
||||
#[clap(long, value_delimiter = ',')]
|
||||
#[clap(long, value_delimiter = ',', group = "network")]
|
||||
nym_apis: Option<Vec<url::Url>>,
|
||||
|
||||
/// Port for the socket to listen on
|
||||
#[clap(short, long)]
|
||||
port: Option<u16>,
|
||||
|
||||
/// Path to .json file containing custom network specification.
|
||||
#[clap(long, group = "network", group = "routing", hide = true)]
|
||||
custom_mixnet: Option<PathBuf>,
|
||||
|
||||
/// Mostly debug-related option to increase default traffic rate so that you would not need to
|
||||
/// modify config post init
|
||||
#[clap(long, hide = true)]
|
||||
@@ -62,7 +67,7 @@ pub(crate) struct Run {
|
||||
no_cover: bool,
|
||||
|
||||
/// Set geo-aware mixnode selection when sending mixnet traffic, for experiments only.
|
||||
#[clap(long, hide = true, value_parser = validate_country_group)]
|
||||
#[clap(long, hide = true, value_parser = validate_country_group, group="routing")]
|
||||
geo_routing: Option<CountryGroup>,
|
||||
|
||||
/// Enable medium mixnet traffic, for experiments only.
|
||||
@@ -124,7 +129,7 @@ fn version_check(cfg: &Config) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pub(crate) async fn execute(args: Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
eprintln!("Starting client {}...", args.id);
|
||||
|
||||
let mut config = try_load_current_config(&args.id)?;
|
||||
@@ -138,5 +143,7 @@ pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error
|
||||
let storage =
|
||||
OnDiskPersistent::from_paths(config.storage_paths.common_paths, &config.core.base.debug)
|
||||
.await?;
|
||||
NymClient::new(config.core, storage).run_forever().await
|
||||
NymClient::new(config.core, storage, args.custom_mixnet)
|
||||
.run_forever()
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::persistence::SocksClientPaths;
|
||||
use crate::config::{default_config_filepath, Config};
|
||||
use crate::{
|
||||
config::{default_config_filepath, persistence::SocksClientPaths, Config},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
|
||||
use nym_bin_common::logging::LoggingSettings;
|
||||
use nym_client_core::config::disk_persistence::old_v1_1_20_2::CommonClientPathsV1_1_20_2;
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_config::read_config_from_toml_file;
|
||||
pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
|
||||
pub use nym_socks5_client_core::config::old_config_v1_1_20_2::ConfigV1_1_20_2 as CoreConfigV1_1_20_2;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone)]
|
||||
pub struct SocksClientPathsV1_1_20_2 {
|
||||
#[serde(flatten)]
|
||||
@@ -39,16 +43,16 @@ impl ConfigV1_1_20_2 {
|
||||
|
||||
// in this upgrade, gateway endpoint configuration was moved out of the config file,
|
||||
// so its returned to be stored elsewhere.
|
||||
pub fn upgrade(self) -> (Config, GatewayEndpointConfig) {
|
||||
pub fn upgrade(self) -> Result<(Config, GatewayEndpointConfig), Socks5ClientError> {
|
||||
let gateway_details = self.core.base.client.gateway_endpoint.clone().into();
|
||||
let config = Config {
|
||||
core: self.core.into(),
|
||||
storage_paths: SocksClientPaths {
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default(),
|
||||
common_paths: self.storage_paths.common_paths.upgrade_default()?,
|
||||
},
|
||||
logging: self.logging,
|
||||
};
|
||||
|
||||
(config, gateway_details)
|
||||
Ok((config, gateway_details))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
install:
|
||||
- appveyor-retry appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
|
||||
- if not defined RUSTFLAGS rustup-init.exe -y --default-host x86_64-pc-windows-msvc --default-toolchain nightly
|
||||
- set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
|
||||
- rustc -V
|
||||
- cargo -V
|
||||
|
||||
build: false
|
||||
|
||||
test_script:
|
||||
- cargo test --locked
|
||||
@@ -1,2 +0,0 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
@@ -1,6 +0,0 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
||||
node_modules
|
||||
@@ -1,69 +0,0 @@
|
||||
language: rust
|
||||
sudo: false
|
||||
|
||||
cache: cargo
|
||||
|
||||
matrix:
|
||||
include:
|
||||
|
||||
# Builds with wasm-pack.
|
||||
- rust: beta
|
||||
env: RUST_BACKTRACE=1
|
||||
addons:
|
||||
firefox: latest
|
||||
chrome: stable
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
# Having a broken Cargo.toml (in that it has curlies in fields) anywhere
|
||||
# in any of our parent dirs is problematic.
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- wasm-pack build
|
||||
- wasm-pack test --chrome --firefox --headless
|
||||
|
||||
# Builds on nightly.
|
||||
- rust: nightly
|
||||
env: RUST_BACKTRACE=1
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- rustup target add wasm32-unknown-unknown
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- cargo check
|
||||
- cargo check --target wasm32-unknown-unknown
|
||||
- cargo check --no-default-features
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features
|
||||
- cargo check --no-default-features --features console_error_panic_hook
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
|
||||
- cargo check --no-default-features --features "console_error_panic_hook wee_alloc"
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features "console_error_panic_hook wee_alloc"
|
||||
|
||||
# Builds on beta.
|
||||
- rust: beta
|
||||
env: RUST_BACKTRACE=1
|
||||
before_script:
|
||||
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
|
||||
- (test -x $HOME/.cargo/bin/cargo-generate || cargo install --vers "^0.2" cargo-generate)
|
||||
- cargo install-update -a
|
||||
- rustup target add wasm32-unknown-unknown
|
||||
script:
|
||||
- cargo generate --git . --name testing
|
||||
- mv Cargo.toml Cargo.toml.tmpl
|
||||
- cd testing
|
||||
- cargo check
|
||||
- cargo check --target wasm32-unknown-unknown
|
||||
- cargo check --no-default-features
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features
|
||||
- cargo check --no-default-features --features console_error_panic_hook
|
||||
- cargo check --target wasm32-unknown-unknown --no-default-features --features console_error_panic_hook
|
||||
# Note: no enabling the `wee_alloc` feature here because it requires
|
||||
# nightly for now.
|
||||
Generated
-5547
File diff suppressed because it is too large
Load Diff
@@ -1,74 +0,0 @@
|
||||
[package]
|
||||
name = "nym-client-wasm"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
version = "1.1.1"
|
||||
edition = "2021"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy"]
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/nymtech/nym"
|
||||
description = "A webassembly client which can be used to interact with the the Nym privacy platform. Wasm is used for Sphinx packet generation."
|
||||
rust-version = "1.56"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook"]
|
||||
offline-test = []
|
||||
|
||||
[dependencies]
|
||||
async-trait = "0.1.68"
|
||||
bs58 = "0.4.0"
|
||||
futures = "0.3"
|
||||
js-sys = "0.3"
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0"
|
||||
serde-wasm-bindgen = "0.5"
|
||||
tokio = { version = "1.24.1", features = ["sync"] }
|
||||
url = "2.2"
|
||||
wasm-bindgen = { version = "=0.2.83", features = ["serde-serialize"] }
|
||||
wasm-bindgen-futures = "0.4"
|
||||
thiserror = "1.0.40"
|
||||
zeroize = "1.6.0"
|
||||
|
||||
wasmtimer = { version = "0.2.0", features = ["tokio"] }
|
||||
|
||||
# internal
|
||||
nym-node-tester-utils = { path = "../../common/node-tester-utils" }
|
||||
nym-client-core = { path = "../../common/client-core", default-features = false, features = ["wasm"] }
|
||||
nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-coconut-interface = { path = "../../common/coconut-interface" }
|
||||
nym-credentials = { path = "../../common/credentials" }
|
||||
nym-credential-storage = { path = "../../common/credential-storage" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
|
||||
nym-sphinx = { path = "../../common/nymsphinx" }
|
||||
nym-sphinx-acknowledgements = { path = "../../common/nymsphinx/acknowledgements", features = ["serde"]}
|
||||
nym-topology = { path = "../../common/topology" }
|
||||
nym-gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm"] }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../../common/task" }
|
||||
wasm-utils = { path = "../../common/wasm-utils", features = ["storage"], default-features = false }
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1", optional = true }
|
||||
|
||||
# `wee_alloc` is a tiny allocator for wasm that is only ~1K in code size
|
||||
# compared to the default allocator's ~10K. It is slower than the default
|
||||
# allocator, however.
|
||||
#
|
||||
# Unfortunately, `wee_alloc` requires nightly Rust when targeting wasm for now.
|
||||
wee_alloc = { version = "0.4", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 'z'
|
||||
@@ -1,176 +0,0 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
@@ -1,8 +0,0 @@
|
||||
clippy:
|
||||
cargo clippy --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
|
||||
test:
|
||||
wasm-pack test --node
|
||||
|
||||
wasm-build:
|
||||
wasm-pack build
|
||||
@@ -1,382 +0,0 @@
|
||||
// Copyright 2020-2023 Nym Technologies SA
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
importScripts("nym_client_wasm.js");
|
||||
|
||||
console.log("Initializing worker");
|
||||
|
||||
// wasm_bindgen creates a global variable (with the exports attached) that is in scope after `importScripts`
|
||||
const {
|
||||
NymNodeTester,
|
||||
WasmGateway,
|
||||
WasmMixNode,
|
||||
WasmNymTopology,
|
||||
default_debug,
|
||||
NymClientBuilder,
|
||||
NymClient,
|
||||
set_panic_hook,
|
||||
Config,
|
||||
GatewayEndpointConfig,
|
||||
ClientStorage,
|
||||
current_network_topology,
|
||||
make_key,
|
||||
make_key2,
|
||||
} = wasm_bindgen;
|
||||
|
||||
let client = null;
|
||||
let tester = null;
|
||||
|
||||
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
|
||||
function dummyTopology() {
|
||||
const l1Mixnode = new WasmMixNode(
|
||||
1,
|
||||
"n1fzv4jc7fanl9s0qj02ge2ezk3kts545kjtek47",
|
||||
"178.79.143.65",
|
||||
1789,
|
||||
"4Yr4qmEHd9sgsuQ83191FR2hD88RfsbMmB4tzhhZWriz",
|
||||
"8ndjk5oZ6HxUZNScLJJ7hk39XtUqGexdKgW7hSX6kpWG",
|
||||
1,
|
||||
"1.10.0"
|
||||
);
|
||||
const l2Mixnode = new WasmMixNode(
|
||||
2,
|
||||
"n1z93z44vf8ssvdhujjvxcj4rd5e3lz0l60wdk70",
|
||||
"109.74.197.180",
|
||||
1789,
|
||||
"7sVjiMrPYZrDWRujku9QLxgE8noT7NTgBAqizCsu7AoK",
|
||||
"GepXwRnKZDd8x2nBWAajGGBVvF3mrpVMQBkgfrGuqRCN",
|
||||
2,
|
||||
"1.10.0"
|
||||
);
|
||||
const l3Mixnode = new WasmMixNode(
|
||||
3,
|
||||
"n1ptg680vnmef2cd8l0s9uyc4f0hgf3x8sed6w77",
|
||||
"176.58.101.80",
|
||||
1789,
|
||||
"FoM5Mx9Pxk1g3zEqkS3APgtBeTtTo3M8k7Yu4bV6kK1R",
|
||||
"DeYjrDC2AcQRVFshiKnbUo6bRvPyZ33QGYR2DLeFJ9qD",
|
||||
3,
|
||||
"1.10.0"
|
||||
);
|
||||
|
||||
const gateway = new WasmGateway(
|
||||
"n16evnn8glr0sham3matj8rg2s24m6x56ayk87ts",
|
||||
"85.159.212.96",
|
||||
1789,
|
||||
9000,
|
||||
"336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9",
|
||||
"BtYjoWihiuFihGKQypmpSspbhmWDPxzqeTVSd8ciCpWL",
|
||||
"1.10.1"
|
||||
);
|
||||
|
||||
const mixnodes = new Map();
|
||||
mixnodes.set(1, [l1Mixnode]);
|
||||
mixnodes.set(2, [l2Mixnode]);
|
||||
mixnodes.set(3, [l3Mixnode]);
|
||||
|
||||
const gateways = [gateway];
|
||||
|
||||
return new WasmNymTopology(mixnodes, gateways);
|
||||
}
|
||||
|
||||
function printAndDisplayTestResult(result) {
|
||||
result.log_details();
|
||||
|
||||
self.postMessage({
|
||||
kind: "DisplayTesterResults",
|
||||
args: {
|
||||
score: result.score(),
|
||||
sentPackets: result.sent_packets,
|
||||
receivedPackets: result.received_packets,
|
||||
receivedAcks: result.received_acks,
|
||||
duplicatePackets: result.duplicate_packets,
|
||||
duplicateAcks: result.duplicate_acks,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function testWithTester() {
|
||||
// A) construct with hardcoded topology
|
||||
const topology = dummyTopology();
|
||||
const nodeTester = await new NymNodeTester(topology, preferredGateway);
|
||||
|
||||
// B) first get topology directly from nym-api
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const topology = await current_network_topology(validator)
|
||||
// const nodeTester = await new NymNodeTester(topology, preferredGateway);
|
||||
//
|
||||
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const nodeTester = await NymNodeTester.new_with_api(validator, preferredGateway)
|
||||
|
||||
// B) first get topology directly from nym-api
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const topology = await current_network_topology(validator)
|
||||
// const nodeTester = await new NymNodeTester(topology, undefined, preferredGateway);
|
||||
//
|
||||
// C) use nym-api in the constructor (note: it does no filtering for 'good' nodes on other layers)
|
||||
// const validator = 'https://qwerty-validator-api.qa.nymte.ch/api';
|
||||
// const nodeTester = await NymNodeTester.new_with_api(validator, undefined, preferredGateway)
|
||||
// D, E, F) you also don't have to specify the gateway. if you don't, a random one (from your topology) will be used
|
||||
// const topology = dummyTopology()
|
||||
// const nodeTester = await new NymNodeTester(topology);
|
||||
|
||||
self.onmessage = async (event) => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "TestPacket": {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
console.log("starting node test...");
|
||||
|
||||
let result = await nodeTester.test_node(mixnodeIdentity);
|
||||
printAndDisplayTestResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function testerReconnection() {
|
||||
const validator = "https://qwerty-validator-api.qa.nymte.ch/api";
|
||||
const nodeTester = await NymNodeTester.new_with_api(validator);
|
||||
|
||||
self.onmessage = async (event) => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "TestPacket": {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
console.log("starting node test...");
|
||||
|
||||
let result1 = await nodeTester.test_node(mixnodeIdentity);
|
||||
console.log("sleeping for 5s");
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
await nodeTester.disconnect_from_gateway();
|
||||
|
||||
console.log("sleeping for 5s");
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
|
||||
await nodeTester.reconnect_to_gateway();
|
||||
let result2 = await nodeTester.test_node(mixnodeIdentity);
|
||||
|
||||
printAndDisplayTestResult(result1);
|
||||
printAndDisplayTestResult(result2);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function testWithNymClient() {
|
||||
const topology = dummyTopology();
|
||||
|
||||
let received = 0;
|
||||
|
||||
const onMessageHandler = (message) => {
|
||||
received += 1;
|
||||
self.postMessage({
|
||||
kind: "ReceiveMessage",
|
||||
args: {
|
||||
message,
|
||||
senderTag: undefined,
|
||||
isTestPacket: true,
|
||||
},
|
||||
});
|
||||
|
||||
// it's really up to the user to create proper callback here...
|
||||
console.log(`received ${received} packets so far`);
|
||||
};
|
||||
|
||||
console.log("Instantiating WASM client...");
|
||||
|
||||
let clientBuilder = NymClientBuilder.new_tester(
|
||||
topology,
|
||||
onMessageHandler,
|
||||
preferredGateway
|
||||
);
|
||||
|
||||
console.log("Web worker creating WASM client...");
|
||||
let local_client = await clientBuilder.start_client();
|
||||
console.log("WASM client running!");
|
||||
|
||||
const selfAddress = local_client.self_address();
|
||||
|
||||
// set the global (I guess we don't have to anymore?)
|
||||
client = local_client;
|
||||
|
||||
console.log(`Client address is ${selfAddress}`);
|
||||
self.postMessage({
|
||||
kind: "Ready",
|
||||
args: {
|
||||
selfAddress,
|
||||
},
|
||||
});
|
||||
|
||||
// Set callback to handle messages passed to the worker.
|
||||
self.onmessage = async (event) => {
|
||||
console.log(event);
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "SendMessage": {
|
||||
const { message, recipient } = event.data.args;
|
||||
let uint8Array = new TextEncoder().encode(message);
|
||||
await client.send_regular_message(uint8Array, recipient);
|
||||
break;
|
||||
}
|
||||
case "TestPacket": {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
const req = await client.try_construct_test_packet_request(
|
||||
mixnodeIdentity
|
||||
);
|
||||
await client.change_hardcoded_topology(req.injectable_topology());
|
||||
await client.try_send_test_packets(req);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function normalNymClientUsage() {
|
||||
self.postMessage({ kind: "DisableMagicTestButton" });
|
||||
|
||||
// only really useful if you want to adjust some settings like traffic rate
|
||||
// (if not needed you can just pass a null)
|
||||
const debug = default_debug();
|
||||
|
||||
debug.disable_main_poisson_packet_distribution = true;
|
||||
debug.disable_loop_cover_traffic_stream = true;
|
||||
debug.use_extended_packet_size = false;
|
||||
// debug.average_packet_delay_ms = BigInt(10);
|
||||
// debug.average_ack_delay_ms = BigInt(10);
|
||||
// debug.ack_wait_addition_ms = BigInt(3000);
|
||||
// debug.ack_wait_multiplier = 10;
|
||||
|
||||
debug.topology_refresh_rate_ms = BigInt(60000);
|
||||
|
||||
const preferredGateway = "336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9";
|
||||
const validator = "https://qwerty-validator-api.qa.nymte.ch/api";
|
||||
|
||||
const config = new Config("my-awesome-wasm-client", validator, debug);
|
||||
|
||||
const onMessageHandler = (message) => {
|
||||
console.log(message);
|
||||
self.postMessage({
|
||||
kind: "ReceiveMessage",
|
||||
args: {
|
||||
message,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
console.log("Instantiating WASM client...");
|
||||
|
||||
let localClient = await new NymClient(config, onMessageHandler);
|
||||
console.log("WASM client running!");
|
||||
|
||||
const selfAddress = localClient.self_address();
|
||||
|
||||
// set the global (I guess we don't have to anymore?)
|
||||
client = localClient;
|
||||
|
||||
console.log(`Client address is ${selfAddress}`);
|
||||
self.postMessage({
|
||||
kind: "Ready",
|
||||
args: {
|
||||
selfAddress,
|
||||
},
|
||||
});
|
||||
|
||||
// Set callback to handle messages passed to the worker.
|
||||
self.onmessage = async (event) => {
|
||||
console.log(event);
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "SendMessage": {
|
||||
const { message, recipient } = event.data.args;
|
||||
let uint8Array = new TextEncoder().encode(message);
|
||||
await client.send_regular_message(uint8Array, recipient);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function messWithStorage() {
|
||||
self.onmessage = async (event) => {
|
||||
if (event.data && event.data.kind) {
|
||||
switch (event.data.kind) {
|
||||
case "TestPacket": {
|
||||
const { mixnodeIdentity } = event.data.args;
|
||||
console.log("button clicked...", mixnodeIdentity);
|
||||
|
||||
let id1 = "one";
|
||||
let id2 = "two";
|
||||
|
||||
console.log("making store1 NO-ENC");
|
||||
let _storage1 = await ClientStorage.new_unencrypted(id1);
|
||||
|
||||
console.log("making store2 ENC");
|
||||
let _storage2 = await new ClientStorage(id2, "my-secret-password");
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store1 WITH PASSWORD")
|
||||
// let _storage1_alt = await new ClientStorage(id1, "password");
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store2 WITHOUT PASSWORD")
|
||||
// let _storage2_alt = await ClientStorage.new_unencrypted(id2);
|
||||
//
|
||||
//
|
||||
//
|
||||
// console.log("attempting to use store2 with WRONG PASSWORD")
|
||||
// let _storage2_bad = await new ClientStorage(id2, "bad-password")
|
||||
|
||||
//
|
||||
// console.log("read1: ", await storage1.read());
|
||||
// console.log("read2: ", await storage2.read());
|
||||
//
|
||||
// console.log("store1: ", await storage1.store("FOOMP"));
|
||||
//
|
||||
// console.log("read1: ", await storage1.read());
|
||||
// console.log("read2: ", await storage2.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// load WASM package
|
||||
await wasm_bindgen("nym_client_wasm_bg.wasm");
|
||||
console.log("Loaded WASM");
|
||||
|
||||
// sets up better stack traces in case of in-rust panics
|
||||
set_panic_hook();
|
||||
|
||||
// show reconnection capabilities
|
||||
// await testerReconnection()
|
||||
|
||||
// run test on simplified and dedicated tester:
|
||||
await testWithTester();
|
||||
|
||||
// 'Normal' client setup (to send 'normal' messages)
|
||||
// await normalNymClientUsage()
|
||||
}
|
||||
|
||||
// Let's get started!
|
||||
main();
|
||||
@@ -1,5 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) const NODE_TESTER_ID: &str = "_nym-node-tester";
|
||||
pub(crate) const NODE_TESTER_CLIENT_ID: &str = "_nym-node-tester-client";
|
||||
@@ -1,45 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub async fn get_gateway(api_server: String, preferred: Option<String>) -> GatewayEndpointConfig {
|
||||
let validator_client =
|
||||
nym_validator_client::client::NymApiClient::new(api_server.parse().unwrap());
|
||||
|
||||
let gateways = match validator_client.get_cached_gateways().await {
|
||||
Err(err) => panic!("failed to obtain list of all gateways - {err}"),
|
||||
Ok(gateways) => gateways,
|
||||
};
|
||||
|
||||
if let Some(preferred) = preferred {
|
||||
if let Some(details) = gateways
|
||||
.iter()
|
||||
.find(|g| g.gateway.identity_key == preferred)
|
||||
{
|
||||
return GatewayEndpointConfig {
|
||||
gateway_id: details.gateway.identity_key.clone(),
|
||||
gateway_owner: details.owner.to_string(),
|
||||
gateway_listener: format!(
|
||||
"ws://{}:{}",
|
||||
details.gateway.host, details.gateway.clients_port
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
let details = gateways
|
||||
.first()
|
||||
.expect("current topology holds no gateways");
|
||||
|
||||
GatewayEndpointConfig {
|
||||
gateway_id: details.gateway.identity_key.clone(),
|
||||
gateway_owner: details.owner.to_string(),
|
||||
gateway_listener: format!(
|
||||
"ws://{}:{}",
|
||||
details.gateway.host, details.gateway.clients_port
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// After reading https://github.com/rust-lang/rust-clippy/issues/11382
|
||||
// I suspect we *maybe* have hit a false positive, but I'm not sure.
|
||||
#![allow(clippy::arc_with_non_send_sync)]
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod client;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod encoded_payload_helper;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod error;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod gateway_selector;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod storage;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod tester;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod topology;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod validation;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod helpers;
|
||||
|
||||
mod constants;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::simple_js_error;
|
||||
use wasm_utils::storage::error::StorageError;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientStorageError {
|
||||
#[error("failed to use the storage: {source}")]
|
||||
StorageError {
|
||||
#[from]
|
||||
source: StorageError,
|
||||
},
|
||||
|
||||
#[error("{typ} cryptographic key is not available in storage")]
|
||||
CryptoKeyNotInStorage { typ: String },
|
||||
|
||||
#[error("the prior gateway details are not available in the storage")]
|
||||
GatewayDetailsNotInStorage,
|
||||
}
|
||||
|
||||
impl From<ClientStorageError> for JsValue {
|
||||
fn from(value: ClientStorageError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::Config;
|
||||
use crate::storage::error::ClientStorageError;
|
||||
use js_sys::Promise;
|
||||
use nym_client_core::client::base_client::storage::gateway_details::PersistedGatewayDetails;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_gateway_client::SharedKeys;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::storage::{IdbVersionChangeEvent, WasmStorage};
|
||||
use wasm_utils::PromisableResult;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
pub(crate) mod error;
|
||||
pub(crate) mod traits;
|
||||
|
||||
const STORAGE_NAME_PREFIX: &str = "wasm-client-storage";
|
||||
const STORAGE_VERSION: u32 = 1;
|
||||
|
||||
// v1 tables
|
||||
mod v1 {
|
||||
// stores
|
||||
pub const KEYS_STORE: &str = "keys";
|
||||
pub const CORE_STORE: &str = "core";
|
||||
|
||||
// keys
|
||||
pub const CONFIG: &str = "config";
|
||||
pub const GATEWAY_DETAILS: &str = "gateway_details";
|
||||
|
||||
pub const ED25519_IDENTITY_KEYPAIR: &str = "ed25519_identity_keypair";
|
||||
pub const X25519_ENCRYPTION_KEYPAIR: &str = "x25519_encryption_keypair";
|
||||
|
||||
// TODO: for those we could actually use the subtle crypto storage
|
||||
pub const AES128CTR_ACK_KEY: &str = "aes128ctr_ack_key";
|
||||
pub const AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS: &str = "aes128ctr_blake3_hmac_gateway_keys";
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ClientStorage {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) name: String,
|
||||
pub(crate) inner: Arc<WasmStorage>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ClientStorage {
|
||||
fn db_name(client_id: &str) -> String {
|
||||
format!("{STORAGE_NAME_PREFIX}-{client_id}")
|
||||
}
|
||||
|
||||
pub(crate) async fn new_async(
|
||||
client_id: &str,
|
||||
passphrase: Option<String>,
|
||||
) -> Result<Self, ClientStorageError> {
|
||||
let name = Self::db_name(client_id);
|
||||
|
||||
// make sure the password is zeroized when no longer used, especially if we error out.
|
||||
// special care must be taken on JS side to ensure it's correctly used there.
|
||||
let passphrase = Zeroizing::new(passphrase);
|
||||
|
||||
let migrate_fn = Some(|evt: &IdbVersionChangeEvent| -> Result<(), JsValue> {
|
||||
// Even if the web-sys bindings expose the version as a f64, the IndexedDB API
|
||||
// works with an unsigned integer.
|
||||
// See <https://github.com/rustwasm/wasm-bindgen/issues/1149>
|
||||
let old_version = evt.old_version() as u32;
|
||||
|
||||
if old_version < 1 {
|
||||
// migrating to version 1
|
||||
let db = evt.db();
|
||||
|
||||
db.create_object_store(v1::KEYS_STORE)?;
|
||||
db.create_object_store(v1::CORE_STORE)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
let inner = WasmStorage::new(
|
||||
&name,
|
||||
STORAGE_VERSION,
|
||||
migrate_fn,
|
||||
passphrase.as_ref().map(|p| p.as_bytes()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(ClientStorage {
|
||||
inner: Arc::new(inner),
|
||||
name,
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new(client_id: String, passphrase: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, Some(passphrase))
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_unencrypted(client_id: String) -> Promise {
|
||||
future_to_promise(async move {
|
||||
Self::new_async(&client_id, None)
|
||||
.await
|
||||
.into_promise_result()
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: persist client's config
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn read_config(&self) -> Result<Option<Config>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn may_read_gateway_details(
|
||||
&self,
|
||||
) -> Result<Option<PersistedGatewayDetails>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::CORE_STORE, JsValue::from_str(v1::GATEWAY_DETAILS))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn must_read_gateway_details(
|
||||
&self,
|
||||
) -> Result<PersistedGatewayDetails, ClientStorageError> {
|
||||
self.may_read_gateway_details()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::GatewayDetailsNotInStorage)
|
||||
}
|
||||
|
||||
async fn may_read_identity_keypair(
|
||||
&self,
|
||||
) -> Result<Option<identity::KeyPair>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<Option<encryption::KeyPair>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_ack_key(&self) -> Result<Option<AckKey>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(v1::KEYS_STORE, JsValue::from_str(v1::AES128CTR_ACK_KEY))
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn may_read_gateway_shared_key(&self) -> Result<Option<SharedKeys>, ClientStorageError> {
|
||||
self.inner
|
||||
.read_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn must_read_identity_keypair(&self) -> Result<identity::KeyPair, ClientStorageError> {
|
||||
self.may_read_identity_keypair()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::ED25519_IDENTITY_KEYPAIR.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_encryption_keypair(
|
||||
&self,
|
||||
) -> Result<encryption::KeyPair, ClientStorageError> {
|
||||
self.may_read_encryption_keypair()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::X25519_ENCRYPTION_KEYPAIR.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_ack_key(&self) -> Result<AckKey, ClientStorageError> {
|
||||
self.may_read_ack_key()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_ACK_KEY.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn must_read_gateway_shared_key(&self) -> Result<SharedKeys, ClientStorageError> {
|
||||
self.may_read_gateway_shared_key()
|
||||
.await?
|
||||
.ok_or(ClientStorageError::CryptoKeyNotInStorage {
|
||||
typ: v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
async fn store_identity_keypair(
|
||||
&self,
|
||||
keypair: &identity::KeyPair,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::ED25519_IDENTITY_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_encryption_keypair(
|
||||
&self,
|
||||
keypair: &encryption::KeyPair,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::X25519_ENCRYPTION_KEYPAIR),
|
||||
keypair,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_ack_key(&self, key: &AckKey) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_ACK_KEY),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
async fn store_gateway_shared_key(&self, key: &SharedKeys) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::KEYS_STORE,
|
||||
JsValue::from_str(v1::AES128CTR_BLAKE3_HMAC_GATEWAY_KEYS),
|
||||
key,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn store_gateway_details(
|
||||
&self,
|
||||
gateway_endpoint: &PersistedGatewayDetails,
|
||||
) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(
|
||||
v1::CORE_STORE,
|
||||
JsValue::from_str(v1::GATEWAY_DETAILS),
|
||||
gateway_endpoint,
|
||||
)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
// TODO: persist client's config
|
||||
#[allow(dead_code)]
|
||||
pub(crate) async fn store_config(&self, config: &Config) -> Result<(), ClientStorageError> {
|
||||
self.inner
|
||||
.store_value(v1::CORE_STORE, JsValue::from_str(v1::CONFIG), config)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn has_full_gateway_info(&self) -> Result<bool, ClientStorageError> {
|
||||
let has_keys = self.may_read_gateway_shared_key().await?.is_some();
|
||||
let has_details = self.may_read_gateway_details().await?.is_some();
|
||||
|
||||
Ok(has_keys && has_details)
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_client_core::config::GatewayEndpointConfig;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_topology::gateway::GatewayConversionError;
|
||||
use nym_topology::mix::{Layer, MixnodeConversionError};
|
||||
use nym_topology::{gateway, mix, MixLayer, NymTopology};
|
||||
use nym_validator_client::client::{IdentityKeyRef, MixId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_utils::{console_log, simple_js_error};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WasmTopologyError {
|
||||
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
|
||||
InvalidMixLayer { value: u8 },
|
||||
|
||||
#[error(transparent)]
|
||||
GatewayConversion(#[from] GatewayConversionError),
|
||||
|
||||
#[error(transparent)]
|
||||
MixnodeConversion(#[from] MixnodeConversionError),
|
||||
|
||||
#[error("The provided mixnode map was malformed: {source}")]
|
||||
MalformedMixnodeMap { source: serde_wasm_bindgen::Error },
|
||||
|
||||
#[error("The provided gateway list was malformed: {source}")]
|
||||
MalformedGatewayList { source: serde_wasm_bindgen::Error },
|
||||
}
|
||||
|
||||
impl From<WasmTopologyError> for JsValue {
|
||||
fn from(value: WasmTopologyError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug)]
|
||||
pub struct WasmNymTopology {
|
||||
inner: NymTopology,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmNymTopology {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
// expected: BTreeMap<MixLayer, Vec<WasmMixNode>>,
|
||||
// HashMap<MixLayer, Vec<WasmMixNode>> will also work because it has the same json representation
|
||||
mixnodes: JsValue,
|
||||
// expected: Vec<WasmGateway>
|
||||
gateways: JsValue,
|
||||
) -> Result<WasmNymTopology, WasmTopologyError> {
|
||||
let mixnodes: BTreeMap<MixLayer, Vec<WasmMixNode>> =
|
||||
serde_wasm_bindgen::from_value(mixnodes)
|
||||
.map_err(|source| WasmTopologyError::MalformedMixnodeMap { source })?;
|
||||
|
||||
let gateways: Vec<WasmGateway> = serde_wasm_bindgen::from_value(gateways)
|
||||
.map_err(|source| WasmTopologyError::MalformedGatewayList { source })?;
|
||||
|
||||
let mut converted_mixes = BTreeMap::new();
|
||||
|
||||
for (layer, nodes) in mixnodes {
|
||||
let layer_nodes = nodes
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
converted_mixes.insert(layer, layer_nodes);
|
||||
}
|
||||
|
||||
let gateways = gateways
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(WasmNymTopology {
|
||||
inner: NymTopology::new(converted_mixes, gateways),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn ensure_contains(&self, gateway_config: &GatewayEndpointConfig) -> bool {
|
||||
self.ensure_contains_gateway_id(&gateway_config.gateway_id)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
|
||||
self.inner
|
||||
.gateways()
|
||||
.iter()
|
||||
.any(|g| g.identity_key.to_base58_string() == gateway_id)
|
||||
}
|
||||
|
||||
pub fn print(&self) {
|
||||
if !self.inner.mixes().is_empty() {
|
||||
console_log!("mixnodes:");
|
||||
for (layer, nodes) in self.inner.mixes() {
|
||||
console_log!("\tlayer {layer}:");
|
||||
for node in nodes {
|
||||
console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console_log!("NO MIXNODES")
|
||||
}
|
||||
|
||||
if !self.inner.gateways().is_empty() {
|
||||
console_log!("gateways:");
|
||||
for gateway in self.inner.gateways() {
|
||||
console_log!("\t{}", gateway.identity_key)
|
||||
}
|
||||
} else {
|
||||
console_log!("NO GATEWAYS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WasmNymTopology> for NymTopology {
|
||||
fn from(value: WasmNymTopology) -> Self {
|
||||
value.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymTopology> for WasmNymTopology {
|
||||
fn from(value: NymTopology) -> Self {
|
||||
WasmNymTopology { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct WasmMixNode {
|
||||
pub mix_id: MixId,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub owner: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub identity_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub sphinx_key: String,
|
||||
pub layer: MixLayer,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmMixNode {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
mix_id: MixId,
|
||||
owner: String,
|
||||
host: String,
|
||||
mix_port: u16,
|
||||
identity_key: String,
|
||||
sphinx_key: String,
|
||||
layer: MixLayer,
|
||||
version: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
mix_id,
|
||||
owner,
|
||||
host,
|
||||
mix_port,
|
||||
identity_key,
|
||||
sphinx_key,
|
||||
layer,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WasmMixNode> for mix::Node {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmMixNode) -> Result<Self, Self::Error> {
|
||||
let host = mix::Node::parse_host(&value.host)?;
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = mix::Node::extract_mix_host(&host, value.mix_port)?;
|
||||
|
||||
Ok(mix::Node {
|
||||
mix_id: value.mix_id,
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
layer: Layer::try_from(value.layer)
|
||||
.map_err(|_| WasmTopologyError::InvalidMixLayer { value: value.layer })?,
|
||||
version: value.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct WasmGateway {
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub owner: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub host: String,
|
||||
pub mix_port: u16,
|
||||
pub clients_port: u16,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub identity_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub sphinx_key: String,
|
||||
#[wasm_bindgen(getter_with_clone)]
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl WasmGateway {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
owner: String,
|
||||
host: String,
|
||||
mix_port: u16,
|
||||
clients_port: u16,
|
||||
identity_key: String,
|
||||
sphinx_key: String,
|
||||
version: String,
|
||||
) -> Self {
|
||||
Self {
|
||||
owner,
|
||||
host,
|
||||
mix_port,
|
||||
clients_port,
|
||||
identity_key,
|
||||
sphinx_key,
|
||||
version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WasmGateway> for gateway::Node {
|
||||
type Error = WasmTopologyError;
|
||||
|
||||
fn try_from(value: WasmGateway) -> Result<Self, Self::Error> {
|
||||
let host = gateway::Node::parse_host(&value.host)?;
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = gateway::Node::extract_mix_host(&host, value.mix_port)?;
|
||||
|
||||
Ok(gateway::Node {
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
clients_port: value.clients_port,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
version: value.version,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn validate_recipient(recipient: String) -> Result<(), JsError> {
|
||||
match Recipient::try_from_base58_string(recipient) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(JsError::new(format!("{}", e).as_str())),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::validate_recipient;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_recipient_validation_ok() {
|
||||
let res = validate_recipient("DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string());
|
||||
assert!(res.is_ok())
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_recipient_validation_fails() {
|
||||
assert!(validate_recipient(" DyQmXnst5NGGjzUZxRC5Bjs5bd7CBF3xMpsSmbRiizr2.GH6YTBP2NXU3AVqd8WjiTMVyeMjunXMEsp7gVCMEJqpD@336yuXAeGEgedRfqTJZsG2YV7P13QH1bHv1SjCZYarc9".to_string()).is_err());
|
||||
assert!(validate_recipient(
|
||||
"DyQmXnst5NGGjzUZxRC5BjbRiizr2.GH6YTBP2NXU3AVqd8WD@336yuXAeGEgedRfqTJZQH1bHv1SjCZYarc9"
|
||||
.to_string()
|
||||
)
|
||||
.is_err());
|
||||
assert!(validate_recipient("🙀🙀🙀🙀".to_string()).is_err());
|
||||
assert!(validate_recipient("".to_string()).is_err());
|
||||
assert!(validate_recipient(" ".to_string()).is_err());
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
assert_eq!(1 + 1, 2);
|
||||
}
|
||||
@@ -22,25 +22,25 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
sha2 = "0.10.6"
|
||||
tap = "1.0.1"
|
||||
thiserror = "1.0.34"
|
||||
time = "0.3.17"
|
||||
tokio = { version = "1.24.1", features = ["macros"]}
|
||||
tungstenite = { version = "0.13.0", default-features = false }
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
tungstenite = { version = "0.13.0", default-features = false }
|
||||
tokio = { workspace = true, features = ["macros"]}
|
||||
time = "0.3.17"
|
||||
zeroize = { workspace = true }
|
||||
|
||||
# internal
|
||||
nym-bandwidth-controller = { path = "../bandwidth-controller" }
|
||||
nym-config = { path = "../config" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-explorer-api-requests = { path = "../../explorer-api/explorer-api-requests" }
|
||||
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
#gateway-client = { path = "../../common/client-libs/gateway-client", default-features = false, features = ["wasm", "coconut"] }
|
||||
nym-gateway-requests = { path = "../../gateway/gateway-requests" }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-topology = { path = "../topology" }
|
||||
nym-topology = { path = "../topology", features = ["serializable"] }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-task = { path = "../task" }
|
||||
nym-credential-storage = { path = "../credential-storage" }
|
||||
@@ -51,7 +51,7 @@ version = "0.1.11"
|
||||
features = ["time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.24.1"
|
||||
workspace = true
|
||||
features = ["time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-tungstenite]
|
||||
@@ -63,10 +63,10 @@ features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]
|
||||
optional = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
|
||||
version = "0.4"
|
||||
workspace = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2.83"
|
||||
workspace = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
workspace = true
|
||||
@@ -77,7 +77,7 @@ version = "0.2.4"
|
||||
features = ["futures"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
|
||||
path = "../wasm-utils"
|
||||
path = "../wasm/utils"
|
||||
features = ["websocket"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.time]
|
||||
@@ -88,7 +88,7 @@ features = ["wasm-bindgen"]
|
||||
tempfile = "3.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
tokio = { version = "1.24.1", features = ["rt-multi-thread", "macros"] }
|
||||
tokio = { workspace = true, features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.6.2", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -44,9 +44,10 @@ use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tap::TapFallible;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
@@ -179,11 +180,13 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
|
||||
self.setup_method = setup;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_topology_provider(
|
||||
mut self,
|
||||
provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
@@ -192,6 +195,15 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_stored_topology<P: AsRef<Path>>(
|
||||
mut self,
|
||||
file: P,
|
||||
) -> Result<Self, ClientCoreError> {
|
||||
self.custom_topology_provider =
|
||||
Some(Box::new(HardcodedTopologyProvider::new_from_file(file)?));
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
|
||||
// because it relies on the crypto keys being already loaded
|
||||
fn mix_address(details: &InitialisationDetails) -> Recipient {
|
||||
@@ -329,13 +341,18 @@ where
|
||||
)
|
||||
};
|
||||
|
||||
let gateway_id = gateway_client.gateway_identity();
|
||||
gateway_client.set_disabled_credentials_mode(config.client.disabled_credentials_mode);
|
||||
|
||||
let shared_key = gateway_client
|
||||
.authenticate_and_start()
|
||||
.await
|
||||
.tap_err(|err| {
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}")
|
||||
.map_err(|err| {
|
||||
log::error!("Could not authenticate and start up the gateway connection - {err}");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway_id.to_base58_string(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
|
||||
managed_keys.ensure_gateway_key(shared_key);
|
||||
|
||||
@@ -6,7 +6,7 @@ use async_trait::async_trait;
|
||||
use std::error::Error;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
// TODO: this should now live inside our wasm/client-core
|
||||
pub mod browser_backend;
|
||||
|
||||
#[cfg(all(not(target_arch = "wasm32"), feature = "fs-surb-storage"))]
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use log::{debug, error, info};
|
||||
use nym_explorer_api_requests::{PrettyDetailedGatewayBond, PrettyDetailedMixNodeBond};
|
||||
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
|
||||
use nym_network_defaults::var_names::EXPLORER_API;
|
||||
use nym_topology::{
|
||||
nym_topology_from_detailed,
|
||||
provider_trait::{async_trait, TopologyProvider},
|
||||
NymTopology,
|
||||
};
|
||||
use nym_validator_client::client::{IdentityKey, MixId};
|
||||
use nym_validator_client::client::MixId;
|
||||
use rand::{prelude::SliceRandom, thread_rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tap::TapOptional;
|
||||
@@ -18,55 +18,24 @@ use crate::config::GroupBy;
|
||||
|
||||
const MIN_NODES_PER_LAYER: usize = 1;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn reqwest_client() -> Option<reqwest::Client> {
|
||||
reqwest::Client::builder().build().ok()
|
||||
}
|
||||
fn create_explorer_client() -> Option<ExplorerClient> {
|
||||
let Ok(explorer_api_url) = std::env::var(EXPLORER_API) else {
|
||||
error!("Missing EXPLORER_API");
|
||||
return None;
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn reqwest_client() -> Option<reqwest::Client> {
|
||||
reqwest::Client::builder()
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.build()
|
||||
.ok()
|
||||
}
|
||||
let Ok(explorer_api_url) = explorer_api_url.parse() else {
|
||||
error!("Failed to parse EXPLORER_API");
|
||||
return None;
|
||||
};
|
||||
|
||||
// TODO: create a explorer-api-client
|
||||
async fn fetch_mixnodes_from_explorer_api() -> Option<Vec<PrettyDetailedMixNodeBond>> {
|
||||
let explorer_api_url = std::env::var(EXPLORER_API).ok()?;
|
||||
let explorer_api_url = Url::parse(&explorer_api_url)
|
||||
.ok()?
|
||||
.join("v1/mix-nodes")
|
||||
.ok()?;
|
||||
log::debug!("Using explorer-api url: {}", explorer_api_url);
|
||||
let Ok(client) = nym_explorer_client::ExplorerClient::new(explorer_api_url) else {
|
||||
error!("Failed to create explorer-api client");
|
||||
return None;
|
||||
};
|
||||
|
||||
debug!("Fetching: {}", explorer_api_url);
|
||||
reqwest_client()?
|
||||
.get(explorer_api_url)
|
||||
.send()
|
||||
.await
|
||||
.ok()?
|
||||
.json::<Vec<PrettyDetailedMixNodeBond>>()
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
// TODO: create a explorer-api-client
|
||||
async fn fetch_gateways_from_explorer_api() -> Option<Vec<PrettyDetailedGatewayBond>> {
|
||||
let explorer_api_url = std::env::var(EXPLORER_API).ok()?;
|
||||
let explorer_api_url = Url::parse(&explorer_api_url)
|
||||
.ok()?
|
||||
.join("v1/gateways")
|
||||
.ok()?;
|
||||
|
||||
debug!("Fetching: {}", explorer_api_url);
|
||||
reqwest_client()?
|
||||
.get(explorer_api_url)
|
||||
.send()
|
||||
.await
|
||||
.ok()?
|
||||
.json::<Vec<PrettyDetailedGatewayBond>>()
|
||||
.await
|
||||
.ok()
|
||||
Some(client)
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Hash, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||
@@ -83,6 +52,8 @@ pub enum CountryGroup {
|
||||
impl CountryGroup {
|
||||
// We map contry codes into group, which initially are continent codes to a first approximation,
|
||||
// but we do it manually to reserve the right to tweak this distribution for our purposes.
|
||||
// NOTE: I did this quickly and it's not a complete list of all countries, but only those that
|
||||
// were present in the network at the time. Please add more as needed.
|
||||
fn new(country_code: &str) -> Self {
|
||||
let country_code = country_code.to_uppercase();
|
||||
use CountryGroup::*;
|
||||
@@ -235,23 +206,6 @@ fn group_mixnodes_by_country_code(
|
||||
})
|
||||
}
|
||||
|
||||
fn group_gateways_by_country_code(
|
||||
gateways: Vec<PrettyDetailedGatewayBond>,
|
||||
) -> HashMap<CountryGroup, Vec<IdentityKey>> {
|
||||
gateways.into_iter().fold(
|
||||
HashMap::<CountryGroup, Vec<IdentityKey>>::new(),
|
||||
|mut acc, g| {
|
||||
if let Some(ref location) = g.location {
|
||||
let country_code = location.two_letter_iso_country_code.clone();
|
||||
let group_code = CountryGroup::new(country_code.as_str());
|
||||
let gateways = acc.entry(group_code).or_insert_with(Vec::new);
|
||||
gateways.push(g.gateway.identity_key)
|
||||
}
|
||||
acc
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
|
||||
let mixnode_distribution = mixnodes
|
||||
.iter()
|
||||
@@ -261,15 +215,6 @@ fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
|
||||
debug!("Mixnode distribution - {}", mixnode_distribution);
|
||||
}
|
||||
|
||||
fn log_gateway_distribution(gateways: &HashMap<CountryGroup, Vec<IdentityKey>>) {
|
||||
let gateway_distribution = gateways
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}: {}", k, v.len()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
debug!("Gateway distribution - {}", gateway_distribution);
|
||||
}
|
||||
|
||||
fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
|
||||
let mixes = topology.mixes();
|
||||
if mixes.keys().len() < 3 {
|
||||
@@ -303,7 +248,7 @@ impl GeoAwareTopologyProvider {
|
||||
filter_on: GroupBy,
|
||||
) -> GeoAwareTopologyProvider {
|
||||
log::info!(
|
||||
"Creating geo-aware topology provider with filter on {:?}",
|
||||
"Creating geo-aware topology provider with filter on {}",
|
||||
filter_on
|
||||
);
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
@@ -337,13 +282,14 @@ impl GeoAwareTopologyProvider {
|
||||
// Also fetch mixnodes cached by explorer-api, with the purpose of getting their
|
||||
// geolocation.
|
||||
debug!("Fetching mixnodes from explorer-api...");
|
||||
let Some(mixnodes_from_explorer_api) = fetch_mixnodes_from_explorer_api().await else {
|
||||
let explorer_client = create_explorer_client()?;
|
||||
let Ok(mixnodes_from_explorer_api) = explorer_client.get_mixnodes().await else {
|
||||
error!("failed to get mixnodes from explorer-api");
|
||||
return None;
|
||||
};
|
||||
|
||||
debug!("Fetching gateways from explorer-api...");
|
||||
let Some(gateways_from_explorer_api) = fetch_gateways_from_explorer_api().await else {
|
||||
let Ok(gateways_from_explorer_api) = explorer_client.get_gateways().await else {
|
||||
error!("failed to get mixnodes from explorer-api");
|
||||
return None;
|
||||
};
|
||||
@@ -382,29 +328,16 @@ impl GeoAwareTopologyProvider {
|
||||
let mixnode_distribution = group_mixnodes_by_country_code(mixnodes_from_explorer_api);
|
||||
log_mixnode_distribution(&mixnode_distribution);
|
||||
|
||||
let gateway_distribution = group_gateways_by_country_code(gateways_from_explorer_api);
|
||||
log_gateway_distribution(&gateway_distribution);
|
||||
|
||||
let Some(filtered_mixnode_ids) = mixnode_distribution.get(&filter_on) else {
|
||||
error!("no mixnodes found for: {}", filter_on);
|
||||
return None;
|
||||
};
|
||||
|
||||
let Some(filtered_gateway_ids) = gateway_distribution.get(&filter_on) else {
|
||||
error!("no gateways found for: {}", filter_on);
|
||||
return None;
|
||||
};
|
||||
|
||||
let mixnodes = mixnodes
|
||||
.into_iter()
|
||||
.filter(|m| filtered_mixnode_ids.contains(&m.mix_id()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let gateways = gateways
|
||||
.into_iter()
|
||||
.filter(|g| filtered_gateway_ids.contains(g.identity()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let topology = nym_topology_from_detailed(mixnodes, gateways)
|
||||
.filter_system_version(&self.client_version);
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::config::disk_persistence::keys_paths::ClientKeysPaths;
|
||||
use crate::config::disk_persistence::{CommonClientPaths, DEFAULT_GATEWAY_DETAILS_FILENAME};
|
||||
use crate::error::ClientCoreError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
@@ -15,16 +16,17 @@ pub struct CommonClientPathsV1_1_20_2 {
|
||||
}
|
||||
|
||||
impl CommonClientPathsV1_1_20_2 {
|
||||
pub fn upgrade_default(self) -> CommonClientPaths {
|
||||
let data_dir = self
|
||||
.reply_surb_database
|
||||
.parent()
|
||||
.expect("client paths upgrade failure");
|
||||
CommonClientPaths {
|
||||
pub fn upgrade_default(self) -> Result<CommonClientPaths, ClientCoreError> {
|
||||
let data_dir = self.reply_surb_database.parent().ok_or_else(|| {
|
||||
ClientCoreError::UnableToUpgradeConfigFile {
|
||||
new_version: "1.1.20-2".to_string(),
|
||||
}
|
||||
})?;
|
||||
Ok(CommonClientPaths {
|
||||
keys: self.keys,
|
||||
gateway_details: data_dir.join(DEFAULT_GATEWAY_DETAILS_FILENAME),
|
||||
credentials_database: self.credentials_database,
|
||||
reply_surb_database: self.reply_surb_database,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,11 @@ pub struct Config {
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn new<S: Into<String>>(id: S, version: S) -> Self {
|
||||
pub fn new<S1, S2>(id: S1, version: S2) -> Self
|
||||
where
|
||||
S1: Into<String>,
|
||||
S2: Into<String>,
|
||||
{
|
||||
Config {
|
||||
client: Client::new_default(id, version),
|
||||
debug: Default::default(),
|
||||
@@ -287,7 +291,11 @@ pub struct Client {
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub fn new_default<S: Into<String>>(id: S, version: S) -> Self {
|
||||
pub fn new_default<S1, S2>(id: S1, version: S2) -> Self
|
||||
where
|
||||
S1: Into<String>,
|
||||
S2: Into<String>,
|
||||
{
|
||||
let network = NymNetworkDetails::new_mainnet();
|
||||
let nyxd_urls = network
|
||||
.endpoints
|
||||
@@ -498,6 +506,15 @@ pub enum GroupBy {
|
||||
NymAddress(Recipient),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for GroupBy {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
GroupBy::CountryGroup(group) => write!(f, "group: {}", group),
|
||||
GroupBy::NymAddress(address) => write!(f, "address: {}", address),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Topology {
|
||||
fn default() -> Self {
|
||||
Topology {
|
||||
|
||||
@@ -13,8 +13,11 @@ pub enum ClientCoreError {
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
#[error("Gateway client error: {0}")]
|
||||
GatewayClientError(#[from] GatewayClientError),
|
||||
#[error("Gateway client error ({gateway_id}): {source}")]
|
||||
GatewayClientError {
|
||||
gateway_id: String,
|
||||
source: GatewayClientError,
|
||||
},
|
||||
|
||||
#[error("Ed25519 error: {0}")]
|
||||
Ed25519RecoveryError(#[from] Ed25519RecoveryError),
|
||||
@@ -116,6 +119,9 @@ pub enum ClientCoreError {
|
||||
|
||||
#[error("the provided gateway details (for gateway {gateway_id}) do not correspond to the shared keys")]
|
||||
MismatchedGatewayDetails { gateway_id: String },
|
||||
|
||||
#[error("unable to upgrade config file to `{new_version}`")]
|
||||
UnableToUpgradeConfigFile { new_version: String },
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
|
||||
@@ -11,7 +11,6 @@ use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::{filter::VersionFilterable, gateway};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tap::TapFallible;
|
||||
use tungstenite::Message;
|
||||
use url::Url;
|
||||
|
||||
@@ -149,7 +148,7 @@ async fn measure_latency(gateway: &gateway::Node) -> Result<GatewayWithLatency,
|
||||
Ok(GatewayWithLatency::new(gateway, avg))
|
||||
}
|
||||
|
||||
pub(super) async fn choose_gateway_by_latency<R: Rng>(
|
||||
pub async fn choose_gateway_by_latency<R: Rng>(
|
||||
rng: &mut R,
|
||||
gateways: &[gateway::Node],
|
||||
) -> Result<gateway::Node, ClientCoreError> {
|
||||
@@ -209,14 +208,26 @@ pub(super) async fn register_with_gateway(
|
||||
our_identity.clone(),
|
||||
timeout,
|
||||
);
|
||||
gateway_client
|
||||
.establish_connection()
|
||||
.await
|
||||
.tap_err(|_| log::warn!("Failed to establish connection with gateway!"))?;
|
||||
gateway_client.establish_connection().await.map_err(|err| {
|
||||
log::warn!("Failed to establish connection with gateway!");
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway.gateway_id.clone(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
let shared_keys = gateway_client
|
||||
.perform_initial_authentication()
|
||||
.await
|
||||
.tap_err(|_| log::warn!("Failed to register with the gateway!"))?;
|
||||
.map_err(|err| {
|
||||
log::warn!(
|
||||
"Failed to register with the gateway {}!",
|
||||
gateway.gateway_id
|
||||
);
|
||||
ClientCoreError::GatewayClientError {
|
||||
gateway_id: gateway.gateway_id.clone(),
|
||||
source: err,
|
||||
}
|
||||
})?;
|
||||
Ok(RegistrationResult {
|
||||
shared_keys,
|
||||
authenticated_ephemeral_client: Some(gateway_client),
|
||||
|
||||
@@ -106,14 +106,17 @@ pub enum GatewaySetup {
|
||||
/// Should the new gateway be selected based on latency.
|
||||
by_latency: bool,
|
||||
},
|
||||
|
||||
Specified {
|
||||
/// Identity key of the gateway we want to try to use.
|
||||
gateway_identity: IdentityKey,
|
||||
},
|
||||
|
||||
Predefined {
|
||||
/// Full gateway configuration
|
||||
details: PersistedGatewayDetails,
|
||||
},
|
||||
|
||||
ReuseConnection {
|
||||
/// The authenticated ephemeral client that was created during `init`
|
||||
authenticated_ephemeral_client: GatewayClient<InitOnly>,
|
||||
|
||||
@@ -36,7 +36,7 @@ default-features = false
|
||||
|
||||
# non-wasm-only dependencies
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
version = "1.24.1"
|
||||
workspace = true
|
||||
features = ["macros", "rt", "net", "sync", "time"]
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio-stream]
|
||||
@@ -48,15 +48,18 @@ version = "0.14"
|
||||
|
||||
# wasm-only dependencies
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
|
||||
version = "0.2"
|
||||
workspace = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen-futures]
|
||||
version = "0.4"
|
||||
workspace = true
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
|
||||
path = "../../wasm-utils"
|
||||
path = "../../wasm/utils"
|
||||
features = ["websocket"]
|
||||
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.gloo-utils]
|
||||
workspace = true
|
||||
|
||||
# only import it in wasm. Prefer proper tokio timer in non-wasm
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
|
||||
workspace = true
|
||||
@@ -68,7 +71,7 @@ features = ["tokio"]
|
||||
# containing javascript (such as a web browser or node.js).
|
||||
# refer to https://docs.rs/getrandom/0.2.2/getrandom/#webassembly-support for more information
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.getrandom]
|
||||
version = "0.2"
|
||||
workspace = true
|
||||
features = ["js"]
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -141,8 +141,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
async fn _close_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
match std::mem::replace(&mut self.connection, SocketState::NotConnected) {
|
||||
SocketState::Available(mut socket) => {
|
||||
(*socket).close(None).await;
|
||||
SocketState::Available(socket) => {
|
||||
(*socket).close(None, None).await?;
|
||||
Ok(())
|
||||
}
|
||||
SocketState::PartiallyDelegated(_) => {
|
||||
@@ -175,7 +175,9 @@ impl<C, St> GatewayClient<C, St> {
|
||||
pub async fn establish_connection(&mut self) -> Result<(), GatewayClientError> {
|
||||
let ws_stream = match JSWebsocket::new(&self.gateway_address) {
|
||||
Ok(ws_stream) => ws_stream,
|
||||
Err(e) => return Err(GatewayClientError::NetworkErrorWasm(e)),
|
||||
Err(e) => {
|
||||
return Err(GatewayClientError::NetworkErrorWasm(e));
|
||||
}
|
||||
};
|
||||
|
||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use gloo_utils::errors::JsError;
|
||||
use nym_gateway_requests::registration::handshake::error::HandshakeError;
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
use tungstenite::Error as WsError;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::JsValue;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum GatewayClientError {
|
||||
@@ -19,10 +19,9 @@ pub enum GatewayClientError {
|
||||
#[error("There was a network error - {0}")]
|
||||
NetworkError(#[from] WsError),
|
||||
|
||||
// TODO: see if `JsValue` is a reasonable type for this
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[error("There was a network error")]
|
||||
NetworkErrorWasm(JsValue),
|
||||
NetworkErrorWasm(#[from] JsError),
|
||||
|
||||
#[error("Invalid URL - {0}")]
|
||||
InvalidURL(String),
|
||||
|
||||
@@ -144,7 +144,7 @@ pub struct PendingIntervalEventData {
|
||||
#[cw_serde]
|
||||
pub enum PendingIntervalEventKind {
|
||||
/// Request to update cost parameters of given mixnode.
|
||||
#[serde(alias = "PendingIntervalEventKind")]
|
||||
#[serde(alias = "ChangeMixCostParams")]
|
||||
ChangeMixCostParams {
|
||||
/// The id of the mixnode that will have its cost parameters updated.
|
||||
mix_id: MixId,
|
||||
|
||||
@@ -5,9 +5,10 @@ use crate::backends::memory::CoconutCredentialManager;
|
||||
use crate::error::StorageError;
|
||||
use crate::models::CoconutCredential;
|
||||
use crate::storage::Storage;
|
||||
|
||||
use async_trait::async_trait;
|
||||
|
||||
pub type EphemeralCredentialStorage = EphemeralStorage;
|
||||
|
||||
// note that clone here is fine as upon cloning the same underlying pool will be used
|
||||
#[derive(Clone)]
|
||||
pub struct EphemeralStorage {
|
||||
|
||||
@@ -242,8 +242,7 @@ impl PrivateKey {
|
||||
/// Signs text with the provided Ed25519 private key, returning a base58 signature
|
||||
pub fn sign_text(&self, text: &str) -> String {
|
||||
let signature_bytes = self.sign(text.as_ref()).to_bytes();
|
||||
let signature = bs58::encode(signature_bytes).into_string();
|
||||
signature
|
||||
bs58::encode(signature_bytes).into_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "nym-http-requests"
|
||||
version = "0.1.0"
|
||||
description = "Helper library for sending HTTP requesters over the Nym mixnet"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
nym-socks5-requests = { path = "../socks5/requests" }
|
||||
nym-ordered-buffer = { path = "../socks5/ordered-buffer" }
|
||||
nym-service-providers-common = { path = "../../service-providers/common" }
|
||||
bytecodec = "0.4.15"
|
||||
httpcodec = "0.2.3"
|
||||
bytes = "1"
|
||||
http = "0.2.9"
|
||||
thiserror = "1"
|
||||
url = "2"
|
||||
@@ -0,0 +1,23 @@
|
||||
use std::io;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum MixHttpRequestError {
|
||||
#[error("invalid Socks5 response")]
|
||||
InvalidSocks5Response,
|
||||
|
||||
#[error("the received Socks5 response was empty")]
|
||||
EmptySocks5Response,
|
||||
|
||||
#[error("bytecodec Error: {0}")]
|
||||
ByteCodecError(#[from] bytecodec::Error),
|
||||
|
||||
#[error("Url parse error: {0}")]
|
||||
UrlParseError(#[from] url::ParseError),
|
||||
|
||||
#[error("could not resolve socket address from the provided URL")]
|
||||
SocketAddrResolveError {
|
||||
#[source]
|
||||
source: io::Error,
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use bytecodec::bytes::BytesEncoder;
|
||||
use bytecodec::io::IoEncodeExt;
|
||||
use bytecodec::Encode;
|
||||
use httpcodec::{BodyEncoder, Request, RequestEncoder};
|
||||
|
||||
pub mod error;
|
||||
pub mod socks;
|
||||
|
||||
pub fn encode_http_request_as_string(
|
||||
request: Request<Vec<u8>>,
|
||||
) -> Result<String, error::MixHttpRequestError> {
|
||||
// Encode HTTP request as bytes
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request)?;
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf)?;
|
||||
|
||||
Ok(String::from_utf8_lossy(&buf).to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod http_requests_tests {
|
||||
use super::*;
|
||||
use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
|
||||
|
||||
fn create_http_get_request() -> Request<Vec<u8>> {
|
||||
let mut request = Request::new(
|
||||
Method::new("GET").unwrap(),
|
||||
RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
|
||||
HttpVersion::V1_1,
|
||||
b"".to_vec(),
|
||||
);
|
||||
let mut headers = request.header_mut();
|
||||
headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
|
||||
|
||||
request
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http_request_ok() {
|
||||
// Encode HTTP request as bytes
|
||||
let request = create_http_get_request();
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf).unwrap();
|
||||
|
||||
let body_as_string = String::from_utf8(buf).unwrap();
|
||||
|
||||
// replace newlines with \r\n
|
||||
let expected = r"GET /.wellknown/wallet/validators.json HTTP/1.1
|
||||
Host: nymtech.net
|
||||
Content-Length: 0
|
||||
|
||||
"
|
||||
.replace('\n', "\r\n");
|
||||
|
||||
assert_eq!(expected, body_as_string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error;
|
||||
use bytecodec::bytes::BytesEncoder;
|
||||
use bytecodec::bytes::RemainingBytesDecoder;
|
||||
use bytecodec::io::IoEncodeExt;
|
||||
use bytecodec::{DecodeExt, Encode};
|
||||
use httpcodec::{BodyDecoder, ResponseDecoder};
|
||||
use httpcodec::{BodyEncoder, Request, RequestEncoder};
|
||||
use nym_service_providers_common::interface::ProviderInterfaceVersion;
|
||||
use nym_socks5_requests::{SocketData, Socks5ProtocolVersion, Socks5ProviderRequest};
|
||||
|
||||
pub fn encode_http_request_as_socks_send_request(
|
||||
provider_interface: ProviderInterfaceVersion,
|
||||
socks5_protocol: Socks5ProtocolVersion,
|
||||
conn_id: u64,
|
||||
request: Request<Vec<u8>>,
|
||||
seq: Option<u64>,
|
||||
local_closed: bool,
|
||||
) -> Result<nym_socks5_requests::Socks5ProviderRequest, error::MixHttpRequestError> {
|
||||
// Encode HTTP request as bytes
|
||||
let mut encoder = RequestEncoder::new(BodyEncoder::new(BytesEncoder::new()));
|
||||
encoder.start_encoding(request)?;
|
||||
let mut buf = Vec::new();
|
||||
encoder.encode_all(&mut buf)?;
|
||||
|
||||
// Wrap it as SOCKS send request
|
||||
let request_content = nym_socks5_requests::request::Socks5Request::new_send(
|
||||
socks5_protocol,
|
||||
SocketData::new(seq.unwrap_or_default(), conn_id, local_closed, buf),
|
||||
);
|
||||
|
||||
// and wrap it in provider request
|
||||
Ok(Socks5ProviderRequest::new_provider_data(
|
||||
provider_interface,
|
||||
request_content,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MixHttpResponse {
|
||||
// pub connection_id: u64,
|
||||
// #[deprecated]
|
||||
// pub is_closed: bool,
|
||||
pub http_response: httpcodec::Response<Vec<u8>>,
|
||||
// #[deprecated]
|
||||
// pub seq: u64,
|
||||
}
|
||||
|
||||
impl MixHttpResponse {
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<MixHttpResponse, error::MixHttpRequestError> {
|
||||
if b.is_empty() {
|
||||
Err(error::MixHttpRequestError::EmptySocks5Response)
|
||||
} else {
|
||||
let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
|
||||
let http_response = decoder.decode_from_bytes(b)?;
|
||||
|
||||
Ok(MixHttpResponse { http_response })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl TryFrom<Socks5Response> for MixHttpResponse {
|
||||
// type Error = error::MixHttpRequestError;
|
||||
//
|
||||
// fn try_from(value: Socks5Response) -> Result<Self, Self::Error> {
|
||||
// if let Socks5ResponseContent::NetworkData { content } = value.content {
|
||||
// content.try_into()
|
||||
// } else {
|
||||
// Err(error::MixHttpRequestError::InvalidSocks5Response)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl TryFrom<SocketData> for MixHttpResponse {
|
||||
// type Error = error::MixHttpRequestError;
|
||||
//
|
||||
// fn try_from(value: SocketData) -> Result<Self, Self::Error> {
|
||||
// if value.data.is_empty() {
|
||||
// Err(error::MixHttpRequestError::EmptySocks5Response)
|
||||
// } else {
|
||||
// let mut decoder = ResponseDecoder::<BodyDecoder<RemainingBytesDecoder>>::default();
|
||||
// let http_response = decoder.decode_from_bytes(value.data.as_ref())?;
|
||||
//
|
||||
// Ok(MixHttpResponse {
|
||||
// connection_id: value.header.connection_id,
|
||||
// is_closed: value.header.local_socket_closed,
|
||||
// http_response,
|
||||
// seq: value.header.seq,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn decode_socks_response_as_http_response(
|
||||
// socks5_response: Socks5Response,
|
||||
// ) -> Result<MixHttpResponse, error::MixHttpRequestError> {
|
||||
// socks5_response.try_into()
|
||||
// }
|
||||
//
|
||||
// #[cfg(test)]
|
||||
// mod http_requests_tests {
|
||||
// use super::*;
|
||||
// use httpcodec::{HeaderField, HttpVersion, Method, RequestTarget};
|
||||
// use nym_service_providers_common::interface::Serializable;
|
||||
// use nym_socks5_requests::Socks5Response;
|
||||
//
|
||||
// fn create_http_get_request() -> Request<Vec<u8>> {
|
||||
// let mut request = Request::new(
|
||||
// Method::new("GET").unwrap(),
|
||||
// RequestTarget::new("/.wellknown/wallet/validators.json").unwrap(),
|
||||
// HttpVersion::V1_1,
|
||||
// b"".to_vec(),
|
||||
// );
|
||||
// let mut headers = request.header_mut();
|
||||
// headers.add_field(HeaderField::new("Host", "nymtech.net").unwrap());
|
||||
//
|
||||
// request
|
||||
// }
|
||||
//
|
||||
// fn create_socks5_request_buffer() -> Vec<u8> {
|
||||
// let request = create_http_get_request();
|
||||
// let socks5_request = encode_http_request_as_socks_send_request(
|
||||
// ProviderInterfaceVersion::new_current(),
|
||||
// Socks5ProtocolVersion::new_current(),
|
||||
// 99u64,
|
||||
// request,
|
||||
// Some(42u64),
|
||||
// true,
|
||||
// )
|
||||
// .unwrap();
|
||||
// socks5_request.into_bytes()
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_http_request_content_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// // HTTP request string content is as expected
|
||||
// assert_eq!(
|
||||
// [71u8, 69u8, 84u8, 32u8, 47u8, 46u8, 119u8, 101u8],
|
||||
// buffer[19..27]
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// /// This test will fail if the framing of the request buffer changes, e.g. when OrderedMessage
|
||||
// /// changes to have the `index` value as a field, instead of packed with the `data`
|
||||
// #[test]
|
||||
// fn request_size_as_expected_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
// // println!("{:?}", buffer) // uncomment and run `cargo test -- --nocapture` to view
|
||||
//
|
||||
// assert_eq!(108, buffer.len()); // version set to SOCKS5
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_socks5_headers_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// assert_eq!(5u8, buffer[0]); // version set to SOCKS5
|
||||
// assert_eq!(1u8, buffer[1]); // type is SEND
|
||||
// assert_eq!(99u8, buffer[9]); // ConnectionId is correct
|
||||
// assert_eq!(1u8, buffer[10]); // local_closed is true
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn request_ordered_message_ok() {
|
||||
// let buffer = create_socks5_request_buffer();
|
||||
//
|
||||
// // OrderedMessage index is correct
|
||||
// assert_eq!(42u8, buffer[18]);
|
||||
// }
|
||||
//
|
||||
// fn create_socks_response() -> Socks5Response {
|
||||
// // HTTP response is just a string
|
||||
// let http_response_string = "HTTP/1.1 200 OK\r\nServer: foo/0.0.1\r\n\r\n";
|
||||
//
|
||||
// let data = http_response_string.as_bytes().to_vec();
|
||||
//
|
||||
// // wrap in `NetworkData`, then Socks5Response
|
||||
// Socks5Response::new(
|
||||
// Socks5ProtocolVersion::new_current(),
|
||||
// Socks5ResponseContent::NetworkData {
|
||||
// content: SocketData::new(42, 99u64, false, data),
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// /// This test will fail is anything in the framing of the socks5_response byte
|
||||
// /// representation changes
|
||||
// #[test]
|
||||
// fn response_byte_size_is_as_expected() {
|
||||
// let socks5_response = create_socks_response();
|
||||
// let buf = socks5_response.into_bytes();
|
||||
//
|
||||
// assert_eq!(57, buf.len());
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn response_parses() {
|
||||
// unimplemented!()
|
||||
// // let socks5_response = create_socks_response();
|
||||
// // let response = decode_socks_response_as_http_response(socks5_response).unwrap();
|
||||
// //
|
||||
// // assert_eq!(42u64, response.seq); // OrderedMessage index as expected
|
||||
// // assert_eq!(HttpVersion::V1_1, response.http_response.http_version());
|
||||
// // assert_eq!(200u16, response.http_response.status_code().as_u16());
|
||||
// // assert_eq!(
|
||||
// // "foo/0.0.1",
|
||||
// // response.http_response.header().get_field("Server").unwrap()
|
||||
// // );
|
||||
// }
|
||||
// }
|
||||
@@ -197,12 +197,12 @@ impl VerlocMeasurer {
|
||||
config.packet_timeout,
|
||||
config.connection_timeout,
|
||||
config.delay_between_packets,
|
||||
shutdown_listener.clone(),
|
||||
shutdown_listener.clone().named("VerlocPacketSender"),
|
||||
)),
|
||||
packet_listener: Arc::new(PacketListener::new(
|
||||
config.listening_address,
|
||||
Arc::clone(&identity),
|
||||
shutdown_listener.clone(),
|
||||
shutdown_listener.clone().named("VerlocPacketListener"),
|
||||
)),
|
||||
shutdown_listener,
|
||||
currently_used_api: 0,
|
||||
@@ -241,7 +241,7 @@ impl VerlocMeasurer {
|
||||
return MeasurementOutcome::Done;
|
||||
}
|
||||
|
||||
let mut shutdown_listener = self.shutdown_listener.clone();
|
||||
let mut shutdown_listener = self.shutdown_listener.clone().named("VerlocMeasurement");
|
||||
shutdown_listener.mark_as_success();
|
||||
|
||||
for chunk in nodes_to_test.chunks(self.config.tested_nodes_batch_size) {
|
||||
|
||||
@@ -83,7 +83,7 @@ impl PacketSender {
|
||||
self: Arc<Self>,
|
||||
tested_node: TestedNode,
|
||||
) -> Result<Measurement, RttError> {
|
||||
let mut shutdown_listener = self.shutdown_listener.clone();
|
||||
let mut shutdown_listener = self.shutdown_listener.fork(tested_node.address.to_string());
|
||||
shutdown_listener.mark_as_success();
|
||||
|
||||
let mut conn = match tokio::time::timeout(
|
||||
|
||||
@@ -27,4 +27,4 @@ workspace = true
|
||||
|
||||
## wasm-only dependencies
|
||||
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-utils]
|
||||
path = "../wasm-utils"
|
||||
path = "../wasm/utils"
|
||||
|
||||
@@ -9,6 +9,9 @@ pub mod receiver;
|
||||
pub mod tester;
|
||||
|
||||
pub use message::{Empty, TestMessage};
|
||||
pub use nym_sphinx::{
|
||||
chunking::fragment::FragmentIdentifier, params::PacketSize, preparer::PreparedFragment,
|
||||
};
|
||||
pub use tester::NodeTester;
|
||||
|
||||
// it feels wrong to redefine it, but I don't want to import the whole of contract commons just for this one type
|
||||
|
||||
@@ -15,3 +15,4 @@ thiserror = "1.0.37"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.7"
|
||||
nym-crypto = { path = "../../crypto", features = ["rand"] }
|
||||
@@ -39,7 +39,7 @@ pub enum RecipientFormattingError {
|
||||
}
|
||||
|
||||
// TODO: this should a different home... somewhere, but where?
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct Recipient {
|
||||
client_identity: ClientIdentity,
|
||||
client_encryption_key: ClientEncryptionKey,
|
||||
@@ -226,6 +226,13 @@ impl std::fmt::Display for Recipient {
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Recipient {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
// use the Display implementation
|
||||
<Self as std::fmt::Display>::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Recipient {
|
||||
type Err = RecipientFormattingError;
|
||||
|
||||
|
||||
@@ -243,7 +243,7 @@ mod message_receiver {
|
||||
)
|
||||
.unwrap(),
|
||||
layer: Layer::One,
|
||||
version: "0.8.0-dev".to_string(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
@@ -263,7 +263,7 @@ mod message_receiver {
|
||||
)
|
||||
.unwrap(),
|
||||
layer: Layer::Two,
|
||||
version: "0.8.0-dev".to_string(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
@@ -283,7 +283,7 @@ mod message_receiver {
|
||||
)
|
||||
.unwrap(),
|
||||
layer: Layer::Three,
|
||||
version: "0.8.0-dev".to_string(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
);
|
||||
|
||||
@@ -303,7 +303,7 @@ mod message_receiver {
|
||||
"EB42xvMFMD5rUCstE2CDazgQQJ22zLv8SPm1Luxni44c",
|
||||
)
|
||||
.unwrap(),
|
||||
version: "0.8.0-dev".to_string(),
|
||||
version: "0.8.0-dev".into(),
|
||||
}],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -110,10 +110,10 @@ pub struct Socks5 {
|
||||
/// The version of the 'service provider' this client is going to use in its communication with the
|
||||
/// specified socks5 provider.
|
||||
// if in doubt, use the legacy version as initially nobody will be using the updated binaries
|
||||
#[serde(default = "ProviderInterfaceVersion::new_legacy")]
|
||||
#[serde(default)]
|
||||
pub provider_interface_version: ProviderInterfaceVersion,
|
||||
|
||||
#[serde(default = "Socks5ProtocolVersion::new_legacy")]
|
||||
#[serde(default)]
|
||||
pub socks5_protocol_version: Socks5ProtocolVersion,
|
||||
|
||||
/// Specifies whether this client is going to use an anonymous sender tag for communication with the service provider.
|
||||
@@ -147,7 +147,7 @@ impl Socks5 {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Socks5Debug {
|
||||
/// Number of reply SURBs attached to each `Request::Connect` message.
|
||||
pub connection_start_surbs: u32,
|
||||
|
||||
@@ -26,6 +26,7 @@ use nym_sphinx::params::PacketType;
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub mod config;
|
||||
pub mod error;
|
||||
@@ -57,6 +58,9 @@ pub struct NymClient<S> {
|
||||
storage: S,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
|
||||
/// Optional path to a .json file containing standalone network details.
|
||||
custom_mixnet: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl<S> NymClient<S>
|
||||
@@ -68,11 +72,12 @@ where
|
||||
<S::GatewayDetailsStore as GatewayDetailsStore>::StorageError: Sync + Send,
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync,
|
||||
{
|
||||
pub fn new(config: Config, storage: S) -> Self {
|
||||
pub fn new(config: Config, storage: S, custom_mixnet: Option<PathBuf>) -> Self {
|
||||
NymClient {
|
||||
config,
|
||||
storage,
|
||||
setup_method: GatewaySetup::MustLoad,
|
||||
custom_mixnet,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,10 +215,14 @@ where
|
||||
Some(default_query_dkg_client_from_config(&self.config.base))
|
||||
};
|
||||
|
||||
let base_builder =
|
||||
let mut base_builder =
|
||||
BaseClientBuilder::new(&self.config.base, self.storage, dkg_query_client)
|
||||
.with_gateway_setup(self.setup_method);
|
||||
|
||||
if let Some(custom_mixnet) = &self.custom_mixnet {
|
||||
base_builder = base_builder.with_stored_topology(custom_mixnet)?;
|
||||
}
|
||||
|
||||
let packet_type = self.config.base.debug.traffic.packet_type;
|
||||
let mut started_client = base_builder.start_base().await?;
|
||||
let self_address = started_client.address;
|
||||
|
||||
@@ -78,6 +78,16 @@ impl OrderedMessageBuffer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks whether the buffer contains enough contiguous regions to read until the specified target sequence.
|
||||
pub fn can_read_until(&self, target: u64) -> bool {
|
||||
for seq in self.next_sequence..=target {
|
||||
if !self.messages.contains_key(&seq) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns `Option<Vec<u8>>` where it's `Some(bytes)` if there is gapless
|
||||
/// ordered data in the buffer, and `None` if the buffer is empty or has
|
||||
/// gaps in the contained data.
|
||||
@@ -110,7 +120,7 @@ impl OrderedMessageBuffer {
|
||||
);
|
||||
Some(ReadContiguousData {
|
||||
data: contiguous_messages,
|
||||
last_sequence: seq,
|
||||
last_sequence: self.next_sequence - 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ impl Controller {
|
||||
|
||||
if let Some(payload) = active_connection.read_from_buf() {
|
||||
if let Some(closed_at_index) = active_connection.closed_at_index {
|
||||
if payload.last_sequence > closed_at_index {
|
||||
if payload.last_sequence >= closed_at_index {
|
||||
active_connection.is_closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use nym_service_providers_common::interface::{Serializable, ServiceProviderReque
|
||||
use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use tap::TapFallible;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -75,7 +76,7 @@ impl RequestDeserializationError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ConnectRequest {
|
||||
// TODO: is connection_id redundant now?
|
||||
pub conn_id: ConnectionId,
|
||||
@@ -83,6 +84,19 @@ pub struct ConnectRequest {
|
||||
pub return_address: Option<Recipient>,
|
||||
}
|
||||
|
||||
impl Debug for ConnectRequest {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ConnectRequest")
|
||||
.field("conn_id", &self.conn_id)
|
||||
.field("remote_addr", &self.remote_addr)
|
||||
.field(
|
||||
"return_address",
|
||||
&self.return_address.map(|r| r.to_string()),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct SendRequest {
|
||||
pub data: SocketData,
|
||||
|
||||
@@ -64,7 +64,7 @@ pub enum ResponseDeserializationError {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Socks5Response {
|
||||
pub protocol_version: Socks5ProtocolVersion,
|
||||
pub content: Socks5ResponseContent,
|
||||
|
||||
+115
-11
@@ -5,6 +5,7 @@ use std::future::Future;
|
||||
use std::{error::Error, time::Duration};
|
||||
|
||||
use futures::{future::pending, FutureExt, SinkExt, StreamExt};
|
||||
use log::{log, Level};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc,
|
||||
@@ -23,10 +24,18 @@ pub type SentStatus = Box<dyn Error + Send + Sync>;
|
||||
pub type StatusSender = futures::channel::mpsc::Sender<SentStatus>;
|
||||
pub type StatusReceiver = futures::channel::mpsc::Receiver<SentStatus>;
|
||||
|
||||
fn try_recover_name(name: &Option<String>) -> String {
|
||||
if let Some(name) = name {
|
||||
name.clone()
|
||||
} else {
|
||||
"unknown".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum TaskError {
|
||||
#[error("Task halted unexpectedly")]
|
||||
UnexpectedHalt,
|
||||
#[error("Task '{}' halted unexpectedly", try_recover_name(.shutdown_name))]
|
||||
UnexpectedHalt { shutdown_name: Option<String> },
|
||||
}
|
||||
|
||||
// TODO: possibly we should create a `Status` trait instead of reusing `Error`
|
||||
@@ -40,6 +49,9 @@ pub enum TaskStatus {
|
||||
/// shutdown. Keeps track of if task stop unexpectedly, such as in a panic.
|
||||
#[derive(Debug)]
|
||||
pub struct TaskManager {
|
||||
// optional name assigned to the task manager that all subscribed task clients will inherit
|
||||
name: Option<String>,
|
||||
|
||||
// These channels have the dual purpose of signalling it's time to shutdown, but also to keep
|
||||
// track of which tasks we are still waiting for.
|
||||
notify_tx: watch::Sender<()>,
|
||||
@@ -72,6 +84,7 @@ impl Default for TaskManager {
|
||||
// there is a listener.
|
||||
let (task_status_tx, task_status_rx) = futures::channel::mpsc::channel(128);
|
||||
Self {
|
||||
name: None,
|
||||
notify_tx,
|
||||
notify_rx: Some(notify_rx),
|
||||
shutdown_timer_secs: DEFAULT_SHUTDOWN_TIMER_SECS,
|
||||
@@ -93,6 +106,12 @@ impl TaskManager {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
|
||||
self.name = Some(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn catch_interrupt(mut self) -> Result<(), SentError> {
|
||||
let res = crate::wait_for_signal_and_error(&mut self).await;
|
||||
@@ -107,7 +126,7 @@ impl TaskManager {
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> TaskClient {
|
||||
TaskClient::new(
|
||||
let task_client = TaskClient::new(
|
||||
self.notify_rx
|
||||
.as_ref()
|
||||
.expect("Unable to subscribe to shutdown notifier that is already shutdown")
|
||||
@@ -115,7 +134,13 @@ impl TaskManager {
|
||||
self.task_return_error_tx.clone(),
|
||||
self.task_drop_tx.clone(),
|
||||
self.task_status_tx.clone(),
|
||||
)
|
||||
);
|
||||
|
||||
if let Some(name) = &self.name {
|
||||
task_client.named(format!("{name}-child"))
|
||||
} else {
|
||||
task_client
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signal_shutdown(&self) -> Result<(), SendError<()>> {
|
||||
@@ -207,8 +232,11 @@ impl TaskManager {
|
||||
|
||||
/// Listen for shutdown notifications, and can send error and status messages back to the
|
||||
/// `TaskManager`
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct TaskClient {
|
||||
// optional name assigned to the shutdown handle
|
||||
name: Option<String>,
|
||||
|
||||
// If a shutdown notification has been registered
|
||||
shutdown: bool,
|
||||
|
||||
@@ -229,7 +257,35 @@ pub struct TaskClient {
|
||||
mode: ClientOperatingMode,
|
||||
}
|
||||
|
||||
impl Clone for TaskClient {
|
||||
fn clone(&self) -> Self {
|
||||
// make sure to not accidentally overflow the stack if we keep cloning the handle
|
||||
let name = if let Some(name) = &self.name {
|
||||
if name != Self::OVERFLOW_NAME && name.len() < Self::MAX_NAME_LENGTH {
|
||||
Some(format!("{name}-child"))
|
||||
} else {
|
||||
Some(Self::OVERFLOW_NAME.to_string())
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
TaskClient {
|
||||
name,
|
||||
shutdown: self.shutdown,
|
||||
notify: self.notify.clone(),
|
||||
return_error: self.return_error.clone(),
|
||||
drop_error: self.drop_error.clone(),
|
||||
status_msg: self.status_msg.clone(),
|
||||
mode: self.mode.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TaskClient {
|
||||
const MAX_NAME_LENGTH: usize = 128;
|
||||
const OVERFLOW_NAME: &'static str = "reached maximum TaskClient children name depth";
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5);
|
||||
|
||||
@@ -240,6 +296,7 @@ impl TaskClient {
|
||||
status_msg: StatusSender,
|
||||
) -> TaskClient {
|
||||
TaskClient {
|
||||
name: None,
|
||||
shutdown: false,
|
||||
notify,
|
||||
return_error,
|
||||
@@ -249,6 +306,41 @@ impl TaskClient {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: not convinced about the name...
|
||||
pub fn fork<S: Into<String>>(&self, child_suffix: S) -> Self {
|
||||
let mut child = self.clone();
|
||||
let suffix = child_suffix.into();
|
||||
let child_name = if let Some(base) = &self.name {
|
||||
format!("{base}-{suffix}")
|
||||
} else {
|
||||
format!("unknown-{suffix}")
|
||||
};
|
||||
|
||||
child.name = Some(child_name);
|
||||
child
|
||||
}
|
||||
|
||||
// just a convenience wrapper for including the shutdown name when logging
|
||||
// I really didn't want to create macros for that... because that seemed like an overkill.
|
||||
// but I guess it would have resolved needing to call `format!` for additional msg arguments
|
||||
fn log<S: Into<String>>(&self, level: Level, msg: S) {
|
||||
let msg = msg.into();
|
||||
|
||||
let target = &if let Some(name) = &self.name {
|
||||
format!("TaskClient-{name}")
|
||||
} else {
|
||||
"unnamed-TaskClient".to_string()
|
||||
};
|
||||
|
||||
log!(target: target, level, "{msg}")
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn named<S: Into<String>>(mut self, name: S) -> Self {
|
||||
self.name = Some(name.into());
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn run_future<Fut, T>(&mut self, fut: Fut) -> Option<T>
|
||||
where
|
||||
Fut: Future<Output = T>,
|
||||
@@ -267,6 +359,7 @@ impl TaskClient {
|
||||
let (task_drop_tx, _task_drop_rx) = mpsc::unbounded_channel();
|
||||
let (task_status_tx, _task_status_rx) = futures::channel::mpsc::channel(128);
|
||||
TaskClient {
|
||||
name: None,
|
||||
shutdown: false,
|
||||
notify: notify_rx,
|
||||
return_error: task_halt_tx,
|
||||
@@ -310,6 +403,7 @@ impl TaskClient {
|
||||
|
||||
pub async fn recv_timeout(&mut self) {
|
||||
if self.mode.is_dummy() {
|
||||
#[cfg_attr(target_arch = "wasm32", allow(clippy::needless_return))]
|
||||
return pending().await;
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -336,8 +430,9 @@ impl TaskClient {
|
||||
has_changed
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Polling shutdown failed: {err}");
|
||||
log::error!("Assuming this means we should shutdown...");
|
||||
self.log(Level::Error, format!("Polling shutdown failed: {err}"));
|
||||
self.log(Level::Error, "Assuming this means we should shutdown...");
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
@@ -354,9 +449,11 @@ impl TaskClient {
|
||||
if self.mode.is_dummy() {
|
||||
return;
|
||||
}
|
||||
log::trace!("Notifying we stopped: {err}");
|
||||
|
||||
self.log(Level::Trace, format!("Notifying we stopped: {err}"));
|
||||
|
||||
if self.return_error.send(err).is_err() {
|
||||
log::error!("Failed to send back error message");
|
||||
self.log(Level::Error, "failed to send back error message");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,13 +470,20 @@ impl TaskClient {
|
||||
impl Drop for TaskClient {
|
||||
fn drop(&mut self) {
|
||||
if !self.mode.should_signal_on_drop() {
|
||||
self.log(Level::Debug, "the task client is getting dropped");
|
||||
return;
|
||||
} else {
|
||||
self.log(Level::Info, "the task client is getting dropped");
|
||||
}
|
||||
|
||||
if !self.is_shutdown_poll() {
|
||||
log::trace!("Notifying stop on unexpected drop");
|
||||
self.log(Level::Trace, "Notifying stop on unexpected drop");
|
||||
|
||||
// If we can't send, well then there is not much to do
|
||||
self.drop_error
|
||||
.send(Box::new(TaskError::UnexpectedHalt))
|
||||
.send(Box::new(TaskError::UnexpectedHalt {
|
||||
shutdown_name: self.name.clone(),
|
||||
}))
|
||||
.ok();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,15 @@ log = { workspace = true }
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
thiserror = "1.0.37"
|
||||
async-trait = { workspace = true, optional = true }
|
||||
semver = "0.11"
|
||||
|
||||
# 'serializable' feature
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
|
||||
# 'wasm-serde-types' feature
|
||||
tsify = { workspace = true, features = ["js"], optional = true }
|
||||
wasm-bindgen = { workspace = true, optional = true }
|
||||
|
||||
## internal
|
||||
nym-crypto = { path = "../crypto", features = ["sphinx", "outfox"] }
|
||||
@@ -26,6 +35,14 @@ nym-sphinx-types = { path = "../nymsphinx/types", features = ["sphinx", "outfox"
|
||||
nym-sphinx-routing = { path = "../nymsphinx/routing" }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
# 'serializable' feature
|
||||
nym-config = { path = "../config", optional = true }
|
||||
|
||||
# 'wasm-serde-types' feature
|
||||
wasm-utils = { path = "../wasm/utils", default-features = false, optional = true }
|
||||
|
||||
[features]
|
||||
default = ["provider-trait"]
|
||||
provider-trait = ["async-trait"]
|
||||
provider-trait = ["async-trait"]
|
||||
wasm-serde-types = ["tsify", "wasm-bindgen", "wasm-utils"]
|
||||
serializable = ["serde", "nym-config", "serde_json"]
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{filter, NetworkAddress};
|
||||
use crate::{filter, NetworkAddress, NodeVersion};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_mixnet_contract_common::GatewayBond;
|
||||
use nym_sphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress};
|
||||
@@ -38,7 +38,7 @@ pub struct Node {
|
||||
pub clients_port: u16,
|
||||
pub identity_key: identity::PublicKey,
|
||||
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
|
||||
pub version: String,
|
||||
pub version: NodeVersion,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
@@ -83,7 +83,8 @@ impl fmt::Display for Node {
|
||||
|
||||
impl filter::Versioned for Node {
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
// TODO: return semver instead
|
||||
self.version.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +115,7 @@ impl<'a> TryFrom<&'a GatewayBond> for Node {
|
||||
clients_port: bond.gateway.clients_port,
|
||||
identity_key: identity::PublicKey::from_base58_string(&bond.gateway.identity_key)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&bond.gateway.sphinx_key)?,
|
||||
version: bond.gateway.version.clone(),
|
||||
version: bond.gateway.version.as_str().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::filter::VersionFilterable;
|
||||
pub use error::NymTopologyError;
|
||||
use log::warn;
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
@@ -16,6 +17,9 @@ use std::io;
|
||||
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "serializable")]
|
||||
use ::serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
pub mod error;
|
||||
pub mod filter;
|
||||
pub mod gateway;
|
||||
@@ -25,7 +29,44 @@ pub mod random_route_provider;
|
||||
#[cfg(feature = "provider-trait")]
|
||||
pub mod provider_trait;
|
||||
|
||||
pub use error::NymTopologyError;
|
||||
#[cfg(feature = "serializable")]
|
||||
pub(crate) mod serde;
|
||||
|
||||
#[cfg(feature = "serializable")]
|
||||
pub use crate::serde::{SerializableNymTopology, SerializableTopologyError};
|
||||
|
||||
#[cfg(feature = "provider-trait")]
|
||||
pub use provider_trait::{HardcodedTopologyProvider, TopologyProvider};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub enum NodeVersion {
|
||||
Explicit(semver::Version),
|
||||
|
||||
#[default]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
// this is only implemented for backwards compatibility so we wouldn't need to change everything at once
|
||||
// (also I intentionally implemented `ToString` as opposed to `Display`)
|
||||
impl ToString for NodeVersion {
|
||||
fn to_string(&self) -> String {
|
||||
match self {
|
||||
NodeVersion::Explicit(semver) => semver.to_string(),
|
||||
NodeVersion::Unknown => String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is also for backwards compat.
|
||||
impl<'a> From<&'a str> for NodeVersion {
|
||||
fn from(value: &'a str) -> Self {
|
||||
if let Ok(semver) = value.parse() {
|
||||
NodeVersion::Explicit(semver)
|
||||
} else {
|
||||
NodeVersion::Unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NetworkAddress {
|
||||
@@ -78,6 +119,12 @@ impl NymTopology {
|
||||
NymTopology { mixes, gateways }
|
||||
}
|
||||
|
||||
#[cfg(feature = "serializable")]
|
||||
pub fn new_from_file<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
|
||||
let file = std::fs::File::open(path)?;
|
||||
serde_json::from_reader(file).map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn from_detailed(
|
||||
mix_details: Vec<MixNodeDetails>,
|
||||
gateway_bonds: Vec<GatewayBond>,
|
||||
@@ -139,6 +186,10 @@ impl NymTopology {
|
||||
&self.gateways
|
||||
}
|
||||
|
||||
pub fn get_gateways(&self) -> Vec<gateway::Node> {
|
||||
self.gateways.clone()
|
||||
}
|
||||
|
||||
pub fn get_gateway(&self, gateway_identity: &NodeIdentity) -> Option<&gateway::Node> {
|
||||
self.gateways
|
||||
.iter()
|
||||
@@ -318,6 +369,27 @@ impl NymTopology {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serializable")]
|
||||
impl Serialize for NymTopology {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
crate::serde::SerializableNymTopology::from(self.clone()).serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serializable")]
|
||||
impl<'de> Deserialize<'de> for NymTopology {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let serializable = crate::serde::SerializableNymTopology::deserialize(deserializer)?;
|
||||
serializable.try_into().map_err(::serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nym_topology_from_detailed(
|
||||
mix_details: Vec<MixNodeDetails>,
|
||||
gateway_bonds: Vec<GatewayBond>,
|
||||
@@ -390,7 +462,7 @@ mod converting_mixes_to_vec {
|
||||
)
|
||||
.unwrap(),
|
||||
layer: Layer::One,
|
||||
version: "0.x.0".to_string(),
|
||||
version: "0.2.0".into(),
|
||||
};
|
||||
|
||||
let node2 = mix::Node {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{filter, NetworkAddress};
|
||||
use crate::{filter, NetworkAddress, NodeVersion};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
pub use nym_mixnet_contract_common::Layer;
|
||||
use nym_mixnet_contract_common::{MixId, MixNodeBond};
|
||||
@@ -39,7 +39,7 @@ pub struct Node {
|
||||
pub identity_key: identity::PublicKey,
|
||||
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
|
||||
pub layer: Layer,
|
||||
pub version: String,
|
||||
pub version: NodeVersion,
|
||||
}
|
||||
|
||||
impl Node {
|
||||
@@ -66,7 +66,8 @@ impl Node {
|
||||
|
||||
impl filter::Versioned for Node {
|
||||
fn version(&self) -> String {
|
||||
self.version.clone()
|
||||
// TODO: return semver instead
|
||||
self.version.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ impl<'a> TryFrom<&'a MixNodeBond> for Node {
|
||||
identity_key: identity::PublicKey::from_base58_string(&bond.mix_node.identity_key)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&bond.mix_node.sphinx_key)?,
|
||||
layer: bond.layer,
|
||||
version: bond.mix_node.version.clone(),
|
||||
version: bond.mix_node.version.as_str().into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@ pub struct HardcodedTopologyProvider {
|
||||
}
|
||||
|
||||
impl HardcodedTopologyProvider {
|
||||
#[cfg(feature = "serializable")]
|
||||
pub fn new_from_file<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
|
||||
NymTopology::new_from_file(path).map(Self::new)
|
||||
}
|
||||
|
||||
pub fn new(topology: NymTopology) -> Self {
|
||||
HardcodedTopologyProvider { topology }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::gateway::GatewayConversionError;
|
||||
use crate::mix::MixnodeConversionError;
|
||||
use crate::{gateway, mix, MixLayer, NymTopology};
|
||||
use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::BTreeMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[cfg(feature = "wasm-serde-types")]
|
||||
use tsify::Tsify;
|
||||
|
||||
#[cfg(feature = "wasm-serde-types")]
|
||||
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
|
||||
|
||||
#[cfg(feature = "wasm-serde-types")]
|
||||
use wasm_utils::error::simple_js_error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SerializableTopologyError {
|
||||
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
|
||||
InvalidMixLayer { value: u8 },
|
||||
|
||||
#[error(transparent)]
|
||||
GatewayConversion(#[from] GatewayConversionError),
|
||||
|
||||
#[error(transparent)]
|
||||
MixnodeConversion(#[from] MixnodeConversionError),
|
||||
|
||||
#[error("The provided mixnode map was malformed: {msg}")]
|
||||
MalformedMixnodeMap { msg: String },
|
||||
|
||||
#[error("The provided gateway list was malformed: {msg}")]
|
||||
MalformedGatewayList { msg: String },
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm-serde-types")]
|
||||
impl From<SerializableTopologyError> for JsValue {
|
||||
fn from(value: SerializableTopologyError) -> Self {
|
||||
simple_js_error(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SerializableNymTopology {
|
||||
pub mixnodes: BTreeMap<MixLayer, Vec<SerializableMixNode>>,
|
||||
pub gateways: Vec<SerializableGateway>,
|
||||
}
|
||||
|
||||
impl TryFrom<SerializableNymTopology> for NymTopology {
|
||||
type Error = SerializableTopologyError;
|
||||
|
||||
fn try_from(value: SerializableNymTopology) -> Result<Self, Self::Error> {
|
||||
let mut converted_mixes = BTreeMap::new();
|
||||
|
||||
for (layer, nodes) in value.mixnodes {
|
||||
let layer_nodes = nodes
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
converted_mixes.insert(layer, layer_nodes);
|
||||
}
|
||||
|
||||
let gateways = value
|
||||
.gateways
|
||||
.into_iter()
|
||||
.map(TryInto::try_into)
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(NymTopology::new(converted_mixes, gateways))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymTopology> for SerializableNymTopology {
|
||||
fn from(value: NymTopology) -> Self {
|
||||
SerializableNymTopology {
|
||||
mixnodes: value
|
||||
.mixes()
|
||||
.iter()
|
||||
.map(|(&l, nodes)| (l, nodes.iter().map(Into::into).collect()))
|
||||
.collect(),
|
||||
gateways: value.gateways().iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SerializableMixNode {
|
||||
// this is a `MixId` but due to typescript issue, we're using u32 directly.
|
||||
#[serde(alias = "mix_id")]
|
||||
pub mix_id: u32,
|
||||
|
||||
pub owner: String,
|
||||
|
||||
pub host: String,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "mix_port")]
|
||||
pub mix_port: Option<u16>,
|
||||
|
||||
#[serde(alias = "identity_key")]
|
||||
pub identity_key: String,
|
||||
|
||||
#[serde(alias = "sphinx_key")]
|
||||
pub sphinx_key: String,
|
||||
|
||||
// this is a `MixLayer` but due to typescript issue, we're using u8 directly.
|
||||
pub layer: u8,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<SerializableMixNode> for mix::Node {
|
||||
type Error = SerializableTopologyError;
|
||||
|
||||
fn try_from(value: SerializableMixNode) -> Result<Self, Self::Error> {
|
||||
let host = mix::Node::parse_host(&value.host)?;
|
||||
|
||||
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
|
||||
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = mix::Node::extract_mix_host(&host, mix_port)?;
|
||||
|
||||
Ok(mix::Node {
|
||||
mix_id: value.mix_id,
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(MixnodeConversionError::from)?,
|
||||
layer: mix::Layer::try_from(value.layer)
|
||||
.map_err(|_| SerializableTopologyError::InvalidMixLayer { value: value.layer })?,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mix::Node> for SerializableMixNode {
|
||||
fn from(value: &'a mix::Node) -> Self {
|
||||
SerializableMixNode {
|
||||
mix_id: value.mix_id,
|
||||
owner: value.owner.clone(),
|
||||
host: value.host.to_string(),
|
||||
mix_port: Some(value.mix_host.port()),
|
||||
identity_key: value.identity_key.to_base58_string(),
|
||||
sphinx_key: value.sphinx_key.to_base58_string(),
|
||||
layer: value.layer.into(),
|
||||
version: Some(value.version.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct SerializableGateway {
|
||||
pub owner: String,
|
||||
|
||||
pub host: String,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "mix_port")]
|
||||
pub mix_port: Option<u16>,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
#[serde(alias = "clients_port")]
|
||||
pub clients_port: Option<u16>,
|
||||
|
||||
#[serde(alias = "identity_key")]
|
||||
pub identity_key: String,
|
||||
|
||||
#[serde(alias = "sphinx_key")]
|
||||
pub sphinx_key: String,
|
||||
|
||||
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
|
||||
pub version: Option<String>,
|
||||
}
|
||||
|
||||
impl TryFrom<SerializableGateway> for gateway::Node {
|
||||
type Error = SerializableTopologyError;
|
||||
|
||||
fn try_from(value: SerializableGateway) -> Result<Self, Self::Error> {
|
||||
let host = gateway::Node::parse_host(&value.host)?;
|
||||
|
||||
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
|
||||
let clients_port = value.clients_port.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT);
|
||||
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
|
||||
|
||||
// try to completely resolve the host in the mix situation to avoid doing it every
|
||||
// single time we want to construct a path
|
||||
let mix_host = gateway::Node::extract_mix_host(&host, mix_port)?;
|
||||
|
||||
Ok(gateway::Node {
|
||||
owner: value.owner,
|
||||
host,
|
||||
mix_host,
|
||||
clients_port,
|
||||
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
|
||||
.map_err(GatewayConversionError::from)?,
|
||||
version,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a gateway::Node> for SerializableGateway {
|
||||
fn from(value: &'a gateway::Node) -> Self {
|
||||
SerializableGateway {
|
||||
owner: value.owner.clone(),
|
||||
host: value.host.to_string(),
|
||||
mix_port: Some(value.mix_host.port()),
|
||||
clients_port: Some(value.clients_port),
|
||||
identity_key: value.identity_key.to_base58_string(),
|
||||
sphinx_key: value.sphinx_key.to_base58_string(),
|
||||
version: Some(value.version.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
[package]
|
||||
name = "wasm-utils"
|
||||
version = "0.1.0"
|
||||
authors = ["Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures = { workspace = true }
|
||||
js-sys = "^0.3.51"
|
||||
wasm-bindgen = "=0.2.83"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
serde-wasm-bindgen = { version = "0.5.0", optional = true }
|
||||
getrandom = { version="0.2", features=["js"], optional = true }
|
||||
indexed_db_futures = { version = " 0.3.0", optional = true }
|
||||
thiserror = { workspace = true, optional = true }
|
||||
|
||||
nym-store-cipher = { path = "../store-cipher", features = ["json"], optional = true }
|
||||
|
||||
# we don't want entire tokio-tungstenite, tungstenite itself is just fine - we just want message and error enums
|
||||
[dependencies.tungstenite]
|
||||
version = "0.13"
|
||||
default-features = false
|
||||
optional = true
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
optional = true
|
||||
|
||||
[features]
|
||||
default = ["sleep"]
|
||||
sleep = ["web-sys", "web-sys/Window"]
|
||||
websocket = [
|
||||
"getrandom",
|
||||
"tungstenite",
|
||||
"web-sys",
|
||||
"web-sys/BinaryType",
|
||||
"web-sys/Blob",
|
||||
"web-sys/CloseEvent",
|
||||
"web-sys/ErrorEvent",
|
||||
"web-sys/FileReader",
|
||||
"web-sys/MessageEvent",
|
||||
"web-sys/ProgressEvent",
|
||||
"web-sys/WebSocket",
|
||||
]
|
||||
crypto = [
|
||||
"web-sys",
|
||||
"web-sys/Crypto",
|
||||
"web-sys/CryptoKey",
|
||||
"web-sys/CryptoKeyPair",
|
||||
"web-sys/SubtleCrypto",
|
||||
"web-sys/Window",
|
||||
"web-sys/WorkerGlobalScope",
|
||||
]
|
||||
storage = [
|
||||
"indexed_db_futures",
|
||||
"nym-store-cipher",
|
||||
"serde",
|
||||
"serde-wasm-bindgen",
|
||||
"thiserror"
|
||||
]
|
||||
@@ -1,289 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::websocket::state::State;
|
||||
use crate::{console_error, console_log};
|
||||
use futures::{Sink, Stream};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::rc::Rc;
|
||||
use std::task::{Context, Poll, Waker};
|
||||
use tungstenite::{Error as WsError, Message as WsMessage}; // use tungstenite Message and Error types for easier compatibility with `ClientHandshake`
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::{CloseEvent, ErrorEvent, MessageEvent, WebSocket};
|
||||
|
||||
mod state;
|
||||
|
||||
// Unfortunately this can't be cleanly done with TryFrom/TryInto traits as both are foreign types
|
||||
fn try_message_event_into_ws_message(msg_event: MessageEvent) -> Result<WsMessage, WsError> {
|
||||
match msg_event.data() {
|
||||
buf if buf.is_instance_of::<js_sys::ArrayBuffer>() => {
|
||||
let array = js_sys::Uint8Array::new(&buf);
|
||||
Ok(WsMessage::Binary(array.to_vec()))
|
||||
}
|
||||
blob if blob.is_instance_of::<web_sys::Blob>() => {
|
||||
console_error!("received a blob on the websocket - ignoring it!");
|
||||
// we really don't want to bother dealing with Blobs, because it requires juggling filereaders,
|
||||
// having event handlers to see when they're done, etc.
|
||||
// + we shouldn't even get one [a blob] to begin with
|
||||
// considering that our binary mode is (should be) set to array buffer.
|
||||
Err(WsError::Io(io::Error::from(io::ErrorKind::InvalidInput)))
|
||||
}
|
||||
text if text.is_string() => match text.as_string() {
|
||||
Some(text) => Ok(WsMessage::Text(text)),
|
||||
None => Err(WsError::Utf8),
|
||||
},
|
||||
// "received a websocket message that is neither a String, ArrayBuffer or a Blob"
|
||||
_ => Err(WsError::Io(io::Error::from(io::ErrorKind::InvalidInput))),
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: when compiled to wasm32 everything is going to be running on a single thread and so there
|
||||
// is no shared memory right now.
|
||||
//
|
||||
// Eventually it should be made `Send` properly. Wakers should probably be replaced with AtomicWaker
|
||||
// and the item queue put behind an Arc<Mutex<...>>.
|
||||
// It might also be worth looking at what https://crates.io/crates/send_wrapper could provide.
|
||||
// Because I'm not sure Mutex would solve the `Closure` issue. It's the problem for later.
|
||||
//
|
||||
// ************************************
|
||||
// SUPER IMPORTANT TODO: ONCE WASM IN RUST MATURES AND BECOMES MULTI-THREADED THIS MIGHT
|
||||
// LEAD TO RUNTIME MEMORY CORRUPTION!!
|
||||
// ************************************
|
||||
//
|
||||
unsafe impl Send for JSWebsocket {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::upper_case_acronyms)]
|
||||
pub struct JSWebsocket {
|
||||
socket: web_sys::WebSocket,
|
||||
|
||||
message_queue: Rc<RefCell<VecDeque<Result<WsMessage, WsError>>>>,
|
||||
|
||||
/// Waker of a task wanting to read incoming messages.
|
||||
stream_waker: Rc<RefCell<Option<Waker>>>,
|
||||
|
||||
/// Waker of a task wanting to write to the sink.
|
||||
sink_waker: Rc<RefCell<Option<Waker>>>,
|
||||
|
||||
/// Waker of a sink wanting to close the connection.
|
||||
close_waker: Rc<RefCell<Option<Waker>>>,
|
||||
|
||||
// The callback closures. We need to store them as they will invalidate their
|
||||
// corresponding JS callback whenever they are dropped, so if we were to
|
||||
// normally return from `new` then our registered closures will
|
||||
// raise an exception when invoked.
|
||||
_on_open: Closure<dyn FnMut(JsValue)>,
|
||||
_on_error: Closure<dyn FnMut(ErrorEvent)>,
|
||||
_on_close: Closure<dyn FnMut(CloseEvent)>,
|
||||
_on_message: Closure<dyn FnMut(MessageEvent)>,
|
||||
}
|
||||
|
||||
impl JSWebsocket {
|
||||
pub fn new(url: &str) -> Result<Self, JsValue> {
|
||||
let ws = WebSocket::new(url)?;
|
||||
// we don't want to ever have to deal with blobs
|
||||
ws.set_binary_type(web_sys::BinaryType::Arraybuffer);
|
||||
|
||||
let message_queue = Rc::new(RefCell::new(VecDeque::new()));
|
||||
let message_queue_clone = Rc::clone(&message_queue);
|
||||
|
||||
let stream_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
|
||||
let stream_waker_clone = Rc::clone(&stream_waker);
|
||||
let stream_waker_clone2 = Rc::clone(&stream_waker);
|
||||
|
||||
let sink_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
|
||||
let sink_waker_clone = Rc::clone(&sink_waker);
|
||||
let sink_waker_clone2 = Rc::clone(&sink_waker);
|
||||
|
||||
let close_waker: Rc<RefCell<Option<Waker>>> = Rc::new(RefCell::new(None));
|
||||
let close_waker_clone = Rc::clone(&close_waker);
|
||||
|
||||
let on_message = Closure::wrap(Box::new(move |msg_event| {
|
||||
let ws_message = try_message_event_into_ws_message(msg_event);
|
||||
message_queue_clone.borrow_mut().push_back(ws_message);
|
||||
|
||||
// if there is a task waiting for messages - wake the executor!
|
||||
if let Some(waker) = stream_waker_clone.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
}) as Box<dyn FnMut(MessageEvent)>);
|
||||
|
||||
let url_clone = url.to_string();
|
||||
let on_open = Closure::wrap(Box::new(move |_| {
|
||||
// in case there was a sink send request made before connection was fully established
|
||||
console_log!("Websocket to {:?} is now open!", url_clone);
|
||||
|
||||
// if there is a task waiting to write messages - wake the executor!
|
||||
if let Some(waker) = sink_waker_clone.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
|
||||
// no need to wake the stream_waker because we won't have any message to send
|
||||
// immediately anyway. It only makes sense to wake it during on_message (if any)
|
||||
}) as Box<dyn FnMut(JsValue)>);
|
||||
|
||||
let on_error = Closure::wrap(Box::new(move |e: ErrorEvent| {
|
||||
console_error!("Websocket error event: {:?}", e);
|
||||
}) as Box<dyn FnMut(ErrorEvent)>);
|
||||
|
||||
let on_close = Closure::wrap(Box::new(move |e: CloseEvent| {
|
||||
console_log!("Websocket close event: {:?}", e);
|
||||
// something was waiting for the close event!
|
||||
if let Some(waker) = close_waker_clone.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
|
||||
// TODO: are waking those sufficient to prevent memory leaks?
|
||||
if let Some(waker) = stream_waker_clone2.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
|
||||
if let Some(waker) = sink_waker_clone2.borrow_mut().take() {
|
||||
waker.wake()
|
||||
}
|
||||
}) as Box<dyn FnMut(CloseEvent)>);
|
||||
|
||||
ws.set_onmessage(Some(on_message.as_ref().unchecked_ref()));
|
||||
ws.set_onerror(Some(on_error.as_ref().unchecked_ref()));
|
||||
ws.set_onopen(Some(on_open.as_ref().unchecked_ref()));
|
||||
ws.set_onclose(Some(on_close.as_ref().unchecked_ref()));
|
||||
|
||||
Ok(JSWebsocket {
|
||||
socket: ws,
|
||||
message_queue,
|
||||
stream_waker,
|
||||
sink_waker,
|
||||
close_waker,
|
||||
|
||||
_on_open: on_open,
|
||||
_on_error: on_error,
|
||||
_on_close: on_close,
|
||||
_on_message: on_message,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn close(&mut self, code: Option<u16>) {
|
||||
if let Some(code) = code {
|
||||
self.socket
|
||||
.close_with_code(code)
|
||||
.expect("failed to close the socket!");
|
||||
} else {
|
||||
self.socket.close().expect("failed to close the socket!");
|
||||
}
|
||||
}
|
||||
|
||||
fn state(&self) -> State {
|
||||
self.socket.ready_state().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for JSWebsocket {
|
||||
fn drop(&mut self) {
|
||||
match self.state() {
|
||||
State::Closed | State::Closing => {} // no need to do anything here
|
||||
_ => self
|
||||
.socket
|
||||
.close()
|
||||
.expect("failed to close WebSocket during drop!"),
|
||||
}
|
||||
|
||||
self.socket.set_onmessage(None);
|
||||
self.socket.set_onerror(None);
|
||||
self.socket.set_onopen(None);
|
||||
self.socket.set_onclose(None);
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for JSWebsocket {
|
||||
type Item = Result<WsMessage, WsError>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// if there's anything in the internal queue, keep returning that
|
||||
let ws_message = self.message_queue.borrow_mut().pop_front();
|
||||
match ws_message {
|
||||
Some(message) => Poll::Ready(Some(message)),
|
||||
None => {
|
||||
// if connection is closed or closing it means no more useful messages will ever arrive
|
||||
// and hence we should signal this.
|
||||
match self.state() {
|
||||
State::Closing | State::Closed => Poll::Ready(None),
|
||||
State::Open | State::Connecting => {
|
||||
// clone the waker to be able to notify the executor once we get a new message
|
||||
*self.stream_waker.borrow_mut() = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink<WsMessage> for JSWebsocket {
|
||||
type Error = WsError;
|
||||
|
||||
fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
match self.state() {
|
||||
State::Connecting => {
|
||||
// clone the waker to be able to notify the executor once we get connected
|
||||
*self.sink_waker.borrow_mut() = Some(cx.waker().clone());
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
State::Open => Poll::Ready(Ok(())),
|
||||
State::Closing | State::Closed => Poll::Ready(Err(WsError::AlreadyClosed)),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_send(self: Pin<&mut Self>, item: WsMessage) -> Result<(), Self::Error> {
|
||||
// the only possible errors, per https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send
|
||||
|
||||
// are `INVALID_STATE_ERR` which is when connection is not in open state
|
||||
|
||||
// and `SYNTAX_ERR` which is when data is a string that has unpaired surrogates. This one
|
||||
// is essentially impossible to happen in rust (assuming wasm_bindgen has done its jobs
|
||||
// correctly, but even if not, there's nothing we can do ourselves.
|
||||
|
||||
// hence we can map all errors to not open
|
||||
|
||||
match self.state() {
|
||||
State::Open => match item {
|
||||
WsMessage::Binary(data) => self.socket.send_with_u8_array(&data),
|
||||
WsMessage::Text(text) => self.socket.send_with_str(&text),
|
||||
_ => unreachable!("those are not even exposed by the web_sys API"),
|
||||
}
|
||||
.map_err(|_| WsError::Io(io::Error::from(io::ErrorKind::NotConnected))),
|
||||
|
||||
State::Closing | State::Closed => Err(WsError::AlreadyClosed),
|
||||
State::Connecting => Err(WsError::Io(io::Error::from(io::ErrorKind::NotConnected))),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// TODO: can we/should we do anything more here?
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
match self.state() {
|
||||
State::Open | State::Connecting => {
|
||||
// TODO: do we need to wait for closing event here?
|
||||
*self.close_waker.borrow_mut() = Some(cx.waker().clone());
|
||||
|
||||
// close inner socket
|
||||
Poll::Ready(self.socket.close().map_err(|_| todo!()))
|
||||
}
|
||||
// if we're already closed, nothing left to do!
|
||||
State::Closed => Poll::Ready(Ok(())),
|
||||
State::Closing => {
|
||||
*self.close_waker.borrow_mut() = Some(cx.waker().clone());
|
||||
// wait for the close event...
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user