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