Gun Smith foundations
This commit is contained in:
parent
7133d97749
commit
fb25746072
6 changed files with 513 additions and 0 deletions
154
utils/GunSmith/GunT.ts
Normal file
154
utils/GunSmith/GunT.ts
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* @prettier
|
||||
*/
|
||||
namespace GunT {
|
||||
export type Primitive = boolean | string | number
|
||||
|
||||
export interface Data {
|
||||
[K: string]: ValidDataValue
|
||||
}
|
||||
|
||||
export type ValidDataValue = Primitive | null | Data
|
||||
|
||||
export interface Ack {
|
||||
err: string | undefined
|
||||
}
|
||||
|
||||
type ListenerObjSoul = {
|
||||
'#': string
|
||||
}
|
||||
|
||||
export type ListenerObj = Record<
|
||||
string,
|
||||
ListenerObjSoul | Primitive | null
|
||||
> & {
|
||||
_: ListenerObjSoul
|
||||
}
|
||||
|
||||
export type ListenerData = Primitive | null | ListenerObj | undefined
|
||||
|
||||
interface OpenListenerDataObj {
|
||||
[k: string]: OpenListenerData
|
||||
}
|
||||
|
||||
export type Listener = (data: ListenerData, key: string) => void
|
||||
export type Callback = (ack: Ack) => void
|
||||
|
||||
export interface Peer {
|
||||
url: string
|
||||
id: string
|
||||
wire?: {
|
||||
readyState: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface Soul {
|
||||
get: string
|
||||
put: Primitive | null | object | undefined
|
||||
opt: {
|
||||
peers: Record<string, Peer>
|
||||
}
|
||||
}
|
||||
|
||||
export type OpenListenerData = Primitive | null | OpenListenerDataObj
|
||||
export type OpenListener = (data: OpenListenerData, key: string) => void
|
||||
|
||||
export type LoadListenerData = OpenListenerData
|
||||
export type LoadListener = (data: LoadListenerData, key: string) => void
|
||||
|
||||
export interface GUNNodeBase {
|
||||
_: Soul
|
||||
|
||||
map(): GUNNode
|
||||
|
||||
on(this: GUNNode, cb: Listener): void
|
||||
once(this: GUNNode, cb?: Listener, opts?: { wait: number }): GUNNode
|
||||
|
||||
// open(this: GUNNode, cb?: OpenListener): GUNNode
|
||||
// load(this: GUNNode, cb?: OpenListener): GUNNode
|
||||
|
||||
// load(this: GUNNode, cb?: LoadListener): GUNNode
|
||||
|
||||
off(): void
|
||||
user(): UserGUNNode
|
||||
user(pub: string): GUNNode
|
||||
|
||||
then(): Promise<ListenerData>
|
||||
then<T>(cb: (v: ListenerData) => T): Promise<ListenerData>
|
||||
}
|
||||
|
||||
export interface GUNNode extends GUNNodeBase {
|
||||
get(key: string): GUNNode
|
||||
put(data: ValidDataValue, cb?: Callback): GUNNode
|
||||
set(data: ValidDataValue, cb?: Callback): GUNNode
|
||||
}
|
||||
|
||||
export interface CreateAck {
|
||||
pub: string | undefined
|
||||
err: string | undefined
|
||||
}
|
||||
|
||||
export type CreateCB = (ack: CreateAck) => void
|
||||
|
||||
export interface AuthAck {
|
||||
err: string | undefined
|
||||
sea:
|
||||
| {
|
||||
pub: string
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
|
||||
export type AuthCB = (ack: AuthAck) => void
|
||||
|
||||
export interface UserPair {
|
||||
epriv: string
|
||||
epub: string
|
||||
priv: string
|
||||
pub: string
|
||||
}
|
||||
|
||||
export interface UserSoul extends Soul {
|
||||
sea: UserPair
|
||||
}
|
||||
|
||||
export interface UserGUNNode extends GUNNode {
|
||||
_: UserSoul
|
||||
auth(user: string, pass: string, cb: AuthCB): void
|
||||
is?: {
|
||||
alias: string
|
||||
pub: string
|
||||
}
|
||||
create(user: string, pass: string, cb: CreateCB): void
|
||||
leave(): void
|
||||
}
|
||||
|
||||
export interface ISEA {
|
||||
encrypt(
|
||||
message: string | number | boolean,
|
||||
senderSecret: string
|
||||
): Promise<string>
|
||||
decrypt(encryptedMessage: string, recipientSecret: string): Promise<string>
|
||||
decryptNumber(
|
||||
encryptedMessage: string,
|
||||
recipientSecret: string
|
||||
): Promise<number>
|
||||
decryptBoolean(
|
||||
encryptedMessage: string,
|
||||
recipientSecret: string
|
||||
): Promise<boolean>
|
||||
secret(
|
||||
recipientOrSenderEpub: string,
|
||||
recipientOrSenderUserPair: UserPair
|
||||
): Promise<string>
|
||||
}
|
||||
|
||||
export interface MySEA {
|
||||
encrypt(message: string, senderSecret: string): Promise<string>
|
||||
decrypt(encryptedMessage: string, recipientSecret: string): Promise<string>
|
||||
secret(
|
||||
recipientOrSenderEpub: string,
|
||||
recipientOrSenderUserPair: UserPair
|
||||
): Promise<string>
|
||||
}
|
||||
}
|
||||
50
utils/GunSmith/Smith.ts
Normal file
50
utils/GunSmith/Smith.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
/// <reference path="GunT.ts" />
|
||||
namespace Smith {
|
||||
export interface SmithMsgOn {
|
||||
path: string
|
||||
type: 'on'
|
||||
}
|
||||
|
||||
export interface SmithMsgOnce {
|
||||
id: string
|
||||
path: string
|
||||
type: 'once'
|
||||
}
|
||||
|
||||
export interface SmithMsgPut {
|
||||
id: string
|
||||
path: string
|
||||
type: 'put'
|
||||
value: any
|
||||
}
|
||||
|
||||
export type SmithMsg = SmithMsgOn | SmithMsgOnce | SmithMsgPut | BatchSmithMsg
|
||||
|
||||
export type BatchSmithMsg = SmithMsg[]
|
||||
|
||||
export interface GunMsgOn {
|
||||
data: any
|
||||
key: string
|
||||
path: string
|
||||
type: 'on'
|
||||
}
|
||||
|
||||
export interface GunMsgOnce {
|
||||
data: any
|
||||
id: string
|
||||
key: string
|
||||
type: 'once'
|
||||
}
|
||||
|
||||
export interface GunMsgPut {
|
||||
ack: GunT.Ack
|
||||
id: string
|
||||
path: string
|
||||
type: 'put'
|
||||
}
|
||||
|
||||
export type GunMsg = GunMsgOn | GunMsgOnce | GunMsgPut
|
||||
}
|
||||
25
utils/GunSmith/config.js
Normal file
25
utils/GunSmith/config.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
/* eslint-disable no-process-env */
|
||||
|
||||
const dotenv = require('dotenv')
|
||||
const defaults = require('../../config/defaults')(false)
|
||||
|
||||
dotenv.config()
|
||||
|
||||
// @ts-ignore Let it crash if undefined
|
||||
exports.DATA_FILE_NAME = process.env.DATA_FILE_NAME || defaults.dataFileName
|
||||
|
||||
/**
|
||||
* @type {string[]}
|
||||
*/
|
||||
exports.PEERS = process.env.PEERS
|
||||
? JSON.parse(process.env.PEERS)
|
||||
: defaults.peers
|
||||
|
||||
exports.MS_TO_TOKEN_EXPIRATION = Number(
|
||||
process.env.MS_TO_TOKEN_EXPIRATION || defaults.tokenExpirationMS
|
||||
)
|
||||
|
||||
exports.SHOW_LOG = process.env.SHOW_GUN_DB_LOG === 'true'
|
||||
133
utils/GunSmith/gun.js
Normal file
133
utils/GunSmith/gun.js
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
// @ts-check
|
||||
/// <reference path="Smith.ts" />
|
||||
/// <reference path="GunT.ts" />
|
||||
const Gun = require('gun')
|
||||
// @ts-ignore
|
||||
require('gun/nts')
|
||||
|
||||
const Config = require('./config')
|
||||
|
||||
// @ts-ignore
|
||||
Gun.log = () => {}
|
||||
|
||||
/**
|
||||
* This var is just to please typescript's casting rules.
|
||||
*/
|
||||
const _gun = /** @type {any} */ (new Gun({
|
||||
axe: false,
|
||||
multicast: false,
|
||||
peers: Config.PEERS
|
||||
}))
|
||||
|
||||
/**
|
||||
* @type {GunT.GUNNode}
|
||||
*/
|
||||
const gun = _gun
|
||||
|
||||
const user = gun.user()
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const pendingOnces = new Set()
|
||||
|
||||
/**
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const waitForAuth = async () => {
|
||||
if (user.is?.pub) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
await new Promise(res => setTimeout(res, 1000))
|
||||
|
||||
return waitForAuth()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Smith.SmithMsg} msg
|
||||
*/
|
||||
const handleMsg = msg => {
|
||||
if (Array.isArray(msg)) {
|
||||
msg.forEach(handleMsg)
|
||||
return
|
||||
}
|
||||
if (msg.type === 'on') {
|
||||
const [root, ...keys] = msg.path.split('>')
|
||||
|
||||
/** @type {GunT.GUNNode} */
|
||||
let node =
|
||||
{
|
||||
$root: gun,
|
||||
$user: user
|
||||
}[root] || gun.user(root)
|
||||
|
||||
for (const key of keys) {
|
||||
node = node.get(key)
|
||||
}
|
||||
node.on((data, key) => {
|
||||
/** @type {Smith.GunMsgOn} */
|
||||
const res = {
|
||||
data,
|
||||
key,
|
||||
path: msg.path,
|
||||
type: 'on'
|
||||
}
|
||||
// @ts-expect-error
|
||||
process.send(res)
|
||||
})
|
||||
}
|
||||
if (msg.type === 'once') {
|
||||
const [root, ...keys] = msg.path.split('>')
|
||||
|
||||
/** @type {GunT.GUNNode} */
|
||||
let node =
|
||||
{
|
||||
$root: gun,
|
||||
$user: user
|
||||
}[root] || gun.user(root)
|
||||
|
||||
for (const key of keys) {
|
||||
node = node.get(key)
|
||||
}
|
||||
node.once((data, key) => {
|
||||
/** @type {Smith.GunMsgOnce} */
|
||||
const res = {
|
||||
data,
|
||||
id: msg.id,
|
||||
key,
|
||||
type: 'once'
|
||||
}
|
||||
// @ts-expect-error
|
||||
process.send(res)
|
||||
})
|
||||
}
|
||||
if (msg.type === 'put') {
|
||||
const [root, ...keys] = msg.path.split('>')
|
||||
|
||||
/** @type {GunT.GUNNode} */
|
||||
let node =
|
||||
{
|
||||
$root: gun,
|
||||
$user: user
|
||||
}[root] || gun.user(root)
|
||||
|
||||
for (const key of keys) {
|
||||
node = node.get(key)
|
||||
}
|
||||
node.on((data, key) => {
|
||||
/** @type {Smith.GunMsgOn} */
|
||||
const res = {
|
||||
data,
|
||||
key,
|
||||
path: msg.path,
|
||||
type: 'on'
|
||||
}
|
||||
// @ts-expect-error
|
||||
process.send(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
process.on('message', handleMsg)
|
||||
150
utils/GunSmith/gunSmith.js
Normal file
150
utils/GunSmith/gunSmith.js
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* @format
|
||||
*/
|
||||
// @ts-check
|
||||
/// <reference path="Smith.ts" />
|
||||
/// <reference path="GunT.ts" />
|
||||
const { fork } = require('child_process')
|
||||
|
||||
const Config = require('./config')
|
||||
|
||||
/**
|
||||
* Maps a path to `on()` listeners
|
||||
* @type {Record<string, WeakSet<GunT.Listener>>}
|
||||
*/
|
||||
const pathToListeners = {
|
||||
'x>x>x>': new WeakSet()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Smith.GunMsg} msg
|
||||
*/
|
||||
const handleMsg = msg => {}
|
||||
|
||||
let currentGun = fork('./gun')
|
||||
|
||||
const forge = () => {
|
||||
currentGun.kill()
|
||||
currentGun = fork('./gun')
|
||||
|
||||
currentGun.on('message', handleMsg)
|
||||
|
||||
const msgs = Object.keys(pathToListeners).map(path => {
|
||||
/** @type {Smith.SmithMsgOn} */
|
||||
const msg = {
|
||||
path,
|
||||
type: 'on'
|
||||
}
|
||||
return msg
|
||||
})
|
||||
|
||||
currentGun.send(msgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} path
|
||||
* @param {boolean=} afterMap
|
||||
* @returns {GunT.GUNNode}
|
||||
*/
|
||||
const createReplica = (path, afterMap = false) => {
|
||||
/** @type {GunT.Listener[]} */
|
||||
const listenersForThisRef = []
|
||||
|
||||
return {
|
||||
_: {
|
||||
get: '',
|
||||
opt: {
|
||||
peers: {}
|
||||
},
|
||||
put: {}
|
||||
},
|
||||
get(key) {
|
||||
return createReplica(path + '>' + key)
|
||||
},
|
||||
map() {
|
||||
return createReplica(path, true)
|
||||
},
|
||||
off() {
|
||||
for (const l of listenersForThisRef) {
|
||||
pathToListeners[path].delete(l)
|
||||
}
|
||||
},
|
||||
on(cb) {
|
||||
listenersForThisRef.push(cb)
|
||||
/** @type {Smith.SmithMsgOn} */
|
||||
const msg = {
|
||||
path,
|
||||
type: 'on'
|
||||
}
|
||||
currentGun.send(msg)
|
||||
return this
|
||||
},
|
||||
once(cb, opts) {
|
||||
return this
|
||||
},
|
||||
put(data, cb) {
|
||||
return this
|
||||
},
|
||||
set(data, cb) {
|
||||
return this
|
||||
},
|
||||
then() {
|
||||
return Promise.resolve(null)
|
||||
},
|
||||
user(pub) {
|
||||
if (path !== '$root') {
|
||||
throw new ReferenceError(
|
||||
`Do not call user() on a non-root GunSmith node`
|
||||
)
|
||||
}
|
||||
if (!pub) {
|
||||
throw new Error(
|
||||
'Do not use `user()` (without providing a pub) on a GunSmith node, use getUser()'
|
||||
)
|
||||
}
|
||||
const replica = createReplica(pub)
|
||||
return {
|
||||
...replica,
|
||||
_: {
|
||||
...replica._,
|
||||
sea: {
|
||||
epriv: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
||||
epub: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
||||
priv: 'Do not use ._.sea on a GunSmith node, use getUser()',
|
||||
pub: 'Do not use ._.sea on a GunSmith node, use getUser()'
|
||||
}
|
||||
},
|
||||
auth() {
|
||||
throw new Error(
|
||||
'Do not call auth() on a GunSmith node, use getUser()'
|
||||
)
|
||||
},
|
||||
create() {
|
||||
throw new Error(
|
||||
'Do not call create() on a GunSmith node, use getUser()'
|
||||
)
|
||||
},
|
||||
leave() {
|
||||
throw new Error(
|
||||
'Do not call leave() on a GunSmith node, use getUser()'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {GunT.GUNNode}
|
||||
*/
|
||||
const getGun = () => createReplica('$root')
|
||||
|
||||
/**
|
||||
* @returns {GunT.UserGUNNode}
|
||||
*/
|
||||
const getUser = createReplica('$user')
|
||||
|
||||
module.exports = {
|
||||
getGun,
|
||||
getUser
|
||||
}
|
||||
1
utils/GunSmith/index.js
Normal file
1
utils/GunSmith/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('./GunSmith')
|
||||
Loading…
Add table
Add a link
Reference in a new issue