This commit is contained in:
boufni95 2023-09-18 18:36:04 +02:00
parent 7784beabad
commit bfa23681e6
35 changed files with 4374 additions and 2666 deletions

362
package-lock.json generated
View file

@ -13,6 +13,7 @@
"@protobuf-ts/grpc-transport": "^2.5.0",
"@protobuf-ts/plugin": "^2.5.0",
"@protobuf-ts/runtime": "^2.5.0",
"@stablelib/xchacha20": "^1.0.1",
"@types/express": "^4.17.13",
"@types/node": "^17.0.31",
"@types/secp256k1": "^4.0.3",
@ -39,7 +40,9 @@
"ts-proto": "^1.112.1",
"typeorm": "0.3.15",
"typescript": "^4.6.4",
"uuid": "^8.3.2"
"uuid": "^8.3.2",
"websocket": "^1.0.34",
"websocket-polyfill": "^0.0.3"
},
"devDependencies": {
"@types/chai": "^4.3.4",
@ -50,6 +53,7 @@
"@types/node": "^16.11.10",
"@types/node-fetch": "^2.6.3",
"@types/uuid": "^8.3.4",
"@types/websocket": "^1.0.6",
"nodemon": "^2.0.20",
"ts-node": "10.7.0",
"typescript": "4.5.2"
@ -395,6 +399,43 @@
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
"integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw=="
},
"node_modules/@stablelib/binary": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-1.0.1.tgz",
"integrity": "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==",
"dependencies": {
"@stablelib/int": "^1.0.1"
}
},
"node_modules/@stablelib/chacha": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/chacha/-/chacha-1.0.1.tgz",
"integrity": "sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg==",
"dependencies": {
"@stablelib/binary": "^1.0.1",
"@stablelib/wipe": "^1.0.1"
}
},
"node_modules/@stablelib/int": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/int/-/int-1.0.1.tgz",
"integrity": "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w=="
},
"node_modules/@stablelib/wipe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz",
"integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg=="
},
"node_modules/@stablelib/xchacha20": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/xchacha20/-/xchacha20-1.0.1.tgz",
"integrity": "sha512-1YkiZnFF4veUwBVhDnDYwo6EHeKzQK4FnLiO7ezCl/zu64uG0bCCAUROJaBkaLH+5BEsO3W7BTXTguMbSLlWSw==",
"dependencies": {
"@stablelib/binary": "^1.0.1",
"@stablelib/chacha": "^1.0.1",
"@stablelib/wipe": "^1.0.1"
}
},
"node_modules/@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@ -586,6 +627,15 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"node_modules/@types/websocket": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.6.tgz",
"integrity": "sha512-JXkliwz93B2cMWOI1ukElQBPN88vMg3CruvW4KVSKpflt3NyNCJImnhIuB/f97rG7kakqRJGFiwkA895Kn02Dg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -998,6 +1048,18 @@
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
"optional": true
},
"node_modules/bufferutil": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
"integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -1316,6 +1378,15 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
"node_modules/d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"dependencies": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"node_modules/dataloader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
@ -1550,11 +1621,44 @@
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"optional": true
},
"node_modules/es5-ext": {
"version": "0.10.62",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"node_modules/es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"dependencies": {
"d": "^1.0.1",
"ext": "^1.1.2"
}
},
"node_modules/escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -1627,6 +1731,19 @@
"node": ">= 0.10.0"
}
},
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/ext/node_modules/type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
},
"node_modules/fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@ -2230,6 +2347,11 @@
"node": ">=0.12.0"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@ -2599,6 +2721,11 @@
"node": ">= 0.6"
}
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node_modules/node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
@ -3918,6 +4045,16 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
},
"node_modules/tstl": {
"version": "2.5.13",
"resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.13.tgz",
"integrity": "sha512-h9wayHHFI5+yqt8iau0vqH96cTNhezhZ/Fk/hrIdpfkiMu3lg9nzyvMfs5bIdX51IVzZO6DudLqhkL/rVXpT6g=="
},
"node_modules/type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@ -3938,6 +4075,14 @@
"node": ">= 0.6"
}
},
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"dependencies": {
"is-typedarray": "^1.0.0"
}
},
"node_modules/typeorm": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.15.tgz",
@ -4213,6 +4358,18 @@
"node": ">=8"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -4253,6 +4410,31 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/websocket": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
"integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
"dependencies": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
"es5-ext": "^0.10.50",
"typedarray-to-buffer": "^3.1.5",
"utf-8-validate": "^5.0.2",
"yaeti": "^0.0.6"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/websocket-polyfill": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz",
"integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==",
"dependencies": {
"tstl": "^2.0.7",
"websocket": "^1.0.28"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@ -4322,6 +4504,14 @@
"node": ">=10"
}
},
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
"engines": {
"node": ">=0.10.32"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@ -4622,6 +4812,43 @@
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
"integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw=="
},
"@stablelib/binary": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-1.0.1.tgz",
"integrity": "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==",
"requires": {
"@stablelib/int": "^1.0.1"
}
},
"@stablelib/chacha": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/chacha/-/chacha-1.0.1.tgz",
"integrity": "sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg==",
"requires": {
"@stablelib/binary": "^1.0.1",
"@stablelib/wipe": "^1.0.1"
}
},
"@stablelib/int": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/int/-/int-1.0.1.tgz",
"integrity": "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w=="
},
"@stablelib/wipe": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz",
"integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg=="
},
"@stablelib/xchacha20": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@stablelib/xchacha20/-/xchacha20-1.0.1.tgz",
"integrity": "sha512-1YkiZnFF4veUwBVhDnDYwo6EHeKzQK4FnLiO7ezCl/zu64uG0bCCAUROJaBkaLH+5BEsO3W7BTXTguMbSLlWSw==",
"requires": {
"@stablelib/binary": "^1.0.1",
"@stablelib/chacha": "^1.0.1",
"@stablelib/wipe": "^1.0.1"
}
},
"@tootallnate/once": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
@ -4809,6 +5036,15 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true
},
"@types/websocket": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.6.tgz",
"integrity": "sha512-JXkliwz93B2cMWOI1ukElQBPN88vMg3CruvW4KVSKpflt3NyNCJImnhIuB/f97rG7kakqRJGFiwkA895Kn02Dg==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@ -5122,6 +5358,14 @@
"integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==",
"optional": true
},
"bufferutil": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.7.tgz",
"integrity": "sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw==",
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -5370,6 +5614,15 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"devOptional": true
},
"d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"requires": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"dataloader": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
@ -5560,11 +5813,40 @@
"integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
"optional": true
},
"es5-ext": {
"version": "0.10.62",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
"requires": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"next-tick": "^1.1.0"
}
},
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"requires": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"requires": {
"d": "^1.0.1",
"ext": "^1.1.2"
}
},
"escalade": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
@ -5628,6 +5910,21 @@
"vary": "~1.1.2"
}
},
"ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"requires": {
"type": "^2.7.2"
},
"dependencies": {
"type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
}
}
},
"fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
@ -6073,6 +6370,11 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
@ -6368,6 +6670,11 @@
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
},
"next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node-addon-api": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz",
@ -7357,6 +7664,16 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
},
"tstl": {
"version": "2.5.13",
"resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.13.tgz",
"integrity": "sha512-h9wayHHFI5+yqt8iau0vqH96cTNhezhZ/Fk/hrIdpfkiMu3lg9nzyvMfs5bIdX51IVzZO6DudLqhkL/rVXpT6g=="
},
"type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@ -7371,6 +7688,14 @@
"mime-types": "~2.1.24"
}
},
"typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"requires": {
"is-typedarray": "^1.0.0"
}
},
"typeorm": {
"version": "0.3.15",
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.15.tgz",
@ -7514,6 +7839,14 @@
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
"integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="
},
"utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -7545,6 +7878,28 @@
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"websocket": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
"integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
"requires": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
"es5-ext": "^0.10.50",
"typedarray-to-buffer": "^3.1.5",
"utf-8-validate": "^5.0.2",
"yaeti": "^0.0.6"
}
},
"websocket-polyfill": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz",
"integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==",
"requires": {
"tstl": "^2.0.7",
"websocket": "^1.0.28"
}
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@ -7596,6 +7951,11 @@
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
},
"yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug=="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",

View file

@ -28,6 +28,7 @@
"@protobuf-ts/grpc-transport": "^2.5.0",
"@protobuf-ts/plugin": "^2.5.0",
"@protobuf-ts/runtime": "^2.5.0",
"@stablelib/xchacha20": "^1.0.1",
"@types/express": "^4.17.13",
"@types/node": "^17.0.31",
"@types/secp256k1": "^4.0.3",
@ -54,7 +55,9 @@
"ts-proto": "^1.112.1",
"typeorm": "0.3.15",
"typescript": "^4.6.4",
"uuid": "^8.3.2"
"uuid": "^8.3.2",
"websocket": "^1.0.34",
"websocket-polyfill": "^0.0.3"
},
"devDependencies": {
"@types/chai": "^4.3.4",
@ -65,6 +68,7 @@
"@types/node": "^16.11.10",
"@types/node-fetch": "^2.6.3",
"@types/uuid": "^8.3.4",
"@types/websocket": "^1.0.6",
"nodemon": "^2.0.20",
"ts-node": "10.7.0",
"typescript": "4.5.2"

View file

@ -88,10 +88,13 @@ The nostr server will send back a message response, and inside the body there wi
- __user_id__: _string_
- __Admin__:
- this auth type is __encrypted__
- expected context content
- __admin_id__: _string_
- __App__:
- expected context content
- __app_id__: _string_
## HTTP Methods
### These are the http methods the client implements to communicate with the API
@ -111,12 +114,109 @@ The nostr server will send back a message response, and inside the body there wi
- LndGetInfo
- auth type: __Admin__
- this method is encrypted
- http method: __post__
- http route: __/api/lnd/getinfo__
- http route: __/api/admin/lnd/getinfo__
- input: [LndGetInfoRequest](#LndGetInfoRequest)
- output: [LndGetInfoResponse](#LndGetInfoResponse)
- SetMockInvoiceAsPaid
- auth type: __Guest__
- http method: __post__
- http route: __/api/lnd/mock/invoice/paid__
- input: [SetMockInvoiceAsPaidRequest](#SetMockInvoiceAsPaidRequest)
- This methods has an __empty__ __response__ body
- AddApp
- auth type: __Admin__
- http method: __post__
- http route: __/api/admin/app/add__
- input: [AddAppRequest](#AddAppRequest)
- output: [AuthApp](#AuthApp)
- AuthApp
- auth type: __Admin__
- http method: __post__
- http route: __/api/admin/app/auth__
- input: [AuthAppRequest](#AuthAppRequest)
- output: [AuthApp](#AuthApp)
- GetApp
- auth type: __App__
- http method: __post__
- http route: __/api/app/get__
- This methods has an __empty__ __request__ body
- output: [Application](#Application)
- AddAppUser
- auth type: __App__
- http method: __post__
- http route: __/api/app/user/add__
- input: [AddAppUserRequest](#AddAppUserRequest)
- output: [AppUser](#AppUser)
- AddAppInvoice
- auth type: __App__
- http method: __post__
- http route: __/api/app/add/invoice__
- input: [AddAppInvoiceRequest](#AddAppInvoiceRequest)
- output: [NewInvoiceResponse](#NewInvoiceResponse)
- AddAppUserInvoice
- auth type: __App__
- http method: __post__
- http route: __/api/app/user/add/invoice__
- input: [AddAppUserInvoiceRequest](#AddAppUserInvoiceRequest)
- output: [NewInvoiceResponse](#NewInvoiceResponse)
- GetAppUser
- auth type: __App__
- http method: __post__
- http route: __/api/app/user/get__
- input: [GetAppUserRequest](#GetAppUserRequest)
- output: [AppUser](#AppUser)
- PayAppUserInvoice
- auth type: __App__
- http method: __post__
- http route: __/api/app/invoice/pay__
- input: [PayAppUserInvoiceRequest](#PayAppUserInvoiceRequest)
- output: [PayAppUserInvoiceResponse](#PayAppUserInvoiceResponse)
- SendAppUserToAppUserPayment
- auth type: __App__
- http method: __post__
- http route: __/api/app/user/internal/pay__
- input: [SendAppUserToAppUserPaymentRequest](#SendAppUserToAppUserPaymentRequest)
- This methods has an __empty__ __response__ body
- SendAppUserToAppPayment
- auth type: __App__
- http method: __post__
- http route: __/api/app/internal/pay__
- input: [SendAppUserToAppPaymentRequest](#SendAppUserToAppPaymentRequest)
- This methods has an __empty__ __response__ body
- GetAppUserLNURLInfo
- auth type: __App__
- http method: __post__
- http route: __/api/app/user/lnurl/pay/info__
- input: [GetAppUserLNURLInfoRequest](#GetAppUserLNURLInfoRequest)
- output: [LnurlPayInfoResponse](#LnurlPayInfoResponse)
- SetMockAppUserBalance
- auth type: __App__
- http method: __post__
- http route: __/api/app/mock/user/blance/set__
- input: [SetMockAppUserBalanceRequest](#SetMockAppUserBalanceRequest)
- This methods has an __empty__ __response__ body
- SetMockAppBalance
- auth type: __App__
- http method: __post__
- http route: __/api/app/mock/blance/set__
- input: [SetMockAppBalanceRequest](#SetMockAppBalanceRequest)
- This methods has an __empty__ __response__ body
- AddUser
- auth type: __Guest__
- http method: __post__
@ -260,56 +360,96 @@ The nostr server will send back a message response, and inside the body there wi
## Messages
### The content of requests and response from the methods
### OpenChannelResponse
- __channelId__: _string_
### LnurlPayInfoResponse
- __tag__: _string_
- __callback__: _string_
- __maxSendable__: _number_
- __minSendable__: _number_
- __metadata__: _string_
### UserInfo
- __userId__: _string_
- __balance__: _number_
### AddUserRequest
- __callbackUrl__: _string_
- __name__: _string_
- __secret__: _string_
### GetUserOperationsRequest
- __latestIncomingInvoice__: _number_
- __latestOutgoingInvoice__: _number_
- __latestIncomingTx__: _number_
- __latestOutgoingTx__: _number_
- __latestIncomingUserToUserPayment__: _number_
- __latestOutgoingUserToUserPayment__: _number_
### Empty
### AddAppUserRequest
- __identifier__: _string_
- __fail_if_exists__: _boolean_
- __balance__: _number_
### SetMockAppBalanceRequest
- __amount__: _number_
### NewAddressRequest
- __addressType__: _[AddressType](#AddressType)_
### NewInvoiceRequest
- __amountSats__: _number_
- __memo__: _string_
### GetUserOperationsResponse
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
- __latestIncomingUserToUserPayemnts__: _[UserOperations](#UserOperations)_
### Application
- __name__: _string_
- __id__: _string_
- __balance__: _number_
- __npub__: _string_
### AddAppInvoiceRequest
- __payer_identifier__: _string_
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### GetAppUserLNURLInfoRequest
- __user_identifier__: _string_
- __base_url_override__: _string_
### AuthUserResponse
- __userId__: _string_
- __authToken__: _string_
### AddProductRequest
- __name__: _string_
- __price_sats__: _number_
### Product
- __id__: _string_
- __name__: _string_
- __price_sats__: _number_
### EncryptionExchangeRequest
- __publicKey__: _string_
- __deviceId__: _string_
### NewInvoiceRequest
- __amountSats__: _number_
- __memo__: _string_
### AppUser
- __identifier__: _string_
- __info__: _[UserInfo](#UserInfo)_
- __max_withdrawable__: _number_
### PayInvoiceResponse
- __preimage__: _string_
### DecodeInvoiceResponse
- __amount__: _number_
### HandleLnurlPayResponse
- __pr__: _string_
- __routes__: ARRAY of: _[Empty](#Empty)_
### PayInvoiceRequest
- __invoice__: _string_
- __amount__: _number_
### AuthUserRequest
- __name__: _string_
- __secret__: _string_
### UserOperations
- __fromIndex__: _number_
- __toIndex__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### PayAddressRequest
- __address__: _string_
- __amoutSats__: _number_
- __targetConf__: _number_
### PayAddressResponse
- __txId__: _string_
### OpenChannelRequest
- __destination__: _string_
@ -321,33 +461,56 @@ The nostr server will send back a message response, and inside the body there wi
- __lnurl__: _string_
- __k1__: _string_
### Empty
### NewInvoiceResponse
### SetMockInvoiceAsPaidRequest
- __invoice__: _string_
- __amount__: _number_
### DecodeInvoiceRequest
### AddAppUserInvoiceRequest
- __receiver_identifier__: _string_
- __payer_identifier__: _string_
- __http_callback_url__: _string_
- __invoice_req__: _[NewInvoiceRequest](#NewInvoiceRequest)_
### PayAppUserInvoiceRequest
- __user_identifier__: _string_
- __invoice__: _string_
- __amount__: _number_
### AuthUserResponse
### SendAppUserToAppPaymentRequest
- __from_user_identifier__: _string_
- __amount__: _number_
### PayAddressRequest
- __address__: _string_
- __amoutSats__: _number_
- __targetConf__: _number_
### UserOperation
- __paidAtUnix__: _number_
- __type__: _[UserOperationType](#UserOperationType)_
- __inbound__: _boolean_
- __amount__: _number_
### UserInfo
- __userId__: _string_
- __authToken__: _string_
- __balance__: _number_
### GetUserOperationsResponse
- __latestOutgoingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingInvoiceOperations__: _[UserOperations](#UserOperations)_
- __latestOutgoingTxOperations__: _[UserOperations](#UserOperations)_
- __latestIncomingTxOperations__: _[UserOperations](#UserOperations)_
### Product
- __id__: _string_
- __name__: _string_
- __price_sats__: _number_
### LndGetInfoRequest
- __nodeId__: _number_
### SendAppUserToAppUserPaymentRequest
- __from_user_identifier__: _string_
- __to_user_identifier__: _string_
- __amount__: _number_
### LndGetInfoResponse
- __alias__: _string_
### SetMockAppUserBalanceRequest
- __user_identifier__: _string_
- __amount__: _number_
### AddUserResponse
- __userId__: _string_
- __authToken__: _string_
### NewAddressResponse
- __address__: _string_
### LnurlWithdrawInfoResponse
- __tag__: _string_
@ -359,42 +522,59 @@ The nostr server will send back a message response, and inside the body there wi
- __balanceCheck__: _string_
- __payLink__: _string_
### HandleLnurlPayResponse
- __pr__: _string_
- __routes__: ARRAY of: _[Empty](#Empty)_
### AddUserResponse
- __userId__: _string_
- __authToken__: _string_
### UserOperations
- __fromIndex__: _number_
- __toIndex__: _number_
- __operations__: ARRAY of: _[UserOperation](#UserOperation)_
### AuthAppRequest
- __name__: _string_
- __allow_user_creation__: _boolean_ *this field is optional
### GetAppUserRequest
- __user_identifier__: _string_
### NewInvoiceResponse
- __invoice__: _string_
### DecodeInvoiceRequest
- __invoice__: _string_
### OpenChannelResponse
- __channelId__: _string_
### AddAppRequest
- __name__: _string_
- __allow_user_creation__: _boolean_
### PayAppUserInvoiceResponse
- __preimage__: _string_
- __amount_paid__: _number_
### PayInvoiceResponse
- __preimage__: _string_
- __amount_paid__: _number_
### GetProductBuyLinkResponse
- __link__: _string_
### AddUserRequest
- __callbackUrl__: _string_
- __name__: _string_
- __secret__: _string_
### LndGetInfoRequest
- __nodeId__: _number_
### UserOperation
- __paidAtUnix__: _number_
- __type__: _[UserOperationType](#UserOperationType)_
- __inbound__: _boolean_
- __amount__: _number_
### LndGetInfoResponse
- __alias__: _string_
### NewAddressRequest
- __addressType__: _[AddressType](#AddressType)_
### NewAddressResponse
- __address__: _string_
### PayInvoiceRequest
- __invoice__: _string_
- __amount__: _number_
### PayAddressResponse
- __txId__: _string_
### DecodeInvoiceResponse
- __amount__: _number_
### LnurlPayInfoResponse
- __tag__: _string_
- __callback__: _string_
- __maxSendable__: _number_
- __minSendable__: _number_
- __metadata__: _string_
### AuthApp
- __app__: _[Application](#Application)_
- __auth_token__: _string_
## Enums
### The enumerators used in the messages
@ -408,3 +588,5 @@ The nostr server will send back a message response, and inside the body there wi
- __OUTGOING_TX__
- __INCOMING_INVOICE__
- __OUTGOING_INVOICE__
- __OUTGOING_USER_TO_USER__
- __INCOMING_USER_TO_USER__

File diff suppressed because it is too large Load diff

View file

@ -7,9 +7,12 @@ export type Logger = { log: (v: any) => void, error: (v: any) => void }
export type ServerOptions = {
allowCors?: true
staticFiles?: string
allowNotImplementedMethods?: number
allowNotImplementedMethods?: true
logger?: Logger
throwErrors?: true
overrides?: MethodsOverride
logMethod?: true
logBody?: true
GuestAuthGuard: (authorizationHeader?: string) => Promise<Types.GuestContext>
UserAuthGuard: (authorizationHeader?: string) => Promise<Types.UserContext>
AdminAuthGuard: (authorizationHeader?: string) => Promise<Types.AdminContext>
@ -26,6 +29,8 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
}
app.use(json())
app.use(urlencoded({ extended: true }))
if (opts.logMethod) app.use((req, _, next) => { console.log(req.method, req.path); if (opts.logBody) console.log(req.body); next() })
const overrides = opts.overrides || {} as MethodsOverride
if (!opts.allowNotImplementedMethods && !methods.Health) throw new Error('method: Health is not implemented')
app.get('/api/health', async (req, res) => {
try {
@ -34,7 +39,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
await methods.Health({ ...authContext, ...query, ...params })
res.json({status: 'OK'})
if (overrides.Health_Override) await overrides.Health_Override(res); else res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.EncryptionExchange) throw new Error('method: EncryptionExchange is not implemented')
@ -48,7 +53,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
await methods.EncryptionExchange({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
if (overrides.EncryptionExchange_Override) await overrides.EncryptionExchange_Override(res); else res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.LndGetInfo) throw new Error('method: LndGetInfo is not implemented')
@ -62,7 +67,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.LndGetInfo({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.LndGetInfo_Override) await overrides.LndGetInfo_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SetMockInvoiceAsPaid) throw new Error('method: SetMockInvoiceAsPaid is not implemented')
@ -76,7 +81,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
await methods.SetMockInvoiceAsPaid({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
if (overrides.SetMockInvoiceAsPaid_Override) await overrides.SetMockInvoiceAsPaid_Override(res); else res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddApp) throw new Error('method: AddApp is not implemented')
@ -85,12 +90,12 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
if (!methods.AddApp) throw new Error('method: AddApp is not implemented')
const authContext = await opts.AdminAuthGuard(req.headers['authorization'])
const request = req.body
const error = Types.AuthAppRequestValidate(request)
const error = Types.AddAppRequestValidate(request)
if (error !== null) return logErrorAndReturnResponse(error, 'invalid request body', res, logger)
const query = req.query
const params = req.params
const response = await methods.AddApp({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.AddApp_Override) await overrides.AddApp_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AuthApp) throw new Error('method: AuthApp is not implemented')
@ -104,7 +109,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.AuthApp({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.AuthApp_Override) await overrides.AuthApp_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetApp) throw new Error('method: GetApp is not implemented')
@ -115,7 +120,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetApp({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
if (overrides.GetApp_Override) await overrides.GetApp_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddAppUser) throw new Error('method: AddAppUser is not implemented')
@ -129,7 +134,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.AddAppUser({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.AddAppUser_Override) await overrides.AddAppUser_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddAppInvoice) throw new Error('method: AddAppInvoice is not implemented')
@ -143,7 +148,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.AddAppInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.AddAppInvoice_Override) await overrides.AddAppInvoice_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddAppUserInvoice) throw new Error('method: AddAppUserInvoice is not implemented')
@ -157,7 +162,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.AddAppUserInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.AddAppUserInvoice_Override) await overrides.AddAppUserInvoice_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetAppUser) throw new Error('method: GetAppUser is not implemented')
@ -171,7 +176,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetAppUser({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.GetAppUser_Override) await overrides.GetAppUser_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.PayAppUserInvoice) throw new Error('method: PayAppUserInvoice is not implemented')
@ -185,7 +190,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.PayAppUserInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.PayAppUserInvoice_Override) await overrides.PayAppUserInvoice_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SendAppUserToAppUserPayment) throw new Error('method: SendAppUserToAppUserPayment is not implemented')
@ -199,7 +204,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
await methods.SendAppUserToAppUserPayment({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
if (overrides.SendAppUserToAppUserPayment_Override) await overrides.SendAppUserToAppUserPayment_Override(res); else res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SendAppUserToAppPayment) throw new Error('method: SendAppUserToAppPayment is not implemented')
@ -213,7 +218,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
await methods.SendAppUserToAppPayment({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
if (overrides.SendAppUserToAppPayment_Override) await overrides.SendAppUserToAppPayment_Override(res); else res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetAppUserLNURLInfo) throw new Error('method: GetAppUserLNURLInfo is not implemented')
@ -227,7 +232,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetAppUserLNURLInfo({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.GetAppUserLNURLInfo_Override) await overrides.GetAppUserLNURLInfo_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SetMockAppUserBalance) throw new Error('method: SetMockAppUserBalance is not implemented')
@ -241,7 +246,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
await methods.SetMockAppUserBalance({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
if (overrides.SetMockAppUserBalance_Override) await overrides.SetMockAppUserBalance_Override(res); else res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.SetMockAppBalance) throw new Error('method: SetMockAppBalance is not implemented')
@ -255,7 +260,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
await methods.SetMockAppBalance({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK'})
if (overrides.SetMockAppBalance_Override) await overrides.SetMockAppBalance_Override(res); else res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddUser) throw new Error('method: AddUser is not implemented')
@ -269,7 +274,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.AddUser({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.AddUser_Override) await overrides.AddUser_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AuthUser) throw new Error('method: AuthUser is not implemented')
@ -283,7 +288,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.AuthUser({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.AuthUser_Override) await overrides.AuthUser_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetUserInfo) throw new Error('method: GetUserInfo is not implemented')
@ -294,7 +299,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetUserInfo({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
if (overrides.GetUserInfo_Override) await overrides.GetUserInfo_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.AddProduct) throw new Error('method: AddProduct is not implemented')
@ -308,7 +313,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.AddProduct({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.AddProduct_Override) await overrides.AddProduct_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.NewProductInvoice) throw new Error('method: NewProductInvoice is not implemented')
@ -319,7 +324,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.NewProductInvoice({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
if (overrides.NewProductInvoice_Override) await overrides.NewProductInvoice_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetUserOperations) throw new Error('method: GetUserOperations is not implemented')
@ -333,7 +338,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetUserOperations({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.GetUserOperations_Override) await overrides.GetUserOperations_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.NewAddress) throw new Error('method: NewAddress is not implemented')
@ -347,7 +352,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.NewAddress({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.NewAddress_Override) await overrides.NewAddress_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.PayAddress) throw new Error('method: PayAddress is not implemented')
@ -361,7 +366,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.PayAddress({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.PayAddress_Override) await overrides.PayAddress_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.NewInvoice) throw new Error('method: NewInvoice is not implemented')
@ -375,7 +380,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.NewInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.NewInvoice_Override) await overrides.NewInvoice_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.DecodeInvoice) throw new Error('method: DecodeInvoice is not implemented')
@ -389,7 +394,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.DecodeInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.DecodeInvoice_Override) await overrides.DecodeInvoice_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.PayInvoice) throw new Error('method: PayInvoice is not implemented')
@ -403,7 +408,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.PayInvoice({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.PayInvoice_Override) await overrides.PayInvoice_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.OpenChannel) throw new Error('method: OpenChannel is not implemented')
@ -417,7 +422,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.OpenChannel({ ...authContext, ...query, ...params }, request)
res.json({status: 'OK', ...response})
if (overrides.OpenChannel_Override) await overrides.OpenChannel_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetLnurlWithdrawLink) throw new Error('method: GetLnurlWithdrawLink is not implemented')
@ -428,7 +433,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetLnurlWithdrawLink({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
if (overrides.GetLnurlWithdrawLink_Override) await overrides.GetLnurlWithdrawLink_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetLnurlWithdrawInfo) throw new Error('method: GetLnurlWithdrawInfo is not implemented')
@ -439,7 +444,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetLnurlWithdrawInfo({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
if (overrides.GetLnurlWithdrawInfo_Override) await overrides.GetLnurlWithdrawInfo_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.HandleLnurlWithdraw) throw new Error('method: HandleLnurlWithdraw is not implemented')
@ -450,7 +455,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
await methods.HandleLnurlWithdraw({ ...authContext, ...query, ...params })
res.json({status: 'OK'})
if (overrides.HandleLnurlWithdraw_Override) await overrides.HandleLnurlWithdraw_Override(res); else res.json({status: 'OK'})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetLnurlPayInfo) throw new Error('method: GetLnurlPayInfo is not implemented')
@ -461,7 +466,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetLnurlPayInfo({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
if (overrides.GetLnurlPayInfo_Override) await overrides.GetLnurlPayInfo_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.HandleLnurlPay) throw new Error('method: HandleLnurlPay is not implemented')
@ -472,7 +477,7 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.HandleLnurlPay({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
if (overrides.HandleLnurlPay_Override) await overrides.HandleLnurlPay_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (!opts.allowNotImplementedMethods && !methods.GetLNURLChannelLink) throw new Error('method: GetLNURLChannelLink is not implemented')
@ -483,11 +488,12 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
const query = req.query
const params = req.params
const response = await methods.GetLNURLChannelLink({ ...authContext, ...query, ...params })
res.json({status: 'OK', ...response})
if (overrides.GetLNURLChannelLink_Override) await overrides.GetLNURLChannelLink_Override(res, response); else res.json({status: 'OK', ...response})
} catch (ex) { const e = ex as any; logErrorAndReturnResponse(e, e.message || e, res, logger); if (opts.throwErrors) throw e }
})
if (opts.staticFiles) {
app.use(express.static(opts.staticFiles))
app.get('*', function (_, res) { res.sendFile('index.html', { root: opts.staticFiles })})
}
var server: { close: () => void } | undefined
return {
@ -495,3 +501,40 @@ export default (methods: Types.ServerMethods, opts: ServerOptions) => {
Listen: (port: number) => { server = app.listen(port, () => logger.log('Example app listening on port ' + port)) }
}
}
export type MethodsOverride = {
Health_Override?: (httpRes:Response) => Promise<void>
EncryptionExchange_Override?: (httpRes:Response) => Promise<void>
LndGetInfo_Override?: (httpRes:Response, handlerRes:Types.LndGetInfoResponse) => Promise<void>
SetMockInvoiceAsPaid_Override?: (httpRes:Response) => Promise<void>
AddApp_Override?: (httpRes:Response, handlerRes:Types.AuthApp) => Promise<void>
AuthApp_Override?: (httpRes:Response, handlerRes:Types.AuthApp) => Promise<void>
GetApp_Override?: (httpRes:Response, handlerRes:Types.Application) => Promise<void>
AddAppUser_Override?: (httpRes:Response, handlerRes:Types.AppUser) => Promise<void>
AddAppInvoice_Override?: (httpRes:Response, handlerRes:Types.NewInvoiceResponse) => Promise<void>
AddAppUserInvoice_Override?: (httpRes:Response, handlerRes:Types.NewInvoiceResponse) => Promise<void>
GetAppUser_Override?: (httpRes:Response, handlerRes:Types.AppUser) => Promise<void>
PayAppUserInvoice_Override?: (httpRes:Response, handlerRes:Types.PayAppUserInvoiceResponse) => Promise<void>
SendAppUserToAppUserPayment_Override?: (httpRes:Response) => Promise<void>
SendAppUserToAppPayment_Override?: (httpRes:Response) => Promise<void>
GetAppUserLNURLInfo_Override?: (httpRes:Response, handlerRes:Types.LnurlPayInfoResponse) => Promise<void>
SetMockAppUserBalance_Override?: (httpRes:Response) => Promise<void>
SetMockAppBalance_Override?: (httpRes:Response) => Promise<void>
AddUser_Override?: (httpRes:Response, handlerRes:Types.AddUserResponse) => Promise<void>
AuthUser_Override?: (httpRes:Response, handlerRes:Types.AuthUserResponse) => Promise<void>
GetUserInfo_Override?: (httpRes:Response, handlerRes:Types.UserInfo) => Promise<void>
AddProduct_Override?: (httpRes:Response, handlerRes:Types.Product) => Promise<void>
NewProductInvoice_Override?: (httpRes:Response, handlerRes:Types.NewInvoiceResponse) => Promise<void>
GetUserOperations_Override?: (httpRes:Response, handlerRes:Types.GetUserOperationsResponse) => Promise<void>
NewAddress_Override?: (httpRes:Response, handlerRes:Types.NewAddressResponse) => Promise<void>
PayAddress_Override?: (httpRes:Response, handlerRes:Types.PayAddressResponse) => Promise<void>
NewInvoice_Override?: (httpRes:Response, handlerRes:Types.NewInvoiceResponse) => Promise<void>
DecodeInvoice_Override?: (httpRes:Response, handlerRes:Types.DecodeInvoiceResponse) => Promise<void>
PayInvoice_Override?: (httpRes:Response, handlerRes:Types.PayInvoiceResponse) => Promise<void>
OpenChannel_Override?: (httpRes:Response, handlerRes:Types.OpenChannelResponse) => Promise<void>
GetLnurlWithdrawLink_Override?: (httpRes:Response, handlerRes:Types.LnurlLinkResponse) => Promise<void>
GetLnurlWithdrawInfo_Override?: (httpRes:Response, handlerRes:Types.LnurlWithdrawInfoResponse) => Promise<void>
HandleLnurlWithdraw_Override?: (httpRes:Response) => Promise<void>
GetLnurlPayInfo_Override?: (httpRes:Response, handlerRes:Types.LnurlPayInfoResponse) => Promise<void>
HandleLnurlPay_Override?: (httpRes:Response, handlerRes:Types.HandleLnurlPayResponse) => Promise<void>
GetLNURLChannelLink_Override?: (httpRes:Response, handlerRes:Types.LnurlLinkResponse) => Promise<void>
}

View file

@ -62,7 +62,7 @@ export default (params: ClientParams) => ({
}
return { status: 'ERROR', reason: 'invalid response' }
},
AddApp: async (request: Types.AuthAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AuthApp)> => {
AddApp: async (request: Types.AddAppRequest): Promise<ResultError | ({ status: 'OK' }& Types.AuthApp)> => {
const auth = await params.retrieveAdminAuth()
if (auth === null) throw new Error('retrieveAdminAuth() returned null')
let finalRoute = '/api/admin/app/add'

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -96,7 +96,7 @@ service LightningPub {
// <App>
rpc AddApp(structs.AuthAppRequest) returns (structs.AuthApp) {
rpc AddApp(structs.AddAppRequest) returns (structs.AuthApp) {
option (auth_type) = "Admin";
option (http_method) = "post";
option (http_route) = "/api/admin/app/add";
@ -184,13 +184,13 @@ service LightningPub {
option (http_method) = "post";
option (http_route) = "/api/user/auth";
}
// USER
rpc GetUserInfo(structs.Empty)returns(structs.UserInfo){
option (auth_type) = "User";
option (http_method) = "post";
option (http_route) = "/api/user/info";
option (nostr) = true;
}
// USER
rpc AddProduct(structs.AddProductRequest) returns (structs.Product){
option (auth_type) = "User";
option (http_method) = "post";

View file

@ -25,15 +25,23 @@ message LndGetInfoResponse {
string alias = 1;
}
message AddAppRequest {
string name = 1;
bool allow_user_creation = 2;
}
message AuthAppRequest {
string name = 1;
optional bool allow_user_creation = 2;
}
message Application {
string name = 1;
string id = 2;
int64 balance = 3;
string npub = 4;
}
message AuthApp {
Application app = 1;
string auth_token = 2;
@ -212,6 +220,7 @@ message UserInfo{
string userId = 1;
int64 balance = 2;
}
message GetUserOperationsRequest{
int64 latestIncomingInvoice = 1;
int64 latestOutgoingInvoice = 2;
@ -229,7 +238,7 @@ enum UserOperationType {
INCOMING_USER_TO_USER=5;
}
message UserOperation{
message UserOperation {
int64 paidAtUnix=1;
UserOperationType type = 2;
bool inbound =3;

View file

@ -1,140 +0,0 @@
import 'dotenv/config' // TODO - test env
import crypto from 'crypto';
import { generatePrivateKey, getPublicKey } from 'nostr-tools';
import NewServer from '../proto/autogenerated/ts/express_server.js'
import NewClient from '../proto/autogenerated/ts/http_client.js'
import serverOptions from './auth.js';
import GetServerMethods from './services/serverMethods/index.js'
import Main, { LoadMainSettingsFromEnv } from './services/main/index.js'
import * as Types from '../proto/autogenerated/ts/types.js';
import nostrMiddleware from './nostrMiddleware.js'
import { LoadNosrtSettingsFromEnv } from './services/nostr/index.js';
import { expect } from 'chai';
import NostrHandler from './services/nostr/index.js'
import NewNostrClient from '../proto/autogenerated/ts/nostr_client.js'
import { NostrRequest } from '../proto/autogenerated/ts/nostr_transport.js';
const settings = LoadNosrtSettingsFromEnv(true)
const clientPrivateKey = generatePrivateKey()
const clientPublicKey = getPublicKey(clientPrivateKey)
const serverPrivateKey = generatePrivateKey()
const serverPublicKey = getPublicKey(serverPrivateKey)
const testPort = 4000
var userAuthHeader = ""
const client = NewClient({
baseUrl: `http://localhost:${testPort}`,
retrieveAdminAuth: async () => (""),
retrieveGuestAuth: async () => (""),
retrieveUserAuth: async () => userAuthHeader,
retrieveAppAuth: async () => (""),
decryptCallback: async (b) => b,
encryptCallback: async (b) => b,
deviceId: "device0"
})
const clientCbs: Record<string, (res: any) => void> = {}
const clientNostrHandler = new NostrHandler({
allowedPubs: [],
privateKey: clientPrivateKey,
publicKey: clientPublicKey,
relays: settings.relays
}, (e) => {
const res = JSON.parse(e.content) as { requestId: string }
if (clientCbs[res.requestId]) {
const cb = clientCbs[res.requestId]
cb(res)
delete clientCbs[res.requestId]
}
})
const clientSend = (to: string, message: NostrRequest): Promise<any> => {
console.log("sending", message)
if (!message.requestId) {
message.requestId = crypto.randomBytes(20).toString('hex')
}
const reqId = message.requestId
if (clientCbs[reqId]) {
throw new Error("request was already sent")
}
clientNostrHandler.Send(to, JSON.stringify(message))
return new Promise(res => {
clientCbs[reqId] = (response: any) => {
res(response)
}
})
}
const clientNostr = NewNostrClient({
pubDestination: serverPublicKey,
retrieveNostrUserAuth: async () => { return clientPublicKey }
}, clientSend)
/* new NostrHandler({
allowedPubs: [],
privateKey: clientPrivateKey,
publicKey: clientPublicKey,
relays: settings.relays
}, (event) => {
console.log(event.content)
})*/
const mainSettings = LoadMainSettingsFromEnv(true)
const mainHandler = new Main(mainSettings) // TODO - test env file
const serverMethods = GetServerMethods(mainHandler)
const serverNostr = nostrMiddleware(serverMethods, mainHandler, {
allowedPubs: [clientPublicKey],
privateKey: serverPrivateKey,
publicKey: serverPublicKey,
relays: settings.relays
})
const server = NewServer(serverMethods, { ...serverOptions(mainHandler), throwErrors: true })
export const ignore = false
export const setup = async () => {
await mainHandler.storage.Connect()
await mainHandler.lnd.Warmup()
server.Listen(testPort)
}
export const teardown = async () => {
clientNostrHandler.Stop()
serverNostr.Stop()
mainHandler.lnd.Stop()
server.Close()
}
export default async (d: (message: string, failure?: boolean) => void) => {
await client.Health()
d("health ok")
console.log(await client.LndGetInfo({ nodeId: 0 }))
d("lnd info ok")
const res = await client.AddUser({ name: "test", callbackUrl: "http://...", secret: "shhhhhht" })
if (res.status === 'ERROR') throw new Error(res.reason)
console.log(res)
const user = await mainHandler.storage.userStorage.GetUser(res.userId)
console.log(user)
userAuthHeader = res.authToken
d("create user ok")
console.log(await client.NewAddress({ addressType: Types.AddressType.WITNESS_PUBKEY_HASH }))
d("new address ok")
await new Promise(res => setTimeout(res, 2000))
//clientNostr.Send(serverPublicKey, JSON.stringify({ requestId: "a", method: '/api/user/chain/new', body: { address_type: 'WITNESS_PUBKEY_HASH' } }))
const nostrRes = await clientNostr.NewAddress({ addressType: Types.AddressType.WITNESS_PUBKEY_HASH })
console.log(nostrRes)
d("nostr ok")
const lnurlWithdrawLink = await client.GetLnurlWithdrawLink()
if (lnurlWithdrawLink.status === 'ERROR') throw new Error(lnurlWithdrawLink.reason)
const lnurlWithdrawInfo = await client.GetLnurlWithdrawInfo({ k1: lnurlWithdrawLink.k1 })
const expectedInfo: Partial<Types.LnurlWithdrawInfoResponse> & { status: 'OK' } = {
status: 'OK',
tag: "withdrawRequest",
defaultDescription: "lnurl withdraw from lightning.pub",
maxWithdrawable: 0,
minWithdrawable: 0,
}
expect(lnurlWithdrawInfo).to.deep.include(expectedInfo)
d("lnurl info ok")
}

View file

@ -13,7 +13,17 @@ const start = async () => {
await mainHandler.lnd.Warmup()
const serverMethods = GetServerMethods(mainHandler)
const nostrSettings = LoadNosrtSettingsFromEnv()
nostrMiddleware(serverMethods, mainHandler, nostrSettings)
const appsData = await mainHandler.storage.applicationStorage.GetApplications()
const apps = await Promise.all(appsData.map(app => {
if (!app.nostr_private_key) { // TMP --
return mainHandler.storage.applicationStorage.GenerateApplicationKeys(app);
} // --
else {
return { privateKey: app.nostr_private_key, publicKey: app.nostr_public_key, appId: app.app_id, name: app.name }
}
}))
nostrMiddleware(serverMethods, mainHandler, { ...nostrSettings, apps })
const Server = NewServer(serverMethods, serverOptions(mainHandler))
if (process.argv[2] === 'unlock') {
const u = process.argv[3]

View file

@ -7,14 +7,10 @@ const handledRequests: string[] = [] // TODO: - big memory leak here, add TTL
export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSettings: NostrSettings) => {
const nostrTransport = NewNostrTransport(serverMethods, {
NostrUserAuthGuard: async pub => {
if (!pub || !nostrSettings.allowedPubs.includes(pub)) {
throw new Error("nostr pub invalid or not allowed" + pub)
}
let nostrUser = await mainHandler.storage.userStorage.FindNostrUser(pub)
if (!nostrUser) { // TODO: add POW
nostrUser = await mainHandler.storage.userStorage.AddNostrUser(pub)
}
NostrUserAuthGuard: async (appId, pub) => {
console.log({ appId })
const app = await mainHandler.storage.applicationStorage.GetApplication(appId || "")
let nostrUser = await mainHandler.storage.applicationStorage.GetOrCreateNostrAppUser(app, pub || "")
return { user_id: nostrUser.user.user_id }
}
})
@ -27,8 +23,8 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett
console.error("invalid json event received", event.content)
return
}
nostrTransport(j, res => {
nostr.Send(event.pub, JSON.stringify({ ...res, requestId: j.requestId }))
nostrTransport({ ...j, appId: event.appId }, res => {
nostr.Send(event.appId, event.pub, JSON.stringify({ ...res, requestId: j.requestId }))
})
})
return { Stop: nostr.Stop }

View file

@ -47,15 +47,16 @@ export default class {
}
async AddApp(req: Types.AuthAppRequest): Promise<Types.AuthApp> {
const app = await this.storage.applicationStorage.AddApplication(req.name)
async AddApp(req: Types.AddAppRequest): Promise<Types.AuthApp> {
const app = await this.storage.applicationStorage.AddApplication(req.name, req.allow_user_creation)
getLogger({ appName: app.name })("app created")
return {
app: {
id: app.app_id,
name: app.name,
balance: app.owner.balance_sats
balance: app.owner.balance_sats,
npub: app.nostr_public_key
},
auth_token: this.SignAppToken(app.app_id)
}
@ -63,11 +64,15 @@ export default class {
async AuthApp(req: Types.AuthAppRequest): Promise<Types.AuthApp> {
const app = await this.storage.applicationStorage.GetApplicationByName(req.name)
if (typeof req.allow_user_creation === 'boolean') {
await this.storage.applicationStorage.UpdateApplication(app, { allow_user_creation: req.allow_user_creation })
}
return {
app: {
id: app.app_id,
name: app.name,
balance: app.owner.balance_sats
balance: app.owner.balance_sats,
npub: app.nostr_public_key
},
auth_token: this.SignAppToken(app.app_id)
}
@ -78,7 +83,8 @@ export default class {
return {
name: app.name,
id: app.app_id,
balance: app.owner.balance_sats
balance: app.owner.balance_sats,
npub: app.nostr_public_key
}
}

View file

@ -15,8 +15,7 @@ export default class {
}
DecodeUserToken(token?: string): string {
throw new Error("users methods temporarely disabled")
/*if (!token) throw new Error("empty user token provided")
if (!token) throw new Error("empty user token provided")
let t = token
if (token.startsWith("Bearer ")) {
t = token.substring("Bearer ".length)
@ -26,7 +25,7 @@ export default class {
if (!decoded.userId) {
throw new Error("the provided token is not an app token")
}
return decoded.userId*/
return decoded.userId
}
async AddBasicUser(req: Types.AddUserRequest): Promise<Types.AddUserResponse> {

View file

@ -1,15 +1,17 @@
import { SimplePool, Sub, Event, UnsignedEvent, nip04, getEventHash, signEvent } from 'nostr-tools'
//import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, signEvent } from 'nostr-tools'
import { SimplePool, Sub, Event, UnsignedEvent, getEventHash, finishEvent, relayInit } from './tools/index.js'
import { encryptData, decryptData, getSharedSecret, decodePayload, encodePayload } from './nip44.js'
const handledEvents: string[] = [] // TODO: - big memory leak here, add TTL
type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string }
export type NostrSettings = {
privateKey: string
publicKey: string
apps: AppInfo[]
relays: string[]
allowedPubs: string[]
}
export type NostrEvent = {
id: string
pub: string
content: string
appId: string
}
type SettingsRequest = {
type: 'settings'
@ -18,6 +20,7 @@ type SettingsRequest = {
type SendRequest = {
type: 'send'
appId: string
pub: string
message: string
}
@ -43,7 +46,7 @@ process.on("message", (message: ChildProcessRequest) => {
initSubprocessHandler(message.settings)
break
case 'send':
sendToNostr(message.pub, message.message)
sendToNostr(message.appId, message.pub, message.message)
break
default:
console.error("unknown nostr request", message)
@ -62,30 +65,47 @@ const initSubprocessHandler = (settings: NostrSettings) => {
})
})
}
const sendToNostr = (pub: string, message: string) => {
const sendToNostr = (appId: string, pub: string, message: string) => {
if (!subProcessHandler) {
console.error("nostr was not initialized")
return
}
subProcessHandler.Send(pub, message)
subProcessHandler.Send(appId, pub, message)
}
send({ type: 'ready' })
export default class Handler {
pool = new SimplePool()
settings: NostrSettings
sub: Sub
subs: Sub[] = []
constructor(settings: NostrSettings, eventCallback: (event: NostrEvent) => void) {
this.settings = settings
console.log(settings)
this.sub = this.pool.sub(settings.relays, [
this.settings.apps.forEach(app => {
this.SubForApp(app, eventCallback)
})
}
async SubForApp(appInfo: AppInfo, eventCallback: (event: NostrEvent) => void) {
const relay = relayInit(this.settings.relays[0])
relay.on('connect', () => {
console.log(`connected to ${relay.url}`)
})
relay.on('error', () => {
console.log(`failed to connect to ${relay.url}`)
})
await relay.connect()
console.log("relay connected")
const sub = relay.sub([
{
since: Math.ceil(Date.now() / 1000),
kinds: [4],
'#p': [settings.publicKey],
'#p': [appInfo.publicKey],
}
])
this.sub.on("event", async (e) => {
sub.on("event", async (e) => {
console.log({ nostrEvent: e })
if (e.kind !== 4 || !e.pubkey) {
return
}
@ -96,22 +116,47 @@ export default class Handler {
return
}
handledEvents.push(eventId)
eventCallback({ id: eventId, content: await nip04.decrypt(this.settings.privateKey, e.pubkey, e.content), pub: e.pubkey })
const decoded = decodePayload(e.content)
const content = await decryptData(decoded, getSharedSecret(appInfo.privateKey, e.pubkey))
eventCallback({ id: eventId, content, pub: e.pubkey, appId: appInfo.appId })
//eventCallback({ id: eventId, content: await nip04.decrypt(appInfo.privateKey, e.pubkey, e.content), pub: e.pubkey, appId: appInfo.appId })
})
this.subs.push(sub)
}
async Send(appId: string, pubKey: string, message: string) {
const appInfo = this.GetAppKeys({ appId })
const decoded = await encryptData(message, getSharedSecret(appInfo.privateKey, pubKey))
const content = encodePayload(decoded)
const event: UnsignedEvent = {
content,
created_at: Math.floor(Date.now() / 1000),
kind: 4,
pubkey: appInfo.publicKey,
tags: [['p', pubKey]],
}
const signed = finishEvent(event, appInfo.privateKey)
this.pool.publish(this.settings.relays, signed).forEach(p => {
p.then(() => console.log("sent ok"))
p.catch(() => console.log("failed to send"))
})
}
async Send(nostrPub: string, message: string) {
const event: UnsignedEvent = {
content: await nip04.encrypt(this.settings.privateKey, nostrPub, message),
created_at: Math.floor(Date.now() / 1000),
kind: 4,
pubkey: this.settings.publicKey,
tags: [['p', nostrPub]],
GetAppKeys(appInfo: Partial<AppInfo>) {
let check: (info: AppInfo) => boolean
if (appInfo.appId) {
check = (info: AppInfo) => info.appId === appInfo.appId
} else if (appInfo.privateKey) {
check = (info: AppInfo) => info.privateKey === appInfo.privateKey
} else if (appInfo.publicKey) {
check = (info: AppInfo) => info.publicKey === appInfo.publicKey
} else {
throw new Error("app info is empty")
}
const eventId = getEventHash(event)
const sign = signEvent(event, this.settings.privateKey)
const op = this.pool.publish(this.settings.relays,
{ ...event, id: eventId, sig: sign })
op.on('failed', (reason: string) => { console.log('failed to send message cuz: ', reason) })
const found = this.settings.apps.find(check)
if (!found) {
throw new Error("unkown app")
}
return found
}
}

View file

@ -1,52 +0,0 @@
import 'dotenv/config' // TODO - test env
import { Buffer } from 'buffer'
import { generatePrivateKey, getPublicKey } from 'nostr-tools'
import NostrHandler, { LoadNosrtSettingsFromEnv } from './index.js'
import { expect } from 'chai'
export const ignore = true
const settings = LoadNosrtSettingsFromEnv(true)
const clientPrivateKey = generatePrivateKey()
const clientPublicKey = getPublicKey(clientPrivateKey)
const serverPrivateKey = generatePrivateKey()
const serverPublicKey = getPublicKey(serverPrivateKey)
let clientNostr: NostrHandler
let serverNostr: NostrHandler
let receivedServerEvents = 0
let latestReceivedServerEvent = ""
export const setup = () => {
clientNostr = new NostrHandler({
allowedPubs: [],
privateKey: clientPrivateKey,
publicKey: clientPublicKey,
relays: settings.relays
}, (event) => {
})
serverNostr = new NostrHandler({
allowedPubs: [clientPublicKey],
privateKey: serverPrivateKey,
publicKey: serverPublicKey,
relays: settings.relays
}, (event) => {
receivedServerEvents++
latestReceivedServerEvent = event.content
})
}
export const teardown = () => {
clientNostr.Stop()
serverNostr.Stop()
}
export default async (d: (message: string, failure?: boolean) => void) => {
await new Promise(res => setTimeout(res, 2000))
clientNostr.Send(serverPublicKey, "test")
await new Promise(res => setTimeout(res, 1000))
console.log(receivedServerEvents, latestReceivedServerEvent)
expect(receivedServerEvents).to.equal(1)
expect(latestReceivedServerEvent).to.equal("test")
d("nostr ok")
}

View file

@ -4,9 +4,6 @@ import { NostrSettings, NostrEvent, ChildProcessRequest, ChildProcessResponse }
type EventCallback = (event: NostrEvent) => void
export const LoadNosrtSettingsFromEnv = (test = false) => {
return {
allowedPubs: EnvMustBeNonEmptyString("NOSTR_ALLOWED_PUBS").split(' '),
privateKey: EnvMustBeNonEmptyString("NOSTR_PRIVATE_KEY"),
publicKey: EnvMustBeNonEmptyString("NOSTR_PUBLIC_KEY"),
relays: EnvMustBeNonEmptyString("NOSTR_RELAYS").split(' ')
}
}
@ -34,8 +31,8 @@ export default class NostrSubprocess {
this.childProcess.send(message)
}
Send(pub: string, message: string) {
this.sendToChildProcess({ type: 'send', pub: pub, message: message })
Send(appId: string, pub: string, message: string) {
this.sendToChildProcess({ type: 'send', pub, message, appId })
}
Stop() {
this.childProcess.kill()

View file

@ -0,0 +1,55 @@
import { base64 } from "@scure/base";
import { randomBytes } from "@noble/hashes/utils";
import { streamXOR as xchacha20 } from "@stablelib/xchacha20";
import { secp256k1 } from "@noble/curves/secp256k1";
import { sha256 } from "@noble/hashes/sha256";
type EncryptedData = {
ciphertext: Uint8Array;
nonce: Uint8Array;
}
export const getSharedSecret = (privateKey: string, publicKey: string) => {
const key = secp256k1.getSharedSecret(privateKey, "02" + publicKey);
return sha256(key.slice(1, 33));
}
export const encryptData = (content: string, sharedSecret: Uint8Array) => {
const nonce = randomBytes(24);
const plaintext = new TextEncoder().encode(content);
const ciphertext = xchacha20(sharedSecret, nonce, plaintext, plaintext);
return {
ciphertext: Uint8Array.from(ciphertext),
nonce: nonce,
} as EncryptedData;
}
export const decryptData = (payload: EncryptedData, sharedSecret: Uint8Array) => {
const dst = xchacha20(sharedSecret, payload.nonce, payload.ciphertext, payload.ciphertext);
const decoded = new TextDecoder().decode(dst);
return decoded;
}
const xchacha20EncryptionVersion = 1
export const decodePayload = (p: string) => {
if (p.startsWith("{") && p.endsWith("}")) {
const pj = JSON.parse(p) as { v: number; nonce: string; ciphertext: string };
if (pj.v !== xchacha20EncryptionVersion) {
throw new Error("Encryption version unsupported")
}
return {
nonce: base64.decode(pj.nonce),
ciphertext: base64.decode(pj.ciphertext),
} as EncryptedData;
} else {
const buf = base64.decode(p);
if (buf[0] !== xchacha20EncryptionVersion) {
throw new Error("Encryption version unsupported")
}
return {
nonce: buf.subarray(1, 25),
ciphertext: buf.subarray(25),
} as EncryptedData;
}
}
export const encodePayload = (p: EncryptedData) => {
return base64.encode(new Uint8Array([xchacha20EncryptionVersion, ...p.nonce, ...p.ciphertext]));
}

View file

@ -0,0 +1,144 @@
import { schnorr } from '@noble/curves/secp256k1'
import { sha256 } from '@noble/hashes/sha256'
import { bytesToHex } from '@noble/hashes/utils'
import { getPublicKey } from './keys.js'
import { utf8Encoder } from './utils.js'
/** Designates a verified event signature. */
export const verifiedSymbol = Symbol('verified')
/** @deprecated Use numbers instead. */
/* eslint-disable no-unused-vars */
export enum Kind {
Metadata = 0,
Text = 1,
RecommendRelay = 2,
Contacts = 3,
EncryptedDirectMessage = 4,
EventDeletion = 5,
Repost = 6,
Reaction = 7,
BadgeAward = 8,
ChannelCreation = 40,
ChannelMetadata = 41,
ChannelMessage = 42,
ChannelHideMessage = 43,
ChannelMuteUser = 44,
Blank = 255,
Report = 1984,
ZapRequest = 9734,
Zap = 9735,
RelayList = 10002,
ClientAuth = 22242,
HttpAuth = 27235,
ProfileBadge = 30008,
BadgeDefinition = 30009,
Article = 30023,
FileMetadata = 1063,
}
export interface Event<K extends number = number> {
kind: K
tags: string[][]
content: string
created_at: number
pubkey: string
id: string
sig: string
[verifiedSymbol]?: boolean
}
export type EventTemplate<K extends number = number> = Pick<Event<K>, 'kind' | 'tags' | 'content' | 'created_at'>
export type UnsignedEvent<K extends number = number> = Pick<
Event<K>,
'kind' | 'tags' | 'content' | 'created_at' | 'pubkey'
>
/** An event whose signature has been verified. */
export interface VerifiedEvent<K extends number = number> extends Event<K> {
[verifiedSymbol]: true
}
export function getBlankEvent(): EventTemplate<Kind.Blank>
export function getBlankEvent<K extends number>(kind: K): EventTemplate<K>
export function getBlankEvent<K>(kind: K | Kind.Blank = Kind.Blank) {
return {
kind,
content: '',
tags: [],
created_at: 0,
}
}
export function finishEvent<K extends number = number>(t: EventTemplate<K>, privateKey: string): VerifiedEvent<K> {
const event = t as VerifiedEvent<K>
event.pubkey = getPublicKey(privateKey)
event.id = getEventHash(event)
event.sig = getSignature(event, privateKey)
event[verifiedSymbol] = true
return event
}
export function serializeEvent(evt: UnsignedEvent<number>): string {
if (!validateEvent(evt)) throw new Error("can't serialize event with wrong or missing properties")
return JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content])
}
export function getEventHash(event: UnsignedEvent<number>): string {
let eventHash = sha256(utf8Encoder.encode(serializeEvent(event)))
return bytesToHex(eventHash)
}
const isRecord = (obj: unknown): obj is Record<string, unknown> => obj instanceof Object
export function validateEvent<T>(event: T): event is T & UnsignedEvent<number> {
if (!isRecord(event)) return false
if (typeof event.kind !== 'number') return false
if (typeof event.content !== 'string') return false
if (typeof event.created_at !== 'number') return false
if (typeof event.pubkey !== 'string') return false
if (!event.pubkey.match(/^[a-f0-9]{64}$/)) return false
if (!Array.isArray(event.tags)) return false
for (let i = 0; i < event.tags.length; i++) {
let tag = event.tags[i]
if (!Array.isArray(tag)) return false
for (let j = 0; j < tag.length; j++) {
if (typeof tag[j] === 'object') return false
}
}
return true
}
/** Verify the event's signature. This function mutates the event with a `verified` symbol, making it idempotent. */
export function verifySignature<K extends number>(event: Event<K>): event is VerifiedEvent<K> {
//@ts-ignore
if (typeof event[verifiedSymbol] === 'boolean') return event[verifiedSymbol]
const hash = getEventHash(event)
if (hash !== event.id) {
return (event[verifiedSymbol] = false)
}
try {
return (event[verifiedSymbol] = schnorr.verify(event.sig, hash, event.pubkey))
} catch (err) {
return (event[verifiedSymbol] = false)
}
}
/** @deprecated Use `getSignature` instead. */
export function signEvent(event: UnsignedEvent<number>, key: string): string {
console.warn(
'nostr-tools: `signEvent` is deprecated and will be removed or changed in the future. Please use `getSignature` instead.',
)
return getSignature(event, key)
}
/** Calculate the signature for an event. */
export function getSignature(event: UnsignedEvent<number>, key: string): string {
return bytesToHex(schnorr.sign(getEventHash(event), key))
}

View file

@ -0,0 +1,41 @@
export function getHex64(json: string, field: string): string {
let len = field.length + 3
let idx = json.indexOf(`"${field}":`) + len
let s = json.slice(idx).indexOf(`"`) + idx + 1
return json.slice(s, s + 64)
}
export function getInt(json: string, field: string): number {
let len = field.length
let idx = json.indexOf(`"${field}":`) + len + 3
let sliced = json.slice(idx)
let end = Math.min(sliced.indexOf(','), sliced.indexOf('}'))
return parseInt(sliced.slice(0, end), 10)
}
export function getSubscriptionId(json: string): string | null {
let idx = json.slice(0, 22).indexOf(`"EVENT"`)
if (idx === -1) return null
let pstart = json.slice(idx + 7 + 1).indexOf(`"`)
if (pstart === -1) return null
let start = idx + 7 + 1 + pstart
let pend = json.slice(start + 1, 80).indexOf(`"`)
if (pend === -1) return null
let end = start + 1 + pend
return json.slice(start + 1, end)
}
export function matchEventId(json: string, id: string): boolean {
return id === getHex64(json, 'id')
}
export function matchEventPubkey(json: string, pubkey: string): boolean {
return pubkey === getHex64(json, 'pubkey')
}
export function matchEventKind(json: string, kind: number): boolean {
return kind === getInt(json, 'kind')
}

View file

@ -0,0 +1,72 @@
import { Event } from './event.js'
export type Filter<K extends number = number> = {
ids?: string[]
kinds?: K[]
authors?: string[]
since?: number
until?: number
limit?: number
search?: string
[key: `#${string}`]: string[] | undefined
}
export function matchFilter(filter: Filter<number>, event: Event<number>): boolean {
if (filter.ids && filter.ids.indexOf(event.id) === -1) {
if (!filter.ids.some(prefix => event.id.startsWith(prefix))) {
return false
}
}
if (filter.kinds && filter.kinds.indexOf(event.kind) === -1) return false
if (filter.authors && filter.authors.indexOf(event.pubkey) === -1) {
if (!filter.authors.some(prefix => event.pubkey.startsWith(prefix))) {
return false
}
}
for (let f in filter) {
if (f[0] === '#') {
let tagName = f.slice(1)
let values = filter[`#${tagName}`]
if (values && !event.tags.find(([t, v]) => t === f.slice(1) && values!.indexOf(v) !== -1)) return false
}
}
if (filter.since && event.created_at < filter.since) return false
if (filter.until && event.created_at > filter.until) return false
return true
}
export function matchFilters(filters: Filter<number>[], event: Event<number>): boolean {
for (let i = 0; i < filters.length; i++) {
if (matchFilter(filters[i], event)) return true
}
return false
}
export function mergeFilters(...filters: Filter<number>[]): Filter<number> {
let result: Filter<number> = {}
for (let i = 0; i < filters.length; i++) {
let filter = filters[i]
Object.entries(filter).forEach(([property, values]) => {
if (property === 'kinds' || property === 'ids' || property === 'authors' || property[0] === '#') {
// @ts-ignore
result[property] = result[property] || []
// @ts-ignore
for (let v = 0; v < values.length; v++) {
// @ts-ignore
let value = values[v]
// @ts-ignore
if (!result[property].includes(value)) result[property].push(value)
}
}
})
if (filter.limit && (!result.limit || filter.limit > result.limit)) result.limit = filter.limit
if (filter.until && (!result.until || filter.until > result.until)) result.until = filter.until
if (filter.since && (!result.since || filter.since < result.since)) result.since = filter.since
}
return result
}

View file

@ -0,0 +1,7 @@
export * from './event.js'
export * from './fakejson.js'
export * from './filter.js'
export * from './keys.js'
export * from './pool.js'
export * from './relay.js'
export * from './utils.js'

View file

@ -0,0 +1,10 @@
import { schnorr } from '@noble/curves/secp256k1'
import { bytesToHex } from '@noble/hashes/utils'
export function generatePrivateKey(): string {
return bytesToHex(schnorr.utils.randomPrivateKey())
}
export function getPublicKey(privateKey: string): string {
return bytesToHex(schnorr.getPublicKey(privateKey))
}

View file

@ -0,0 +1,249 @@
import { relayInit, eventsGenerator, type Relay, type Sub, type SubscriptionOptions } from './relay.js'
import { normalizeURL } from './utils.js'
import type { Event } from './event.js'
import { matchFilters, type Filter } from './filter.js'
type BatchedRequest = {
filters: Filter<any>[]
relays: string[]
resolve: (events: Event<any>[]) => void
events: Event<any>[]
}
export class SimplePool {
private _conn: { [url: string]: Relay }
private _seenOn: { [id: string]: Set<string> } = {} // a map of all events we've seen in each relay
private batchedByKey: { [batchKey: string]: BatchedRequest[] } = {}
private eoseSubTimeout: number
private getTimeout: number
private seenOnEnabled: boolean = true
private batchInterval: number = 100
constructor(
options: {
eoseSubTimeout?: number
getTimeout?: number
seenOnEnabled?: boolean
batchInterval?: number
} = {},
) {
this._conn = {}
this.eoseSubTimeout = options.eoseSubTimeout || 3400
this.getTimeout = options.getTimeout || 3400
this.seenOnEnabled = options.seenOnEnabled !== false
this.batchInterval = options.batchInterval || 100
}
close(relays: string[]): void {
relays.forEach(url => {
let relay = this._conn[normalizeURL(url)]
if (relay) relay.close()
})
}
async ensureRelay(url: string): Promise<Relay> {
const nm = normalizeURL(url)
if (!this._conn[nm]) {
this._conn[nm] = relayInit(nm, {
getTimeout: this.getTimeout * 0.9,
listTimeout: this.getTimeout * 0.9,
})
}
const relay = this._conn[nm]
await relay.connect()
return relay
}
sub<K extends number = number>(relays: string[], filters: Filter<K>[], opts?: SubscriptionOptions): Sub<K> {
let _knownIds: Set<string> = new Set()
let modifiedOpts = { ...(opts || {}) }
modifiedOpts.alreadyHaveEvent = (id, url) => {
if (opts?.alreadyHaveEvent?.(id, url)) {
return true
}
if (this.seenOnEnabled) {
let set = this._seenOn[id] || new Set()
set.add(url)
this._seenOn[id] = set
}
return _knownIds.has(id)
}
let subs: Sub[] = []
let eventListeners: Set<any> = new Set()
let eoseListeners: Set<() => void> = new Set()
let eosesMissing = relays.length
let eoseSent = false
let eoseTimeout = setTimeout(
() => {
eoseSent = true
for (let cb of eoseListeners.values()) cb()
},
opts?.eoseSubTimeout || this.eoseSubTimeout,
)
relays
.filter((r, i, a) => a.indexOf(r) === i)
.forEach(async relay => {
let r
try {
r = await this.ensureRelay(relay)
} catch (err) {
handleEose()
return
}
if (!r) return
let s = r.sub(filters, modifiedOpts)
s.on('event', event => {
_knownIds.add(event.id as string)
for (let cb of eventListeners.values()) cb(event)
})
s.on('eose', () => {
if (eoseSent) return
handleEose()
})
subs.push(s)
function handleEose() {
eosesMissing--
if (eosesMissing === 0) {
clearTimeout(eoseTimeout)
for (let cb of eoseListeners.values()) cb()
}
}
})
let greaterSub: Sub<K> = {
sub(filters, opts) {
subs.forEach(sub => sub.sub(filters, opts))
return greaterSub as any
},
unsub() {
subs.forEach(sub => sub.unsub())
},
on(type, cb) {
if (type === 'event') {
eventListeners.add(cb)
} else if (type === 'eose') {
eoseListeners.add(cb as () => void | Promise<void>)
}
},
off(type, cb) {
if (type === 'event') {
eventListeners.delete(cb)
} else if (type === 'eose') eoseListeners.delete(cb as () => void | Promise<void>)
},
get events() {
return eventsGenerator(greaterSub)
},
}
return greaterSub
}
get<K extends number = number>(
relays: string[],
filter: Filter<K>,
opts?: SubscriptionOptions,
): Promise<Event<K> | null> {
return new Promise(resolve => {
let sub = this.sub(relays, [filter], opts)
let timeout = setTimeout(() => {
sub.unsub()
resolve(null)
}, this.getTimeout)
sub.on('event', event => {
resolve(event)
clearTimeout(timeout)
sub.unsub()
})
})
}
list<K extends number = number>(
relays: string[],
filters: Filter<K>[],
opts?: SubscriptionOptions,
): Promise<Event<K>[]> {
return new Promise(resolve => {
let events: Event<K>[] = []
let sub = this.sub(relays, filters, opts)
sub.on('event', event => {
events.push(event)
})
// we can rely on an eose being emitted here because pool.sub() will fake one
sub.on('eose', () => {
sub.unsub()
resolve(events)
})
})
}
batchedList<K extends number = number>(
batchKey: string,
relays: string[],
filters: Filter<K>[],
): Promise<Event<K>[]> {
return new Promise(resolve => {
if (!this.batchedByKey[batchKey]) {
this.batchedByKey[batchKey] = [
{
filters,
relays,
resolve,
events: [],
},
]
setTimeout(() => {
Object.keys(this.batchedByKey).forEach(async batchKey => {
const batchedRequests = this.batchedByKey[batchKey]
const filters = [] as Filter[]
const relays = [] as string[]
batchedRequests.forEach(br => {
filters.push(...br.filters)
relays.push(...br.relays)
})
const sub = this.sub(relays, filters)
sub.on('event', event => {
batchedRequests.forEach(br => matchFilters(br.filters, event) && br.events.push(event))
})
sub.on('eose', () => {
sub.unsub()
batchedRequests.forEach(br => br.resolve(br.events))
})
delete this.batchedByKey[batchKey]
})
}, this.batchInterval)
} else {
this.batchedByKey[batchKey].push({
filters,
relays,
resolve,
events: [],
})
}
})
}
publish(relays: string[], event: Event<number>): Promise<void>[] {
return relays.map(async relay => {
let r = await this.ensureRelay(relay)
return r.publish(event)
})
}
seenOn(id: string): string[] {
return Array.from(this._seenOn[id]?.values?.() || [])
}
}

View file

@ -0,0 +1,402 @@
/* global WebSocket */
import "websocket-polyfill"
import { verifySignature, validateEvent, type Event } from './event.js'
import { matchFilters, type Filter } from './filter.js'
import { getHex64, getSubscriptionId } from './fakejson.js'
import { MessageQueue } from './utils.js'
type RelayEvent = {
connect: () => void | Promise<void>
disconnect: () => void | Promise<void>
error: () => void | Promise<void>
notice: (msg: string) => void | Promise<void>
auth: (challenge: string) => void | Promise<void>
}
export type CountPayload = {
count: number
}
export type SubEvent<K extends number> = {
event: (event: Event<K>) => void | Promise<void>
count: (payload: CountPayload) => void | Promise<void>
eose: () => void | Promise<void>
}
export type Relay = {
url: string
status: number
connect: () => Promise<void>
close: () => void
sub: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Sub<K>
list: <K extends number = number>(filters: Filter<K>[], opts?: SubscriptionOptions) => Promise<Event<K>[]>
get: <K extends number = number>(filter: Filter<K>, opts?: SubscriptionOptions) => Promise<Event<K> | null>
count: (filters: Filter[], opts?: SubscriptionOptions) => Promise<CountPayload | null>
publish: (event: Event<number>) => Promise<void>
auth: (event: Event<number>) => Promise<void>
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(event: T, listener: U) => void
}
export type Sub<K extends number = number> = {
sub: <K extends number = number>(filters: Filter<K>[], opts: SubscriptionOptions) => Sub<K>
unsub: () => void
on: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void
off: <T extends keyof SubEvent<K>, U extends SubEvent<K>[T]>(event: T, listener: U) => void
events: AsyncGenerator<Event<K>, void, unknown>
}
export type SubscriptionOptions = {
id?: string
verb?: 'REQ' | 'COUNT'
skipVerification?: boolean
alreadyHaveEvent?: null | ((id: string, relay: string) => boolean)
eoseSubTimeout?: number
}
const newListeners = (): { [TK in keyof RelayEvent]: RelayEvent[TK][] } => ({
connect: [],
disconnect: [],
error: [],
notice: [],
auth: [],
})
export function relayInit(
url: string,
options: {
getTimeout?: number
listTimeout?: number
countTimeout?: number
} = {},
): Relay {
let { listTimeout = 3000, getTimeout = 3000, countTimeout = 3000 } = options
var ws: WebSocket
var openSubs: { [id: string]: { filters: Filter[] } & SubscriptionOptions } = {}
var listeners = newListeners()
var subListeners: {
[subid: string]: { [TK in keyof SubEvent<any>]: SubEvent<any>[TK][] }
} = {}
var pubListeners: {
[eventid: string]: {
resolve: (_: unknown) => void
reject: (err: Error) => void
}
} = {}
var connectionPromise: Promise<void> | undefined
async function connectRelay(): Promise<void> {
if (connectionPromise) return connectionPromise
connectionPromise = new Promise((resolve, reject) => {
try {
ws = new WebSocket(url)
} catch (err) {
reject(err)
}
ws.onopen = () => {
listeners.connect.forEach(cb => cb())
resolve()
}
ws.onerror = () => {
connectionPromise = undefined
listeners.error.forEach(cb => cb())
reject()
}
ws.onclose = async () => {
connectionPromise = undefined
listeners.disconnect.forEach(cb => cb())
}
let incomingMessageQueue: MessageQueue = new MessageQueue()
let handleNextInterval: any
ws.onmessage = e => {
incomingMessageQueue.enqueue(e.data)
if (!handleNextInterval) {
handleNextInterval = setInterval(handleNext, 0)
}
}
function handleNext() {
if (incomingMessageQueue.size === 0) {
clearInterval(handleNextInterval)
handleNextInterval = null
return
}
var json = incomingMessageQueue.dequeue()
if (!json) return
let subid = getSubscriptionId(json)
if (subid) {
let so = openSubs[subid]
if (so && so.alreadyHaveEvent && so.alreadyHaveEvent(getHex64(json, 'id'), url)) {
return
}
}
try {
let data = JSON.parse(json)
// we won't do any checks against the data since all failures (i.e. invalid messages from relays)
// will naturally be caught by the encompassing try..catch block
switch (data[0]) {
case 'EVENT': {
let id = data[1]
let event = data[2]
if (
validateEvent(event) &&
openSubs[id] &&
(openSubs[id].skipVerification || verifySignature(event)) &&
matchFilters(openSubs[id].filters, event)
) {
openSubs[id]
; (subListeners[id]?.event || []).forEach(cb => cb(event))
}
return
}
case 'COUNT':
let id = data[1]
let payload = data[2]
if (openSubs[id]) {
; (subListeners[id]?.count || []).forEach(cb => cb(payload))
}
return
case 'EOSE': {
let id = data[1]
if (id in subListeners) {
subListeners[id].eose.forEach(cb => cb())
subListeners[id].eose = [] // 'eose' only happens once per sub, so stop listeners here
}
return
}
case 'OK': {
let id: string = data[1]
let ok: boolean = data[2]
let reason: string = data[3] || ''
if (id in pubListeners) {
let { resolve, reject } = pubListeners[id]
if (ok) resolve(null)
else reject(new Error(reason))
}
return
}
case 'NOTICE':
let notice = data[1]
listeners.notice.forEach(cb => cb(notice))
return
case 'AUTH': {
let challenge = data[1]
listeners.auth?.forEach(cb => cb(challenge))
return
}
}
} catch (err) {
return
}
}
})
return connectionPromise
}
function connected() {
return ws?.readyState === 1
}
async function connect(): Promise<void> {
if (connected()) return // ws already open
await connectRelay()
}
async function trySend(params: [string, ...any]) {
let msg = JSON.stringify(params)
if (!connected()) {
await new Promise(resolve => setTimeout(resolve, 1000))
if (!connected()) {
return
}
}
try {
ws.send(msg)
} catch (err) {
console.log(err)
}
}
const sub = <K extends number = number>(
filters: Filter<K>[],
{
verb = 'REQ',
skipVerification = false,
alreadyHaveEvent = null,
id = Math.random().toString().slice(2),
}: SubscriptionOptions = {},
): Sub<K> => {
let subid = id
openSubs[subid] = {
id: subid,
filters,
skipVerification,
alreadyHaveEvent,
}
trySend([verb, subid, ...filters])
let subscription: Sub<K> = {
sub: (newFilters, newOpts = {}) =>
sub(newFilters || filters, {
skipVerification: newOpts.skipVerification || skipVerification,
alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent,
id: subid,
}),
unsub: () => {
delete openSubs[subid]
delete subListeners[subid]
trySend(['CLOSE', subid])
},
on: (type, cb) => {
subListeners[subid] = subListeners[subid] || {
event: [],
count: [],
eose: [],
}
//@ts-ignore
subListeners[subid][type].push(cb)
},
off: (type, cb): void => {
let listeners = subListeners[subid]
//@ts-ignore
let idx = listeners[type].indexOf(cb)
if (idx >= 0) listeners[type].splice(idx, 1)
},
get events() {
return eventsGenerator(subscription)
},
}
return subscription
}
function _publishEvent(event: Event<number>, type: string) {
return new Promise((resolve, reject) => {
if (!event.id) {
reject(new Error(`event ${event} has no id`))
return
}
let id = event.id
trySend([type, event])
pubListeners[id] = { resolve, reject }
})
}
return {
url,
sub,
on: <T extends keyof RelayEvent, U extends RelayEvent[T]>(type: T, cb: U): void => {
//@ts-ignore
listeners[type].push(cb)
if (type === 'connect' && ws?.readyState === 1) {
// i would love to know why we need this
; (cb as () => void)()
}
},
off: <T extends keyof RelayEvent, U extends RelayEvent[T]>(type: T, cb: U): void => {
//@ts-ignore
let index = listeners[type].indexOf(cb)
if (index !== -1) listeners[type].splice(index, 1)
},
list: (filters, opts?: SubscriptionOptions) =>
new Promise(resolve => {
let s = sub(filters, opts)
let events: Event<any>[] = []
let timeout = setTimeout(() => {
s.unsub()
resolve(events)
}, listTimeout)
s.on('eose', () => {
s.unsub()
clearTimeout(timeout)
resolve(events)
})
s.on('event', event => {
events.push(event)
})
}),
get: (filter, opts?: SubscriptionOptions) =>
new Promise(resolve => {
let s = sub([filter], opts)
let timeout = setTimeout(() => {
s.unsub()
resolve(null)
}, getTimeout)
s.on('event', event => {
s.unsub()
clearTimeout(timeout)
resolve(event)
})
}),
count: (filters: Filter[]): Promise<CountPayload | null> =>
new Promise(resolve => {
let s = sub(filters, { ...sub, verb: 'COUNT' })
let timeout = setTimeout(() => {
s.unsub()
resolve(null)
}, countTimeout)
s.on('count', (event: CountPayload) => {
s.unsub()
clearTimeout(timeout)
resolve(event)
})
}),
async publish(event): Promise<void> {
await _publishEvent(event, 'EVENT')
},
async auth(event): Promise<void> {
await _publishEvent(event, 'AUTH')
},
connect,
close(): void {
listeners = newListeners()
subListeners = {}
pubListeners = {}
if (ws?.readyState === WebSocket.OPEN) {
ws.close()
}
},
get status() {
return ws?.readyState ?? 3
},
}
}
export async function* eventsGenerator<K extends number>(sub: Sub<K>): AsyncGenerator<Event<K>, void, unknown> {
let nextResolve: ((event: Event<K>) => void) | undefined
const eventQueue: Event<K>[] = []
const pushToQueue = (event: Event<K>) => {
if (nextResolve) {
nextResolve(event)
nextResolve = undefined
} else {
eventQueue.push(event)
}
}
sub.on('event', pushToQueue)
try {
while (true) {
if (eventQueue.length > 0) {
yield eventQueue.shift()!
} else {
const event = await new Promise<Event<K>>(resolve => {
nextResolve = resolve
})
yield event
}
}
} finally {
sub.off('event', pushToQueue)
}
}

View file

@ -0,0 +1,169 @@
import type { Event } from './event.js'
export const utf8Decoder = new TextDecoder('utf-8')
export const utf8Encoder = new TextEncoder()
export function normalizeURL(url: string): string {
let p = new URL(url)
p.pathname = p.pathname.replace(/\/+/g, '/')
if (p.pathname.endsWith('/')) p.pathname = p.pathname.slice(0, -1)
if ((p.port === '80' && p.protocol === 'ws:') || (p.port === '443' && p.protocol === 'wss:')) p.port = ''
p.searchParams.sort()
p.hash = ''
return p.toString()
}
//
// fast insert-into-sorted-array functions adapted from https://github.com/terrymorse58/fast-sorted-array
//
export function insertEventIntoDescendingList(sortedArray: Event<number>[], event: Event<number>) {
let start = 0
let end = sortedArray.length - 1
let midPoint
let position = start
if (end < 0) {
position = 0
} else if (event.created_at < sortedArray[end].created_at) {
position = end + 1
} else if (event.created_at >= sortedArray[start].created_at) {
position = start
} else
while (true) {
if (end <= start + 1) {
position = end
break
}
midPoint = Math.floor(start + (end - start) / 2)
if (sortedArray[midPoint].created_at > event.created_at) {
start = midPoint
} else if (sortedArray[midPoint].created_at < event.created_at) {
end = midPoint
} else {
// aMidPoint === num
position = midPoint
break
}
}
// insert when num is NOT already in (no duplicates)
if (sortedArray[position]?.id !== event.id) {
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)]
}
return sortedArray
}
export function insertEventIntoAscendingList(sortedArray: Event<number>[], event: Event<number>) {
let start = 0
let end = sortedArray.length - 1
let midPoint
let position = start
if (end < 0) {
position = 0
} else if (event.created_at > sortedArray[end].created_at) {
position = end + 1
} else if (event.created_at <= sortedArray[start].created_at) {
position = start
} else
while (true) {
if (end <= start + 1) {
position = end
break
}
midPoint = Math.floor(start + (end - start) / 2)
if (sortedArray[midPoint].created_at < event.created_at) {
start = midPoint
} else if (sortedArray[midPoint].created_at > event.created_at) {
end = midPoint
} else {
// aMidPoint === num
position = midPoint
break
}
}
// insert when num is NOT already in (no duplicates)
if (sortedArray[position]?.id !== event.id) {
return [...sortedArray.slice(0, position), event, ...sortedArray.slice(position)]
}
return sortedArray
}
export class MessageNode {
private _value: string
private _next: MessageNode | null
public get value(): string {
return this._value
}
public set value(message: string) {
this._value = message
}
public get next(): MessageNode | null {
return this._next
}
public set next(node: MessageNode | null) {
this._next = node
}
constructor(message: string) {
this._value = message
this._next = null
}
}
export class MessageQueue {
private _first: MessageNode | null
private _last: MessageNode | null
public get first(): MessageNode | null {
return this._first
}
public set first(messageNode: MessageNode | null) {
this._first = messageNode
}
public get last(): MessageNode | null {
return this._last
}
public set last(messageNode: MessageNode | null) {
this._last = messageNode
}
private _size: number
public get size(): number {
return this._size
}
public set size(v: number) {
this._size = v
}
constructor() {
this._first = null
this._last = null
this._size = 0
}
enqueue(message: string): boolean {
const newNode = new MessageNode(message)
if (this._size === 0 || !this._last) {
this._first = newNode
this._last = newNode
} else {
this._last.next = newNode
this._last = newNode
}
this._size++
return true
}
dequeue(): string | null {
if (this._size === 0 || !this._first) return null
let prev = this._first
this._first = prev.next
prev.next = null
this._size--
return prev.value
}
}

View file

@ -1,5 +1,6 @@
import crypto from 'crypto';
import { DataSource, EntityManager } from "typeorm"
import { generatePrivateKey, getPublicKey } from 'nostr-tools';
import { Application } from "./entity/Application.js"
import UserStorage from './userStorage.js';
import { ApplicationUser } from './entity/ApplicationUser.js';
@ -12,13 +13,14 @@ export default class {
this.userStorage = userStorage
}
async AddApplication(name: string, entityManager = this.DB): Promise<Application> {
async AddApplication(name: string, allowUserCreation: boolean, entityManager = this.DB): Promise<Application> {
const owner = await this.userStorage.AddUser(0, entityManager)
const repo = entityManager.getRepository(Application)
const newApplication = repo.create({
app_id: crypto.randomBytes(32).toString('hex'),
name,
owner
owner,
allow_user_creation: allowUserCreation
})
return repo.save(newApplication)
}
@ -35,7 +37,13 @@ export default class {
return found
}
async GetApplications(entityManager = this.DB): Promise<Application[]> {
return entityManager.getRepository(Application).find()
}
async GetApplication(appId: string, entityManager = this.DB): Promise<Application> {
if (!appId) {
throw new Error("invalid app id provided")
}
const found = await entityManager.getRepository(Application).findOne({
where: {
app_id: appId
@ -47,7 +55,18 @@ export default class {
return found
}
async AddApplicationUser(application: Application, userIdentifier: string, balance: number) {
async UpdateApplication(app: Application, update: Partial<Application>, entityManager = this.DB) {
await entityManager.getRepository(Application).update(app.serial_id, update)
}
async GenerateApplicationKeys(app: Application) {
const priv = generatePrivateKey()
const pub = getPublicKey(priv)
await this.UpdateApplication(app, { nostr_private_key: priv, nostr_public_key: pub })
return { privateKey: priv, publicKey: pub, appId: app.app_id, name: app.name }
}
async AddApplicationUser(application: Application, userIdentifier: string, balance: number, nostrPub?: string) {
return this.DB.transaction(async tx => {
const user = await this.userStorage.AddUser(balance, tx)
const repo = tx.getRepository(ApplicationUser)
@ -55,6 +74,7 @@ export default class {
user: user,
application,
identifier: userIdentifier,
nostr_public_key: nostrPub
})
return repo.save(appUser)
})
@ -64,6 +84,20 @@ export default class {
return entityManager.getRepository(ApplicationUser).findOne({ where: { identifier: userIdentifier, application: { serial_id: application.serial_id } } })
}
async GetOrCreateNostrAppUser(application: Application, nostrPub: string, entityManager = this.DB): Promise<ApplicationUser> {
if (!nostrPub) {
throw new Error("no nostrPub provided")
}
const user = await entityManager.getRepository(ApplicationUser).findOne({ where: { application: { serial_id: application.serial_id }, nostr_public_key: nostrPub } })
if (user) {
return user
}
if (!application.allow_user_creation) {
throw new Error("user creation by client is not allowed in this app")
}
return this.AddApplicationUser(application, crypto.randomBytes(32).toString('hex'), 0, nostrPub)
}
async GetOrCreateApplicationUser(application: Application, userIdentifier: string, balance: number, entityManager = this.DB): Promise<{ user: ApplicationUser, created: boolean }> {
const user = await this.GetApplicationUserIfExists(application, userIdentifier, entityManager)
if (user) {

View file

@ -7,7 +7,6 @@ import { UserReceivingInvoice } from "./entity/UserReceivingInvoice.js"
import { UserInvoicePayment } from "./entity/UserInvoicePayment.js"
import { EnvMustBeNonEmptyString } from "../helpers/envParser.js"
import { UserTransactionPayment } from "./entity/UserTransactionPayment.js"
import { UserNostrAuth } from "./entity/UserNostrAuth.js"
import { UserBasicAuth } from "./entity/UserBasicAuth.js"
import { UserEphemeralKey } from "./entity/UserEphemeralKey.js"
import { Product } from "./entity/Product.js"
@ -25,7 +24,7 @@ export default async (settings: DbSettings) => {
type: "sqlite",
database: settings.databaseFile,
//logging: true,
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserNostrAuth, UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment],
entities: [User, UserReceivingInvoice, UserReceivingAddress, AddressReceivingTransaction, UserInvoicePayment, UserTransactionPayment, UserBasicAuth, UserEphemeralKey, Product, UserToUserPayment, Application, ApplicationUser, UserToUserPayment],
synchronize: true,
}).initialize()
}

View file

@ -18,6 +18,15 @@ export class Application {
@JoinColumn()
owner: User
@Column({ default: false })
allow_user_creation: boolean
@Column({ nullable: true, unique: true })
nostr_private_key: string
@Column({ nullable: true, unique: true })
nostr_public_key: string
@CreateDateColumn()
created_at: Date

View file

@ -20,6 +20,9 @@ export class ApplicationUser {
@Index({ unique: true })
identifier: string
@Column({ nullable: true, unique: true })
nostr_public_key: string
@CreateDateColumn()
created_at: Date

View file

@ -1,23 +0,0 @@
import { Entity, PrimaryGeneratedColumn, Column, Index, JoinColumn, OneToOne, CreateDateColumn, UpdateDateColumn } from "typeorm"
import { User } from "./User.js"
@Entity()
export class UserNostrAuth {
@PrimaryGeneratedColumn()
serial_id: number
@OneToOne(type => User, { eager: true })
@JoinColumn()
user: User
@Column()
@Index({ unique: true })
nostr_pub: string
@CreateDateColumn()
created_at: Date
@UpdateDateColumn()
updated_at: Date
}

View file

@ -1,18 +1,7 @@
import { DataSource, EntityManager, MoreThan, MoreThanOrEqual, TransactionAlreadyStartedError } from "typeorm"
import crypto from 'crypto';
import { DataSource, EntityManager } from "typeorm"
import NewDB, { DbSettings, LoadDbSettingsFromEnv } from "./db.js"
import { User } from "./entity/User.js"
import { UserReceivingAddress } from "./entity/UserReceivingAddress.js";
import { UserReceivingInvoice } from "./entity/UserReceivingInvoice.js";
import { AddressReceivingTransaction } from "./entity/AddressReceivingTransaction.js";
import { UserInvoicePayment } from "./entity/UserInvoicePayment.js";
import { UserTransactionPayment } from "./entity/UserTransactionPayment.js";
import { UserNostrAuth } from "./entity/UserNostrAuth.js";
import { UserBasicAuth } from "./entity/UserBasicAuth.js";
import { EphemeralKeyType, UserEphemeralKey } from "./entity/UserEphemeralKey.js";
import ProductStorage from './productStorage.js'
import ApplicationStorage from './applicationStorage.js'
import { Product } from "./entity/Product.js";
import UserStorage from "./userStorage.js";
import PaymentStorage from "./paymentStorage.js";
export type StorageSettings = {

View file

@ -1,8 +1,6 @@
import crypto from 'crypto';
import { DataSource, EntityManager, MoreThan, MoreThanOrEqual } from "typeorm"
import { User } from './entity/User.js';
import { UserBasicAuth } from './entity/UserBasicAuth.js';
import { UserNostrAuth } from './entity/UserNostrAuth.js';
import { UserTransactionPayment } from './entity/UserTransactionPayment.js';
import { EphemeralKeyType, UserEphemeralKey } from './entity/UserEphemeralKey.js';
import { UserReceivingInvoice } from './entity/UserReceivingInvoice.js';

View file

@ -2,7 +2,6 @@ import crypto from 'crypto';
import { DataSource, EntityManager } from "typeorm"
import { User } from './entity/User.js';
import { UserBasicAuth } from './entity/UserBasicAuth.js';
import { UserNostrAuth } from './entity/UserNostrAuth.js';
import { getLogger } from '../helpers/logger.js';
export default class {
DB: DataSource | EntityManager
@ -21,7 +20,6 @@ export default class {
return entityManager.getRepository(User).save(newUser)
}
async AddBasicUser(name: string, secret: string): Promise<UserBasicAuth> {
return this.DB.transaction(async tx => {
const user = await this.AddUser(0, tx)
@ -49,24 +47,6 @@ export default class {
return user
}
async FindNostrUser(nostrPub: string, entityManager = this.DB): Promise<UserNostrAuth | null> {
return entityManager.getRepository(UserNostrAuth).findOne({
where: { nostr_pub: nostrPub }
})
}
async AddNostrUser(nostrPub: string): Promise<UserNostrAuth> {
return this.DB.transaction(async tx => {
const user = await this.AddUser(0, tx)
const newAuth = tx.getRepository(UserNostrAuth).create({
user: user,
nostr_pub: nostrPub
})
return tx.getRepository(UserNostrAuth).save(newAuth)
})
}
async LockUser(userId: string, entityManager = this.DB) {
const res = await entityManager.getRepository(User).update({
user_id: userId