Evan Feenstra
5 years ago
commit
f6abf808dc
163 changed files with 170504 additions and 0 deletions
@ -0,0 +1,6 @@ |
|||
{ |
|||
"presets": [ |
|||
"@babel/preset-env", |
|||
"@babel/preset-react" |
|||
] |
|||
} |
@ -0,0 +1,20 @@ |
|||
dist/config/app.json |
|||
dist/config/config.json |
|||
|
|||
node_modules/* |
|||
|
|||
# Elastic Beanstalk Files |
|||
.elasticbeanstalk/* |
|||
!.elasticbeanstalk/*.cfg.yml |
|||
!.elasticbeanstalk/*.global.yml |
|||
|
|||
.pgpass |
|||
.vscode |
|||
.DS_Store |
|||
|
|||
public/uploads/* |
|||
!public/uploads/.gitkeep |
|||
|
|||
sqlite/sphinx.db |
|||
sqlite/sphinxpg.sql |
|||
sqlite/sphinxlite.sql |
@ -0,0 +1,16 @@ |
|||
FROM node:8 |
|||
RUN apt-get update |
|||
RUN apt-get install -f sqlite3 |
|||
USER node |
|||
ENV NPM_CONFIG_PREFIX=/home/node/.npm-global |
|||
ENV PATH=$PATH:/home/node/.npm-global/bin |
|||
WORKDIR /home/node |
|||
COPY package.json . |
|||
RUN npm install |
|||
RUN npm install -g nodemon --save-dev |
|||
RUN npm install -g express --save-dev |
|||
RUN npm install -g webpack webpack-cli --save-dev |
|||
RUN npm install -g sqlite3 --build-from-source --save-dev |
|||
RUN npm install -g --save-dev sequelize |
|||
RUN npm rebuild |
|||
COPY . . |
@ -0,0 +1,21 @@ |
|||
MIT License |
|||
|
|||
Copyright (c) 2020 stakwork |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|||
of this software and associated documentation files (the "Software"), to deal |
|||
in the Software without restriction, including without limitation the rights |
|||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|||
copies of the Software, and to permit persons to whom the Software is |
|||
furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all |
|||
copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|||
SOFTWARE. |
@ -0,0 +1,42 @@ |
|||
# Relay |
|||
|
|||
**Relay** is a Node.js wrapper around [LND](https://github.com/lightningnetwork/lnd), handling connectivity and storage for [**Sphinx**](https://github.com/stakwork/sphinx). Communication between Relay nodes takes place entirely on the Lightning Network, so is decentralized, untraceable, and encrypted. Message content is also end-to-end encrypted using client public keys, on the **Sphinx** app itself. |
|||
|
|||
![Relay](https://github.com/stakwork/sphinx-node/raw/master/public/relay.jpg) |
|||
|
|||
Relay stores: |
|||
- Aliases |
|||
- Messages |
|||
- Recurring payment configurations |
|||
- Invites (so you can add your friends) |
|||
- Media Keys: keys for decrypting media files, asymetrically encrypted for each contact in a chat |
|||
|
|||
# run your own sphinx node |
|||
|
|||
You can run your own Sphinx node in order to have full ownership over your communication! |
|||
|
|||
### download |
|||
|
|||
`git clone https://github.com/stakwork/sphinx-node` |
|||
|
|||
`cd sphinx-node` |
|||
|
|||
`npm install` |
|||
|
|||
### dependencies |
|||
|
|||
sqlite3: `apt-get install sqlite3` |
|||
|
|||
### configure |
|||
|
|||
Edit the "production" section of config/app.json: |
|||
- Change `macaroon_location` to the location of your LND admin macaroon |
|||
- Change `tls_location` to the location of your LND cert |
|||
|
|||
### run |
|||
|
|||
`npm run prod` |
|||
|
|||
# Roadmap |
|||
|
|||
- linking recurring payments to files, to enable use cases such as subscribing to podcasts with BTC! |
@ -0,0 +1,318 @@ |
|||
import { models } from '../models' |
|||
import * as jsonUtils from '../utils/json' |
|||
import { success, failure } from '../utils/res' |
|||
import * as helpers from '../helpers' |
|||
import * as socket from '../utils/socket' |
|||
import { sendNotification } from '../hub' |
|||
import * as md5 from 'md5' |
|||
|
|||
const constants = require(__dirname + '/../../config/constants.json') |
|||
|
|||
async function getChats(req, res) { |
|||
const chats = await models.Chat.findAll({ where:{deleted:false}, raw: true }) |
|||
const c = chats.map(chat => jsonUtils.chatToJson(chat)); |
|||
success(res, c) |
|||
} |
|||
|
|||
async function mute(req, res) { |
|||
const chatId = req.params['chat_id'] |
|||
const mute = req.params['mute_unmute'] |
|||
|
|||
if (!["mute", "unmute"].includes(mute)) { |
|||
return failure(res, "invalid option for mute") |
|||
} |
|||
|
|||
const chat = await models.Chat.findOne({ where: { id: chatId } }) |
|||
|
|||
if (!chat) { |
|||
return failure(res, 'chat not found') |
|||
} |
|||
|
|||
chat.update({ isMuted: (mute == "mute") }) |
|||
|
|||
success(res, jsonUtils.chatToJson(chat)) |
|||
} |
|||
|
|||
async function createGroupChat(req, res) { |
|||
const { |
|||
name, |
|||
contact_ids, |
|||
} = req.body |
|||
|
|||
const members: { [k: string]: {[k:string]:string} } = {} //{pubkey:{key,alias}, ...}
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
|
|||
members[owner.publicKey] = { |
|||
key:owner.contactKey, alias:owner.alias |
|||
} |
|||
await asyncForEach(contact_ids, async cid => { |
|||
const contact = await models.Contact.findOne({ where: { id: cid } }) |
|||
members[contact.publicKey] = { |
|||
key: contact.contactKey, |
|||
alias: contact.alias||'' |
|||
} |
|||
}) |
|||
|
|||
const chatParams = createGroupChatParams(owner, contact_ids, members, name) |
|||
|
|||
helpers.sendMessage({ |
|||
chat: { ...chatParams, members }, |
|||
sender: owner, |
|||
type: constants.message_types.group_create, |
|||
message: {}, |
|||
failure: function (e) { |
|||
failure(res, e) |
|||
}, |
|||
success: async function () { |
|||
const chat = await models.Chat.create(chatParams) |
|||
success(res, jsonUtils.chatToJson(chat)) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
async function addGroupMembers(req, res) { |
|||
const { |
|||
contact_ids, |
|||
} = req.body |
|||
const { id } = req.params |
|||
|
|||
const members: { [k: string]: {[k:string]:string} } = {} //{pubkey:{key,alias}, ...}
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
let chat = await models.Chat.findOne({ where: { id } }) |
|||
|
|||
const contactIds = JSON.parse(chat.contactIds || '[]') |
|||
// for all members (existing and new)
|
|||
members[owner.publicKey] = {key:owner.contactKey, alias:owner.alias} |
|||
const allContactIds = contactIds.concat(contact_ids) |
|||
await asyncForEach(allContactIds, async cid => { |
|||
const contact = await models.Contact.findOne({ where: { id: cid } }) |
|||
if(contact) { |
|||
members[contact.publicKey] = { |
|||
key: contact.contactKey, |
|||
alias: contact.alias |
|||
} |
|||
} |
|||
}) |
|||
|
|||
success(res, jsonUtils.chatToJson(chat)) |
|||
|
|||
helpers.sendMessage({ // send ONLY to new members
|
|||
chat: { ...chat.dataValues, contactIds:contact_ids, members }, |
|||
sender: owner, |
|||
type: constants.message_types.group_invite, |
|||
message: {} |
|||
}) |
|||
} |
|||
|
|||
const deleteChat = async (req, res) => { |
|||
const { id } = req.params |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
const chat = await models.Chat.findOne({ where: { id } }) |
|||
helpers.sendMessage({ |
|||
chat, |
|||
sender: owner, |
|||
message: {}, |
|||
type: constants.message_types.group_leave, |
|||
}) |
|||
|
|||
await chat.update({ |
|||
deleted: true, |
|||
uuid:'', |
|||
contactIds:'[]', |
|||
name:'' |
|||
}) |
|||
await models.Message.destroy({ where: { chatId: id } }) |
|||
|
|||
success(res, { chat_id: id }) |
|||
} |
|||
|
|||
async function receiveGroupLeave(payload) { |
|||
console.log('=> receiveGroupLeave') |
|||
const { sender_pub_key, chat_uuid } = await helpers.parseReceiveParams(payload) |
|||
|
|||
const chat = await models.Chat.findOne({ where: { uuid: chat_uuid } }) |
|||
if (!chat) return |
|||
|
|||
const sender = await models.Contact.findOne({ where: { publicKey: sender_pub_key } }) |
|||
if (!sender) return |
|||
|
|||
const oldContactIds = JSON.parse(chat.contactIds || '[]') |
|||
const contactIds = oldContactIds.filter(cid => cid !== sender.id) |
|||
await chat.update({ contactIds: JSON.stringify(contactIds) }) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
const msg = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.group_leave, |
|||
sender: sender.id, |
|||
date: date, |
|||
messageContent: '', |
|||
remoteMessageContent: '', |
|||
status: constants.statuses.confirmed, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
} |
|||
const message = await models.Message.create(msg) |
|||
|
|||
socket.sendJson({ |
|||
type: 'group_leave', |
|||
response: { |
|||
contact: jsonUtils.contactToJson(sender), |
|||
chat: jsonUtils.chatToJson(chat), |
|||
message: jsonUtils.messageToJson(message, null) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
async function receiveGroupJoin(payload) { |
|||
console.log('=> receiveGroupJoin') |
|||
const { sender_pub_key, chat_uuid, chat_members } = await helpers.parseReceiveParams(payload) |
|||
|
|||
const chat = await models.Chat.findOne({ where: { uuid: chat_uuid } }) |
|||
if (!chat) return |
|||
|
|||
let theSender: any = null |
|||
const sender = await models.Contact.findOne({ where: { publicKey: sender_pub_key } }) |
|||
const contactIds = JSON.parse(chat.contactIds || '[]') |
|||
if (sender) { |
|||
theSender = sender // might already include??
|
|||
if(!contactIds.includes(sender.id)) contactIds.push(sender.id) |
|||
} else { |
|||
const member = chat_members[sender_pub_key] |
|||
if(member && member.key) { |
|||
const createdContact = await models.Contact.create({ |
|||
publicKey: sender_pub_key, |
|||
contactKey: member.key, |
|||
alias: member.alias||'Unknown', |
|||
status: 1 |
|||
}) |
|||
theSender = createdContact |
|||
contactIds.push(createdContact.id) |
|||
} |
|||
} |
|||
await chat.update({ contactIds: JSON.stringify(contactIds) }) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
const msg = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.group_join, |
|||
sender: sender.id, |
|||
date: date, |
|||
messageContent: '', |
|||
remoteMessageContent: '', |
|||
status: constants.statuses.confirmed, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
} |
|||
const message = await models.Message.create(msg) |
|||
|
|||
socket.sendJson({ |
|||
type: 'group_join', |
|||
response: { |
|||
contact: jsonUtils.contactToJson(theSender), |
|||
chat: jsonUtils.chatToJson(chat), |
|||
message: jsonUtils.messageToJson(message, null) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
async function receiveGroupCreateOrInvite(payload) { |
|||
const { chat_members, chat_name, chat_uuid } = await helpers.parseReceiveParams(payload) |
|||
|
|||
const contactIds: number[] = [] |
|||
const newContacts: any[] = [] |
|||
for (let [pubkey, member] of Object.entries(chat_members)) { |
|||
const contact = await models.Contact.findOne({ where: { publicKey: pubkey } }) |
|||
if (!contact && member && member.key) { |
|||
const createdContact = await models.Contact.create({ |
|||
publicKey: pubkey, |
|||
contactKey: member.key, |
|||
alias: member.alias||'Unknown', |
|||
status: 1 |
|||
}) |
|||
contactIds.push(createdContact.id) |
|||
newContacts.push(createdContact.dataValues) |
|||
} else { |
|||
contactIds.push(contact.id) |
|||
} |
|||
} |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
if(!contactIds.includes(owner.id)) contactIds.push(owner.id) |
|||
// make chat
|
|||
let date = new Date(); |
|||
date.setMilliseconds(0) |
|||
const chat = await models.Chat.create({ |
|||
uuid: chat_uuid, |
|||
contactIds: JSON.stringify(contactIds), |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
name: chat_name, |
|||
type: constants.chat_types.group |
|||
}) |
|||
|
|||
socket.sendJson({ |
|||
type: 'group_create', |
|||
response: jsonUtils.messageToJson({ newContacts }, chat) |
|||
}) |
|||
|
|||
sendNotification(chat, chat_name, 'group') |
|||
|
|||
if (payload.type === constants.message_types.group_invite) { |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
helpers.sendMessage({ |
|||
chat: { |
|||
...chat.dataValues, members: { |
|||
[owner.publicKey]: { |
|||
key: owner.contactKey, |
|||
alias: owner.alias||'' |
|||
} |
|||
} |
|||
}, |
|||
sender: owner, |
|||
message: {}, |
|||
type: constants.message_types.group_join, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
function createGroupChatParams(owner, contactIds, members, name) { |
|||
let date = new Date(); |
|||
date.setMilliseconds(0) |
|||
if (!(owner && members && contactIds && Array.isArray(contactIds))) { |
|||
return |
|||
} |
|||
|
|||
const pubkeys: string[] = [] |
|||
for (let pubkey of Object.keys(members)) { // just the key
|
|||
pubkeys.push(String(pubkey)) |
|||
} |
|||
if (!(pubkeys && pubkeys.length)) return |
|||
|
|||
const allkeys = pubkeys.includes(owner.publicKey) ? pubkeys : [owner.publicKey].concat(pubkeys) |
|||
const hash = md5(allkeys.sort().join("-")) |
|||
const theContactIds = contactIds.includes(owner.id) ? contactIds : [owner.id].concat(contactIds) |
|||
return { |
|||
uuid: `${new Date().valueOf()}-${hash}`, |
|||
contactIds: JSON.stringify(theContactIds), |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
name: name, |
|||
type: constants.chat_types.group |
|||
} |
|||
} |
|||
|
|||
export { |
|||
getChats, mute, addGroupMembers, |
|||
receiveGroupCreateOrInvite, createGroupChat, |
|||
deleteChat, receiveGroupLeave, receiveGroupJoin |
|||
} |
|||
|
|||
|
|||
async function asyncForEach(array, callback) { |
|||
for (let index = 0; index < array.length; index++) { |
|||
await callback(array[index], index, array); |
|||
} |
|||
} |
@ -0,0 +1,240 @@ |
|||
import {models} from '../models' |
|||
import * as crypto from 'crypto' |
|||
import * as socket from '../utils/socket' |
|||
import * as helpers from '../helpers' |
|||
import * as jsonUtils from '../utils/json' |
|||
import {success, failure} from '../utils/res' |
|||
|
|||
const constants = require(__dirname + '/../../config/constants.json') |
|||
|
|||
const getContacts = async (req, res) => { |
|||
const contacts = await models.Contact.findAll({ where:{deleted:false}, raw: true }) |
|||
const invites = await models.Invite.findAll({ raw: true }) |
|||
const chats = await models.Chat.findAll({ where:{deleted:false}, raw: true }) |
|||
const subscriptions = await models.Subscription.findAll({ raw: true }) |
|||
|
|||
const contactsResponse = contacts.map(contact => { |
|||
let contactJson = jsonUtils.contactToJson(contact) |
|||
let invite = invites.find(invite => invite.contactId == contact.id) |
|||
|
|||
if (invite) { |
|||
contactJson.invite = jsonUtils.inviteToJson(invite) |
|||
} |
|||
|
|||
return contactJson |
|||
}); |
|||
|
|||
const subsResponse = subscriptions.map(s=> jsonUtils.subscriptionToJson(s,null)) |
|||
const chatsResponse = chats.map(chat => jsonUtils.chatToJson(chat)) |
|||
|
|||
success(res, { |
|||
contacts: contactsResponse, |
|||
chats: chatsResponse, |
|||
subscriptions: subsResponse |
|||
}) |
|||
} |
|||
|
|||
const generateToken = async (req, res) => { |
|||
console.log('=> generateToken called', { body: req.body, params: req.params, query: req.query }) |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true, authToken: null }}) |
|||
|
|||
if (owner) { |
|||
const hash = crypto.createHash('sha256').update(req.body['token']).digest('base64'); |
|||
|
|||
console.log("req.params['token']", req.params['token']); |
|||
console.log("hash", hash); |
|||
|
|||
owner.update({ authToken: hash }) |
|||
|
|||
success(res,{}) |
|||
} else { |
|||
failure(res,{}) |
|||
} |
|||
} |
|||
|
|||
const updateContact = async (req, res) => { |
|||
console.log('=> updateContact called', { body: req.body, params: req.params, query: req.query }) |
|||
|
|||
let attrs = extractAttrs(req.body) |
|||
|
|||
const contact = await models.Contact.findOne({ where: { id: req.params.id }}) |
|||
let shouldUpdateContactKey = (contact.isOwner && contact.contactKey == null && attrs["contact_key"] != null) |
|||
|
|||
const owner = await contact.update(jsonUtils.jsonToContact(attrs)) |
|||
success(res, jsonUtils.contactToJson(owner)) |
|||
|
|||
if (!shouldUpdateContactKey) { |
|||
return |
|||
} |
|||
// definitely "owner" now
|
|||
|
|||
const contactIds = await models.Contact.findAll({where:{deleted:false}}).map(c => c.id) |
|||
if (contactIds.length == 0) { |
|||
return |
|||
} |
|||
|
|||
helpers.sendContactKeys({ |
|||
contactIds: contactIds, |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}) |
|||
} |
|||
|
|||
const exchangeKeys = async (req, res) => { |
|||
console.log('=> exchangeKeys called', { body: req.body, params: req.params, query: req.query }) |
|||
|
|||
const contact = await models.Contact.findOne({ where: { id: req.params.id }}) |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
|
|||
success(res, jsonUtils.contactToJson(contact)) |
|||
|
|||
helpers.sendContactKeys({ |
|||
contactIds: [contact.id], |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}) |
|||
} |
|||
|
|||
const createContact = async (req, res) => { |
|||
console.log('=> createContact called', { body: req.body, params: req.params, query: req.query }) |
|||
|
|||
let attrs = extractAttrs(req.body) |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
|
|||
const createdContact = await models.Contact.create(attrs) |
|||
const contact = await createdContact.update(jsonUtils.jsonToContact(attrs)) |
|||
|
|||
success(res, jsonUtils.contactToJson(contact)) |
|||
|
|||
helpers.sendContactKeys({ |
|||
contactIds: [contact.id], |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}) |
|||
} |
|||
|
|||
const deleteContact = async (req, res) => { |
|||
const id = parseInt(req.params.id||'0') |
|||
if(!id || id===1) { |
|||
failure(res, 'Cannot delete self') |
|||
return |
|||
} |
|||
|
|||
const contact = await models.Contact.findOne({ where: { id } }) |
|||
await contact.update({ |
|||
deleted:true, |
|||
publicKey:'', |
|||
photoUrl:'', |
|||
alias:'Unknown', |
|||
contactKey:'', |
|||
}) |
|||
|
|||
// find and destroy chat & messages
|
|||
const chats = await models.Chat.findAll({where:{deleted:false}}) |
|||
chats.map(async chat => { |
|||
if (chat.type === constants.chat_types.conversation) { |
|||
const contactIds = JSON.parse(chat.contactIds) |
|||
if (contactIds.includes(id)) { |
|||
await chat.update({ |
|||
deleted: true, |
|||
uuid:'', |
|||
contactIds:'[]', |
|||
name:'' |
|||
}) |
|||
await models.Message.destroy({ where: { chatId: chat.id } }) |
|||
} |
|||
} |
|||
}) |
|||
await models.Invite.destroy({ where: { contactId: id } }) |
|||
await models.Subscription.destroy({ where: { contactId: id } }) |
|||
|
|||
success(res, {}) |
|||
} |
|||
|
|||
const receiveConfirmContactKey = async (payload) => { |
|||
console.log('=> confirm contact key', { payload }) |
|||
|
|||
const dat = payload.content || payload |
|||
const sender_pub_key = dat.sender.pub_key |
|||
const sender_contact_key = dat.sender.contact_key |
|||
const sender_alias = dat.sender.alias || 'Unknown' |
|||
const sender_photo_url = dat.sender.photoUrl |
|||
|
|||
if(sender_photo_url){ |
|||
// download and store photo locally
|
|||
} |
|||
|
|||
const sender = await models.Contact.findOne({ where: { publicKey: sender_pub_key, status: constants.contact_statuses.confirmed }}) |
|||
if (sender_contact_key && sender) { |
|||
if(!sender.alias || sender.alias==='Unknown') { |
|||
sender.update({ contactKey: sender_contact_key, alias: sender_alias }) |
|||
} else { |
|||
sender.update({ contactKey: sender_contact_key }) |
|||
} |
|||
|
|||
socket.sendJson({ |
|||
type: 'contact', |
|||
response: jsonUtils.contactToJson(sender) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
const receiveContactKey = async (payload) => { |
|||
console.log('=> received contact key', JSON.stringify(payload)) |
|||
|
|||
const dat = payload.content || payload |
|||
const sender_pub_key = dat.sender.pub_key |
|||
const sender_contact_key = dat.sender.contact_key |
|||
const sender_alias = dat.sender.alias || 'Unknown' |
|||
const sender_photo_url = dat.sender.photoUrl |
|||
|
|||
if(sender_photo_url){ |
|||
// download and store photo locally
|
|||
} |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
const sender = await models.Contact.findOne({ where: { publicKey: sender_pub_key, status: constants.contact_statuses.confirmed }}) |
|||
|
|||
if (sender_contact_key && sender) { |
|||
if(!sender.alias || sender.alias==='Unknown') { |
|||
sender.update({ contactKey: sender_contact_key, alias: sender_alias }) |
|||
} else { |
|||
sender.update({ contactKey: sender_contact_key }) |
|||
} |
|||
|
|||
socket.sendJson({ |
|||
type: 'contact', |
|||
response: jsonUtils.contactToJson(sender) |
|||
}) |
|||
} |
|||
|
|||
helpers.sendContactKeys({ |
|||
contactPubKey: sender_pub_key, |
|||
sender: owner, |
|||
type: constants.message_types.contact_key_confirmation, |
|||
}) |
|||
} |
|||
|
|||
const extractAttrs = body => { |
|||
let fields_to_update = ["public_key", "node_alias", "alias", "photo_url", "device_id", "status", "contact_key"] |
|||
let attrs = {} |
|||
Object.keys(body).forEach(key => { |
|||
if (fields_to_update.includes(key)) { |
|||
attrs[key] = body[key] |
|||
} |
|||
}) |
|||
return attrs |
|||
} |
|||
|
|||
export { |
|||
generateToken, |
|||
exchangeKeys, |
|||
getContacts, |
|||
updateContact, |
|||
createContact, |
|||
deleteContact, |
|||
receiveContactKey, |
|||
receiveConfirmContactKey |
|||
} |
@ -0,0 +1,123 @@ |
|||
import {loadLightning} from '../utils/lightning' |
|||
import { success, failure } from '../utils/res' |
|||
import * as readLastLines from 'read-last-lines' |
|||
import { nodeinfo } from '../utils/nodeinfo'; |
|||
|
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/app.json')[env]; |
|||
|
|||
const defaultLogFiles = [ |
|||
'/home/lnd/.pm2/logs/app-error.log', |
|||
'/var/log/syslog', |
|||
] |
|||
async function getLogsSince(req, res) { |
|||
const logFiles = config.log_file ? [config.log_file] : defaultLogFiles |
|||
let txt |
|||
let err |
|||
await asyncForEach(logFiles, async filepath=>{ |
|||
if(!txt){ |
|||
try { |
|||
const lines = await readLastLines.read(filepath, 500) |
|||
if(lines) { |
|||
var linesArray = lines.split('\n') |
|||
linesArray.reverse() |
|||
txt = linesArray.join('\n') |
|||
} |
|||
} catch(e) { |
|||
err = e |
|||
} |
|||
} |
|||
}) |
|||
if(txt) success(res, txt) |
|||
else failure(res, err) |
|||
} |
|||
|
|||
const getInfo = async (req, res) => { |
|||
const lightning = loadLightning() |
|||
var request = {} |
|||
lightning.getInfo(request, function(err, response) { |
|||
res.status(200); |
|||
if (err == null) { |
|||
res.json({ success: true, response }); |
|||
} else { |
|||
res.json({ success: false }); |
|||
} |
|||
res.end(); |
|||
}); |
|||
}; |
|||
|
|||
const getChannels = async (req, res) => { |
|||
const lightning = loadLightning() |
|||
var request = {} |
|||
lightning.listChannels(request, function(err, response) { |
|||
res.status(200); |
|||
if (err == null) { |
|||
res.json({ success: true, response }); |
|||
} else { |
|||
res.json({ success: false }); |
|||
} |
|||
res.end(); |
|||
}); |
|||
}; |
|||
|
|||
const getBalance = (req, res) => { |
|||
const lightning = loadLightning() |
|||
var request = {} |
|||
lightning.channelBalance(request, function(err, response) { |
|||
res.status(200); |
|||
if (err == null) { |
|||
res.json({ success: true, response }); |
|||
} else { |
|||
res.json({ success: false }); |
|||
} |
|||
res.end(); |
|||
}); |
|||
}; |
|||
|
|||
const getLocalRemoteBalance = async (req, res) => { |
|||
const lightning = loadLightning() |
|||
lightning.listChannels({}, (err, channelList) => { |
|||
const { channels } = channelList |
|||
|
|||
const localBalances = channels.map(c => c.local_balance) |
|||
const remoteBalances = channels.map(c => c.remote_balance) |
|||
const totalLocalBalance = localBalances.reduce((a, b) => parseInt(a) + parseInt(b), 0) |
|||
const totalRemoteBalance = remoteBalances.reduce((a, b) => parseInt(a) + parseInt(b), 0) |
|||
|
|||
res.status(200); |
|||
if (err == null) { |
|||
res.json({ success: true, response: { local_balance: totalLocalBalance, remote_balance: totalRemoteBalance } }); |
|||
} else { |
|||
res.json({ success: false }); |
|||
} |
|||
res.end(); |
|||
}) |
|||
}; |
|||
|
|||
const getNodeInfo = async (req, res) => { |
|||
var ipOfSource = req.connection.remoteAddress; |
|||
if(!(ipOfSource.includes('127.0.0.1') || ipOfSource.includes('localhost'))){ |
|||
res.status(401) |
|||
res.end() |
|||
return |
|||
} |
|||
const node = await nodeinfo() |
|||
res.status(200) |
|||
res.json(node) |
|||
res.end() |
|||
} |
|||
|
|||
export { |
|||
getInfo, |
|||
getBalance, |
|||
getChannels, |
|||
getLocalRemoteBalance, |
|||
getLogsSince, |
|||
getNodeInfo, |
|||
} |
|||
|
|||
async function asyncForEach(array, callback) { |
|||
for (let index = 0; index < array.length; index++) { |
|||
await callback(array[index], index, array); |
|||
} |
|||
} |
@ -0,0 +1,139 @@ |
|||
import {models} from '../models' |
|||
import * as lndService from '../grpc' |
|||
import {checkTag} from '../utils/gitinfo' |
|||
import {checkConnection} from '../utils/lightning' |
|||
|
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
|
|||
const env = process.env.NODE_ENV || 'development'; |
|||
console.log("=> env:",env) |
|||
|
|||
let controllers = { |
|||
messages: require('./messages'), |
|||
invoices: require('./invoices'), |
|||
uploads: require('./uploads'), |
|||
contacts: require('./contacts'), |
|||
invites: require('./invites'), |
|||
payments: require('./payment'), |
|||
details: require('./details'), |
|||
chats: require('./chats'), |
|||
subcriptions: require('./subscriptions'), |
|||
media: require('./media'), |
|||
} |
|||
|
|||
async function iniGrpcSubscriptions() { |
|||
try{ |
|||
await checkConnection() |
|||
const types = constants.message_types |
|||
await lndService.subscribeInvoices({ |
|||
[types.contact_key]: controllers.contacts.receiveContactKey, |
|||
[types.contact_key_confirmation]: controllers.contacts.receiveConfirmContactKey, |
|||
[types.message]: controllers.messages.receiveMessage, |
|||
[types.invoice]: controllers.invoices.receiveInvoice, |
|||
[types.direct_payment]: controllers.payments.receivePayment, |
|||
[types.confirmation]: controllers.messages.receiveConfirmation, |
|||
[types.attachment]: controllers.media.receiveAttachment, |
|||
[types.purchase]: controllers.media.receivePurchase, |
|||
[types.purchase_accept]: controllers.media.receivePurchaseAccept, |
|||
[types.purchase_deny]: controllers.media.receivePurchaseDeny, |
|||
[types.group_create]: controllers.chats.receiveGroupCreateOrInvite, |
|||
[types.group_invite]: controllers.chats.receiveGroupCreateOrInvite, |
|||
[types.group_join]: controllers.chats.receiveGroupJoin, |
|||
[types.group_leave]: controllers.chats.receiveGroupLeave, |
|||
}) |
|||
} catch(e) { |
|||
throw e |
|||
} |
|||
} |
|||
|
|||
async function set(app) { |
|||
|
|||
if(models && models.Subscription){ |
|||
controllers.subcriptions.initializeCronJobs() |
|||
} |
|||
try{ |
|||
await controllers.media.cycleMediaToken() |
|||
} catch(e) { |
|||
console.log('=> could not auth with media server', e.message) |
|||
} |
|||
|
|||
app.get('/chats', controllers.chats.getChats) |
|||
app.post('/group', controllers.chats.createGroupChat) |
|||
app.post('/chats/:chat_id/:mute_unmute', controllers.chats.mute) |
|||
app.delete('/chat/:id', controllers.chats.deleteChat) |
|||
app.put('/chat/:id', controllers.chats.addGroupMembers) |
|||
|
|||
app.post('/contacts/tokens', controllers.contacts.generateToken) |
|||
|
|||
app.post('/upload', controllers.uploads.avatarUpload.single('file'), controllers.uploads.uploadFile) |
|||
|
|||
app.post('/invites', controllers.invites.createInvite) |
|||
app.post('/invites/:invite_string/pay', controllers.invites.payInvite) |
|||
app.post('/invites/finish', controllers.invites.finishInvite) |
|||
|
|||
app.get('/contacts', controllers.contacts.getContacts) |
|||
app.put('/contacts/:id', controllers.contacts.updateContact) |
|||
app.post('/contacts/:id/keys', controllers.contacts.exchangeKeys) |
|||
app.post('/contacts', controllers.contacts.createContact) |
|||
app.delete('/contacts/:id', controllers.contacts.deleteContact) |
|||
|
|||
app.get('/messages', controllers.messages.getMessages) |
|||
app.post('/messages', controllers.messages.sendMessage) |
|||
app.post('/messages/:chat_id/read', controllers.messages.readMessages) |
|||
app.post('/messages/clear', controllers.messages.clearMessages) |
|||
|
|||
app.get('/subscriptions', controllers.subcriptions.getAllSubscriptions) |
|||
app.get('/subscription/:id', controllers.subcriptions.getSubscription) |
|||
app.delete('/subscription/:id', controllers.subcriptions.deleteSubscription) |
|||
app.post('/subscriptions', controllers.subcriptions.createSubscription) |
|||
app.put('/subscription/:id', controllers.subcriptions.editSubscription) |
|||
app.get('/subscriptions/contact/:contactId', controllers.subcriptions.getSubscriptionsForContact) |
|||
app.put('/subscription/:id/pause', controllers.subcriptions.pauseSubscription) |
|||
app.put('/subscription/:id/restart', controllers.subcriptions.restartSubscription) |
|||
|
|||
app.post('/attachment', controllers.media.sendAttachmentMessage) |
|||
app.post('/purchase', controllers.media.purchase) |
|||
app.get('/signer/:challenge', controllers.media.signer) |
|||
|
|||
app.post('/invoices', controllers.invoices.createInvoice) |
|||
app.get('/invoices', controllers.invoices.listInvoices) |
|||
app.put('/invoices', controllers.invoices.payInvoice) |
|||
app.post('/invoices/cancel', controllers.invoices.cancelInvoice) |
|||
|
|||
app.post('/payment', controllers.payments.sendPayment) |
|||
app.get('/payments', controllers.payments.listPayments) |
|||
|
|||
app.get('/channels', controllers.details.getChannels) |
|||
app.get('/balance', controllers.details.getBalance) |
|||
app.get('/balance/all', controllers.details.getLocalRemoteBalance) |
|||
app.get('/getinfo', controllers.details.getInfo) |
|||
app.get('/logs', controllers.details.getLogsSince) |
|||
app.get('/info', controllers.details.getNodeInfo) |
|||
|
|||
app.get('/version', async function(req,res) { |
|||
const version = await checkTag() |
|||
res.send({version}) |
|||
}) |
|||
|
|||
if (env != "production") { // web dashboard login
|
|||
app.post('/login', login) |
|||
} |
|||
} |
|||
|
|||
const login = (req, res) => { |
|||
const { code } = req.body; |
|||
|
|||
if (code == "sphinx") { |
|||
models.Contact.findOne({ where: { isOwner: true } }).then(owner => { |
|||
res.status(200); |
|||
res.json({ success: true, token: owner.authToken }); |
|||
res.end(); |
|||
}) |
|||
} else { |
|||
res.status(200); |
|||
res.json({ success: false }); |
|||
res.end(); |
|||
} |
|||
} |
|||
|
|||
export {set, iniGrpcSubscriptions} |
@ -0,0 +1,118 @@ |
|||
import {models} from '../models' |
|||
import * as crypto from 'crypto' |
|||
import * as jsonUtils from '../utils/json' |
|||
import {finishInviteInHub, createInviteInHub, payInviteInHub} from '../hub' |
|||
|
|||
const finishInvite = async (req, res) => { |
|||
const { |
|||
invite_string |
|||
} = req.body |
|||
const params = { |
|||
invite: { |
|||
pin: invite_string |
|||
} |
|||
} |
|||
|
|||
function onSuccess() { |
|||
res.status(200) |
|||
res.json({ success: true }) |
|||
res.end() |
|||
} |
|||
function onFailure() { |
|||
res.status(200) |
|||
res.json({ success: false }) |
|||
res.end() |
|||
} |
|||
|
|||
finishInviteInHub(params, onSuccess, onFailure) |
|||
} |
|||
|
|||
const payInvite = async (req, res) => { |
|||
const params = { |
|||
node_ip: process.env.NODE_IP |
|||
} |
|||
|
|||
const invite_string = req.params['invite_string'] |
|||
|
|||
const onSuccess = async (response) => { |
|||
const invite = response.object |
|||
|
|||
console.log("response", invite) |
|||
|
|||
const dbInvite = await models.Invite.findOne({ where: { inviteString: invite.pin }}) |
|||
|
|||
if (dbInvite.status != invite.invite_status) { |
|||
dbInvite.update({ status: invite.invite_status }) |
|||
} |
|||
|
|||
res.status(200) |
|||
res.json({ success: true, response: { invite: jsonUtils.inviteToJson(dbInvite) } }) |
|||
res.end() |
|||
} |
|||
|
|||
const onFailure = (response) => { |
|||
res.status(200) |
|||
res.json({ success: false }) |
|||
res.end() |
|||
} |
|||
|
|||
payInviteInHub(invite_string, params, onSuccess, onFailure) |
|||
} |
|||
|
|||
const createInvite = async (req, res) => { |
|||
const { |
|||
nickname, |
|||
welcome_message |
|||
} = req.body |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
|
|||
const params = { |
|||
invite: { |
|||
nickname: owner.alias, |
|||
pubkey: owner.publicKey, |
|||
contact_nickname: nickname, |
|||
message: welcome_message, |
|||
pin: crypto.randomBytes(20).toString('hex') |
|||
} |
|||
} |
|||
|
|||
const onSuccess = async (response) => { |
|||
console.log("response", response) |
|||
|
|||
const inviteCreated = response.object |
|||
|
|||
const contact = await models.Contact.create({ |
|||
alias: nickname, |
|||
status: 0 |
|||
}) |
|||
const invite = await models.Invite.create({ |
|||
welcomeMessage: inviteCreated.message, |
|||
contactId: contact.id, |
|||
status: inviteCreated.invite_status, |
|||
inviteString: inviteCreated.pin |
|||
}) |
|||
let contactJson = jsonUtils.contactToJson(contact) |
|||
if (invite) { |
|||
contactJson.invite = jsonUtils.inviteToJson(invite) |
|||
} |
|||
|
|||
res.status(200) |
|||
res.json({ success: true, contact: contactJson }) |
|||
res.end() |
|||
} |
|||
|
|||
const onFailure = (response) => { |
|||
res.status(200) |
|||
res.json(response) |
|||
res.end() |
|||
} |
|||
|
|||
createInviteInHub(params, onSuccess, onFailure) |
|||
} |
|||
|
|||
export { |
|||
createInvite, |
|||
finishInvite, |
|||
payInvite |
|||
} |
@ -0,0 +1,273 @@ |
|||
import { models } from '../models' |
|||
import { loadLightning } from '../utils/lightning' |
|||
import * as socket from '../utils/socket' |
|||
import * as jsonUtils from '../utils/json' |
|||
import * as decodeUtils from '../utils/decode' |
|||
import * as helpers from '../helpers' |
|||
import { sendNotification } from '../hub' |
|||
import { success } from '../utils/res' |
|||
|
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
|
|||
const payInvoice = async (req, res) => { |
|||
const lightning = await loadLightning() |
|||
const { payment_request } = req.body; |
|||
|
|||
var call = lightning.sendPayment({}) |
|||
|
|||
call.on('data', async response => { |
|||
console.log('[pay invoice data]', response) |
|||
|
|||
const message = await models.Message.findOne({ where: { payment_request } }) |
|||
if (!message) { // invoice still paid
|
|||
return success(res, { |
|||
success: true, |
|||
response: { payment_request } |
|||
}) |
|||
} |
|||
|
|||
message.status = constants.statuses.confirmed; |
|||
message.save(); |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const chat = await models.Chat.findOne({ where: { id: message.chatId } }) |
|||
const contactIds = JSON.parse(chat.contactIds) |
|||
const senderId = contactIds.find(id => id != message.sender) |
|||
|
|||
const paidMessage = await models.Message.create({ |
|||
chatId: message.chatId, |
|||
sender: senderId, |
|||
type: constants.message_types.payment, |
|||
amount: message.amount, |
|||
amountMsat: message.amountMsat, |
|||
paymentHash: message.paymentHash, |
|||
date: date, |
|||
expirationDate: null, |
|||
messageContent: null, |
|||
status: constants.statuses.confirmed, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}) |
|||
console.log('[pay invoice] stored message', paidMessage) |
|||
success(res, jsonUtils.messageToJson(paidMessage, chat)) |
|||
}) |
|||
|
|||
call.write({ payment_request }) |
|||
}; |
|||
|
|||
|
|||
const cancelInvoice = (req, res) => { |
|||
res.status(200); |
|||
res.json({ success: false }); |
|||
res.end(); |
|||
}; |
|||
|
|||
const createInvoice = async (req, res) => { |
|||
const lightning = await loadLightning() |
|||
|
|||
const { |
|||
amount, |
|||
memo, |
|||
remote_memo, |
|||
chat_id, |
|||
contact_id |
|||
} = req.body; |
|||
|
|||
var request = { |
|||
value: amount, |
|||
memo: remote_memo || memo |
|||
} |
|||
|
|||
if (amount == null) { |
|||
res.status(200); |
|||
res.json({ err: "no amount specified", }); |
|||
res.end(); |
|||
} else { |
|||
lightning.addInvoice(request, function (err, response) { |
|||
console.log({ err, response }) |
|||
|
|||
if (err == null) { |
|||
const { payment_request } = response |
|||
|
|||
if (!contact_id && !chat_id) { // if no contact
|
|||
success(res, { |
|||
invoice: payment_request |
|||
}) |
|||
return // end here
|
|||
} |
|||
|
|||
lightning.decodePayReq({ pay_req: payment_request }, async (error, invoice) => { |
|||
if (res) { |
|||
console.log('decoded pay req', { invoice }) |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
|
|||
const chat = await helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id |
|||
}) |
|||
|
|||
let timestamp = parseInt(invoice.timestamp + '000') |
|||
let expiry = parseInt(invoice.expiry + '000') |
|||
|
|||
if (error) { |
|||
res.status(200) |
|||
res.json({ success: false, error }) |
|||
res.end() |
|||
} else { |
|||
const message = await models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.invoice, |
|||
amount: parseInt(invoice.num_satoshis), |
|||
amountMsat: parseInt(invoice.num_satoshis) * 1000, |
|||
paymentHash: invoice.payment_hash, |
|||
paymentRequest: payment_request, |
|||
date: new Date(timestamp), |
|||
expirationDate: new Date(timestamp + expiry), |
|||
messageContent: memo, |
|||
remoteMessageContent: remote_memo, |
|||
status: constants.statuses.pending, |
|||
createdAt: new Date(timestamp), |
|||
updatedAt: new Date(timestamp) |
|||
}) |
|||
success(res, jsonUtils.messageToJson(message, chat)) |
|||
|
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.invoice, |
|||
message: { |
|||
id: message.id, |
|||
invoice: message.paymentRequest |
|||
} |
|||
}) |
|||
} |
|||
} else { |
|||
console.log('error decoding pay req', { err, res }) |
|||
res.status(500); |
|||
res.json({ err, res }) |
|||
res.end(); |
|||
} |
|||
}) |
|||
} else { |
|||
console.log({ err, response }) |
|||
} |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
const listInvoices = async (req, res) => { |
|||
const lightning = await loadLightning() |
|||
|
|||
lightning.listInvoices({}, (err, response) => { |
|||
console.log({ err, response }) |
|||
if (err == null) { |
|||
res.status(200); |
|||
res.json(response); |
|||
res.end(); |
|||
} else { |
|||
console.log({ err, response }) |
|||
} |
|||
}); |
|||
}; |
|||
|
|||
const receiveInvoice = async (payload) => { |
|||
console.log('received invoice', payload) |
|||
|
|||
const total_spent = 1 |
|||
const dat = payload.content || payload |
|||
const payment_request = dat.message.invoice |
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const { owner, sender, chat, msg_id } = await helpers.parseReceiveParams(payload) |
|||
if (!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!') |
|||
} |
|||
|
|||
const { memo, sat, msat, paymentHash, invoiceDate, expirationSeconds } = decodePaymentRequest(payment_request) |
|||
|
|||
const message = await models.Message.create({ |
|||
chatId: chat.id, |
|||
type: constants.message_types.invoice, |
|||
sender: sender.id, |
|||
amount: sat, |
|||
amountMsat: msat, |
|||
paymentRequest: payment_request, |
|||
asciiEncodedTotal: total_spent, |
|||
paymentHash: paymentHash, |
|||
messageContent: memo, |
|||
expirationDate: new Date(invoiceDate + expirationSeconds), |
|||
date: new Date(invoiceDate), |
|||
status: constants.statuses.pending, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}) |
|||
console.log('received keysend invoice message', message.id) |
|||
|
|||
socket.sendJson({ |
|||
type: 'invoice', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
|
|||
sendNotification(chat, sender.alias, 'message') |
|||
|
|||
sendConfirmation({ chat, sender: owner, msg_id }) |
|||
} |
|||
|
|||
const sendConfirmation = ({ chat, sender, msg_id }) => { |
|||
helpers.sendMessage({ |
|||
chat, |
|||
sender, |
|||
message: { id: msg_id }, |
|||
type: constants.message_types.confirmation, |
|||
}) |
|||
} |
|||
|
|||
|
|||
export { |
|||
listInvoices, |
|||
payInvoice, |
|||
cancelInvoice, |
|||
createInvoice, |
|||
receiveInvoice |
|||
} |
|||
|
|||
// lnd invoice stuff
|
|||
|
|||
function decodePaymentRequest(paymentRequest) { |
|||
var decodedPaymentRequest: any = decodeUtils.decode(paymentRequest) |
|||
var expirationSeconds = 3600 |
|||
var paymentHash = "" |
|||
var memo = "" |
|||
|
|||
for (var i = 0; i < decodedPaymentRequest.data.tags.length; i++) { |
|||
let tag = decodedPaymentRequest.data.tags[i]; |
|||
if(tag) { |
|||
if (tag.description == 'payment_hash') { |
|||
paymentHash = tag.value; |
|||
} else if (tag.description == 'description') { |
|||
memo = tag.value; |
|||
} else if (tag.description == 'expiry') { |
|||
expirationSeconds = tag.value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
expirationSeconds = parseInt(expirationSeconds.toString() + '000'); |
|||
let invoiceDate = parseInt(decodedPaymentRequest.data.time_stamp.toString() + '000'); |
|||
|
|||
let amount = decodedPaymentRequest['human_readable_part']['amount']; |
|||
var msat = 0; |
|||
var sat = 0; |
|||
if (Number.isInteger(amount)) { |
|||
msat = amount; |
|||
sat = amount / 1000; |
|||
} |
|||
|
|||
return { sat, msat, paymentHash, invoiceDate, expirationSeconds, memo } |
|||
} |
@ -0,0 +1,514 @@ |
|||
import {models} from '../models' |
|||
import * as socket from '../utils/socket' |
|||
import * as jsonUtils from '../utils/json' |
|||
import * as resUtils from '../utils/res' |
|||
import * as helpers from '../helpers' |
|||
import { sendNotification } from '../hub' |
|||
import { signBuffer, verifyMessage } from '../utils/lightning' |
|||
import * as rp from 'request-promise' |
|||
import { loadLightning } from '../utils/lightning' |
|||
import {parseLDAT, tokenFromTerms, urlBase64FromBytes, testLDAT} from '../utils/ldat' |
|||
import {CronJob} from 'cron' |
|||
import * as zbase32 from '../utils/zbase32' |
|||
import * as schemas from './schemas' |
|||
|
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/app.json')[env]; |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
|
|||
/* |
|||
|
|||
TODO line 233: parse that from token itself, dont use getMediaInfo at all |
|||
|
|||
"attachment": sends a message to a chat with a signed receipt for a file, which can be accessed from sphinx-meme server |
|||
If the attachment has a price, then the media must be purchased to get the receipt |
|||
|
|||
"purchase" sends sats. |
|||
if the amount matches the price, the media owner |
|||
will respond ("purchase_accept" or "purchase_deny" type) |
|||
with the signed token, which can only be used by the buyer |
|||
|
|||
purchase_accept should update the original attachment message with the terms and receipt |
|||
(both Relay and client need to do this) or make new??? |
|||
|
|||
purchase_deny returns the sats |
|||
*/ |
|||
|
|||
const sendAttachmentMessage = async (req, res) => { |
|||
// try {
|
|||
// schemas.attachment.validateSync(req.body)
|
|||
// } catch(e) {
|
|||
// return resUtils.failure(res, e.message)
|
|||
// }
|
|||
|
|||
const { |
|||
chat_id, |
|||
contact_id, |
|||
muid, |
|||
text, |
|||
remote_text, |
|||
remote_text_map, |
|||
media_key_map, |
|||
media_type, |
|||
file_name, |
|||
ttl, |
|||
price, // IF AMOUNT>0 THEN do NOT sign or send receipt
|
|||
} = req.body |
|||
|
|||
console.log('[send attachment]', req.body) |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
const chat = await helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id |
|||
}) |
|||
|
|||
let TTL = ttl |
|||
if(ttl) { |
|||
TTL = parseInt(ttl) |
|||
} |
|||
if(!TTL) TTL = 31536000 // default year
|
|||
|
|||
const amt = price||0 |
|||
// generate media token for self!
|
|||
const myMediaToken = await tokenFromTerms({ |
|||
muid, ttl:TTL, host:'', |
|||
pubkey: owner.publicKey, |
|||
meta:{...amt && {amt}, ttl} |
|||
}) |
|||
|
|||
const date = new Date(); |
|||
date.setMilliseconds(0) |
|||
const myMediaKey = (media_key_map && media_key_map[owner.id]) || '' |
|||
const mediaType = media_type || '' |
|||
const remoteMessageContent = remote_text_map?JSON.stringify(remote_text_map) : remote_text |
|||
|
|||
const message = await models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.attachment, |
|||
status: constants.statuses.pending, |
|||
messageContent: text||file_name||'', |
|||
remoteMessageContent, |
|||
mediaToken: myMediaToken, |
|||
mediaKey: myMediaKey, |
|||
mediaType: mediaType, |
|||
date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}) |
|||
|
|||
saveMediaKeys(muid, media_key_map, chat.id, message.id) |
|||
|
|||
const mediaTerms: {[k:string]:any} = { |
|||
muid, ttl:TTL, |
|||
meta:{...amt && {amt}}, |
|||
skipSigning: amt ? true : false // only sign if its free
|
|||
} |
|||
const msg: {[k:string]:any} = { |
|||
mediaTerms, // this gets converted to mediaToken
|
|||
id: message.id, |
|||
content: remote_text_map||remote_text||text||file_name||'', |
|||
mediaKey: media_key_map, |
|||
mediaType: mediaType, |
|||
} |
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.attachment, |
|||
message: msg, |
|||
success: async (data) => { |
|||
console.log('attachment sent', { data }) |
|||
resUtils.success(res, jsonUtils.messageToJson(message, chat)) |
|||
}, |
|||
failure: error=> resUtils.failure(res, error.message), |
|||
}) |
|||
} |
|||
|
|||
function saveMediaKeys(muid, mediaKeyMap, chatId, messageId){ |
|||
if (typeof mediaKeyMap!=='object'){ |
|||
console.log('wrong type for mediaKeyMap') |
|||
return |
|||
} |
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
for (let [contactId, key] of Object.entries(mediaKeyMap)) { |
|||
models.MediaKey.create({ |
|||
muid, chatId, contactId, key, messageId, |
|||
createdAt: date, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
const purchase = async (req, res) => { |
|||
const { |
|||
chat_id, |
|||
contact_id, |
|||
amount, |
|||
mediaToken, |
|||
} = req.body |
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
try { |
|||
schemas.purchase.validateSync(req.body) |
|||
} catch(e) { |
|||
return resUtils.failure(res, e.message) |
|||
} |
|||
|
|||
console.log('purchase!') |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
const chat = await helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id |
|||
}) |
|||
|
|||
const message = await models.Message.create({ |
|||
sender: owner.id, |
|||
type: constants.message_types.purchase, |
|||
mediaToken: mediaToken, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}) |
|||
|
|||
const msg={ |
|||
amount, mediaToken, id:message.id, |
|||
} |
|||
helpers.sendMessage({ |
|||
chat: {...chat, contactIds:[contact_id]}, |
|||
sender: owner, |
|||
type: constants.message_types.purchase, |
|||
message: msg, |
|||
success: async (data) => { |
|||
console.log('purchase sent', { data }) |
|||
resUtils.success(res, jsonUtils.messageToJson(message)) |
|||
}, |
|||
failure: error=> resUtils.failure(res, error.message), |
|||
}) |
|||
} |
|||
|
|||
/* RECEIVERS */ |
|||
|
|||
const receivePurchase = async (payload) => { |
|||
console.log('received purchase', { payload }) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const {owner, sender, chat, amount, mediaToken} = await helpers.parseReceiveParams(payload) |
|||
if(!owner || !sender || !chat) { |
|||
return console.log('=> group chat not found!') |
|||
} |
|||
|
|||
await models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: sender.id, |
|||
type: constants.message_types.purchase, |
|||
mediaToken: mediaToken, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}) |
|||
|
|||
const muid = mediaToken && mediaToken.split('.').length && mediaToken.split('.')[1] |
|||
if(!muid){ |
|||
return console.log('no muid') |
|||
} |
|||
|
|||
const ogMessage = models.Message.findOne({ |
|||
where:{mediaToken} |
|||
}) |
|||
if (!ogMessage){ |
|||
return console.log('no original message') |
|||
} |
|||
// find mediaKey for who sent
|
|||
const mediaKey = models.MediaKey.findOne({where:{ |
|||
muid, receiver: sender.id, |
|||
}}) |
|||
|
|||
const terms = parseLDAT(mediaToken) |
|||
// get info
|
|||
let TTL = terms.meta && terms.meta.ttl |
|||
let price = terms.meta && terms.meta.amt |
|||
if(!TTL || !price){ |
|||
const media = await getMediaInfo(muid) |
|||
console.log("GOT MEDIA", media) |
|||
if(media) { |
|||
TTL = media.ttl && parseInt(media.ttl) |
|||
price = media.price |
|||
} |
|||
if(!TTL) TTL = 31536000 |
|||
if(!price) price = 0 |
|||
} |
|||
|
|||
if (amount < price) { // didnt pay enough
|
|||
return helpers.sendMessage({ // "purchase_deny"
|
|||
chat: {...chat, contactIds:[sender.id]}, // only send back to sender
|
|||
sender: owner, |
|||
amount: amount, |
|||
type: constants.message_types.purchase_deny, |
|||
message: {amount,content:'Payment Denied'}, |
|||
success: async (data) => { |
|||
console.log('purchase_deny sent', { data }) |
|||
}, |
|||
failure: error=> console.log('=> couldnt send purcahse deny', error), |
|||
}) |
|||
} |
|||
|
|||
const acceptTerms = { |
|||
muid, ttl: TTL, |
|||
meta: {amt:amount}, |
|||
} |
|||
helpers.sendMessage({ |
|||
chat: {...chat, contactIds:[sender.id]}, // only to sender
|
|||
sender: owner, |
|||
type: constants.message_types.purchase_accept, |
|||
message: { |
|||
mediaTerms: acceptTerms, // converted to token in utils/msg.ts
|
|||
mediaKey: mediaKey.key, |
|||
mediaType: ogMessage.mediaType, |
|||
}, |
|||
success: async (data) => { |
|||
console.log('purchase_accept sent', { data }) |
|||
}, |
|||
failure: error=> console.log('=> couldnt send purchase accept', error), |
|||
}) |
|||
} |
|||
|
|||
const receivePurchaseAccept = async (payload) => { |
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const {owner, sender, chat, mediaToken, mediaKey, mediaType} = await helpers.parseReceiveParams(payload) |
|||
if(!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!') |
|||
} |
|||
|
|||
const termsArray = mediaToken.split('.') |
|||
// const host = termsArray[0]
|
|||
const muid = termsArray[1] |
|||
if(!muid){ |
|||
return console.log('wtf no muid') |
|||
} |
|||
// const attachmentMessage = await models.Message.findOne({where:{
|
|||
// mediaToken: {$like: `${host}.${muid}%`}
|
|||
// }})
|
|||
// if(attachmentMessage){
|
|||
// console.log('=> updated msg!')
|
|||
// attachmentMessage.update({
|
|||
// mediaToken, mediaKey
|
|||
// })
|
|||
// }
|
|||
|
|||
const msg = await models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: sender.id, |
|||
type: constants.message_types.purchase_accept, |
|||
status: constants.statuses.received, |
|||
mediaToken, |
|||
mediaKey, |
|||
mediaType, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}) |
|||
socket.sendJson({ |
|||
type: 'purchase_accept', |
|||
response: jsonUtils.messageToJson(msg, chat) |
|||
}) |
|||
} |
|||
|
|||
const receivePurchaseDeny = async (payload) => { |
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
const {owner, sender, chat, amount, mediaToken} = await helpers.parseReceiveParams(payload) |
|||
if(!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!') |
|||
} |
|||
const msg = await models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: sender.id, |
|||
type: constants.message_types.purchase_deny, |
|||
status: constants.statuses.received, |
|||
messageContent:'Purchase has been denied and sats returned to you', |
|||
amount: amount, |
|||
amountMsat: parseFloat(amount) * 1000, |
|||
mediaToken, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}) |
|||
socket.sendJson({ |
|||
type: 'purchase_deny', |
|||
response: jsonUtils.messageToJson(msg, chat) |
|||
}) |
|||
} |
|||
|
|||
const receiveAttachment = async (payload) => { |
|||
console.log('received attachment', { payload }) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const {owner, sender, chat, mediaToken, mediaKey, mediaType, content, msg_id} = await helpers.parseReceiveParams(payload) |
|||
if(!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!') |
|||
} |
|||
|
|||
const msg: {[k:string]:any} = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.attachment, |
|||
sender: sender.id, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
} |
|||
if(content) msg.messageContent = content |
|||
if(mediaToken) msg.mediaToken = mediaToken |
|||
if(mediaKey) msg.mediaKey = mediaKey |
|||
if(mediaType) msg.mediaType = mediaType |
|||
|
|||
const message = await models.Message.create(msg) |
|||
|
|||
console.log('saved attachment', message.dataValues) |
|||
|
|||
socket.sendJson({ |
|||
type: 'attachment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
|
|||
sendNotification(chat, sender.alias, 'message') |
|||
|
|||
sendConfirmation({ chat, sender: owner, msg_id }) |
|||
} |
|||
|
|||
const sendConfirmation = ({ chat, sender, msg_id }) => { |
|||
helpers.sendMessage({ |
|||
chat, |
|||
sender, |
|||
message: {id:msg_id}, |
|||
type: constants.message_types.confirmation, |
|||
}) |
|||
} |
|||
|
|||
async function signer(req, res) { |
|||
if(!req.params.challenge) return resUtils.failure(res, "no challenge") |
|||
try { |
|||
const sig = await signBuffer( |
|||
Buffer.from(req.params.challenge, 'base64') |
|||
) |
|||
const sigBytes = zbase32.decode(sig) |
|||
const sigBase64 = urlBase64FromBytes(sigBytes) |
|||
resUtils.success(res, { |
|||
sig: sigBase64 |
|||
}) |
|||
} catch(e) { |
|||
resUtils.failure(res, e) |
|||
} |
|||
} |
|||
|
|||
async function verifier(msg, sig) { |
|||
try { |
|||
const res = await verifyMessage(msg, sig) |
|||
return res |
|||
} catch(e) { |
|||
console.log(e) |
|||
} |
|||
} |
|||
|
|||
async function getMyPubKey(){ |
|||
return new Promise((resolve,reject)=>{ |
|||
const lightning = loadLightning() |
|||
var request = {} |
|||
lightning.getInfo(request, function(err, response) { |
|||
if(err) reject(err) |
|||
if(!response.identity_pubkey) reject('no pub key') |
|||
else resolve(response.identity_pubkey) |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
async function cycleMediaToken() { |
|||
try{ |
|||
if (process.env.TEST_LDAT) testLDAT() |
|||
|
|||
const mt = await getMediaToken(null) |
|||
if(mt) console.log('=> [meme] authed!') |
|||
|
|||
new CronJob('1 * * * *', function() { // every hour
|
|||
getMediaToken(true) |
|||
}) |
|||
} catch(e) { |
|||
console.log(e.message) |
|||
} |
|||
} |
|||
|
|||
const mediaURL = 'http://' + config.media_host + '/' |
|||
let mediaToken; |
|||
async function getMediaToken(force) { |
|||
if(!force && mediaToken) return mediaToken |
|||
await helpers.sleep(3000) |
|||
try { |
|||
const res = await rp.get(mediaURL+'ask') |
|||
const r = JSON.parse(res) |
|||
if (!(r && r.challenge && r.id)) { |
|||
throw new Error('no challenge') |
|||
} |
|||
const sig = await signBuffer( |
|||
Buffer.from(r.challenge, 'base64') |
|||
) |
|||
|
|||
if(!sig) throw new Error('no signature') |
|||
const pubkey = await getMyPubKey() |
|||
if(!pubkey){ |
|||
throw new Error('no pub key!') |
|||
} |
|||
|
|||
const sigBytes = zbase32.decode(sig) |
|||
const sigBase64 = urlBase64FromBytes(sigBytes) |
|||
|
|||
const bod = await rp.post(mediaURL+'verify', { |
|||
form:{id: r.id, sig:sigBase64, pubkey} |
|||
}) |
|||
const body = JSON.parse(bod) |
|||
if(!(body && body.token)){ |
|||
throw new Error('no token') |
|||
} |
|||
mediaToken = body.token |
|||
return body.token |
|||
} catch(e) { |
|||
throw e |
|||
} |
|||
} |
|||
|
|||
async function getMediaInfo(muid) { |
|||
try { |
|||
const token = await getMediaToken(null) |
|||
const res = await rp.get(mediaURL+'mymedia/'+muid,{ |
|||
headers: { |
|||
'Authorization': `Bearer ${token}`, |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
json:true |
|||
}) |
|||
return res |
|||
} catch(e) { |
|||
return null |
|||
} |
|||
} |
|||
|
|||
export { |
|||
sendAttachmentMessage, |
|||
receiveAttachment, |
|||
receivePurchase, |
|||
receivePurchaseAccept, |
|||
receivePurchaseDeny, |
|||
purchase, |
|||
signer, |
|||
verifier, |
|||
getMediaToken, |
|||
cycleMediaToken |
|||
} |
@ -0,0 +1,277 @@ |
|||
import {models} from '../models' |
|||
import { Op } from 'sequelize' |
|||
import { indexBy } from 'underscore' |
|||
import { sendNotification } from '../hub' |
|||
import * as socket from '../utils/socket' |
|||
import * as jsonUtils from '../utils/json' |
|||
import * as helpers from '../helpers' |
|||
import { success } from '../utils/res' |
|||
import lock from '../utils/lock' |
|||
|
|||
const constants = require(__dirname + '/../../config/constants.json') |
|||
|
|||
const getMessages = async (req, res) => { |
|||
const dateToReturn = req.query.date; |
|||
|
|||
if (!dateToReturn) { |
|||
return getAllMessages(req, res) |
|||
} |
|||
console.log(dateToReturn) |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
// const chatId = req.query.chat_id
|
|||
|
|||
let newMessagesWhere = { |
|||
date: { [Op.gte]: dateToReturn }, |
|||
[Op.or]: [ |
|||
{receiver: owner.id}, |
|||
{receiver: null} |
|||
] |
|||
} |
|||
|
|||
let confirmedMessagesWhere = { |
|||
updated_at: { [Op.gte]: dateToReturn }, |
|||
status: constants.statuses.received, |
|||
sender: owner.id |
|||
} |
|||
|
|||
// if (chatId) {
|
|||
// newMessagesWhere.chat_id = chatId
|
|||
// confirmedMessagesWhere.chat_id = chatId
|
|||
// }
|
|||
|
|||
const newMessages = await models.Message.findAll({ where: newMessagesWhere }) |
|||
const confirmedMessages = await models.Message.findAll({ where: confirmedMessagesWhere }) |
|||
|
|||
const chatIds: number[] = [] |
|||
newMessages.forEach(m => { |
|||
if(!chatIds.includes(m.chatId)) chatIds.push(m.chatId) |
|||
}) |
|||
confirmedMessages.forEach(m => { |
|||
if(!chatIds.includes(m.chatId)) chatIds.push(m.chatId) |
|||
}) |
|||
|
|||
let chats = chatIds.length > 0 ? await models.Chat.findAll({ where: {deleted:false, id: chatIds} }) : [] |
|||
const chatsById = indexBy(chats, 'id') |
|||
|
|||
res.json({ |
|||
success: true, |
|||
response: { |
|||
new_messages: newMessages.map(message => |
|||
jsonUtils.messageToJson(message, chatsById[parseInt(message.chatId)]) |
|||
), |
|||
confirmed_messages: confirmedMessages.map(message => |
|||
jsonUtils.messageToJson(message, chatsById[parseInt(message.chatId)]) |
|||
) |
|||
} |
|||
}); |
|||
res.status(200) |
|||
res.end() |
|||
} |
|||
|
|||
const getAllMessages = async (req, res) => { |
|||
const messages = await models.Message.findAll({ order: [['id', 'asc']] }) |
|||
const chatIds = messages.map(m => m.chatId) |
|||
console.log('=> getAllMessages, chatIds',chatIds) |
|||
let chats = chatIds.length > 0 ? await models.Chat.findAll({ where: {deleted:false, id: chatIds} }) : [] |
|||
const chatsById = indexBy(chats, 'id') |
|||
|
|||
success(res, { |
|||
new_messages: messages.map( |
|||
message => jsonUtils.messageToJson(message, chatsById[parseInt(message.chatId)]) |
|||
), |
|||
confirmed_messages: [] |
|||
}) |
|||
}; |
|||
|
|||
const sendMessage = async (req, res) => { |
|||
// try {
|
|||
// schemas.message.validateSync(req.body)
|
|||
// } catch(e) {
|
|||
// return failure(res, e.message)
|
|||
// }
|
|||
const { |
|||
contact_id, |
|||
text, |
|||
remote_text, |
|||
chat_id, |
|||
remote_text_map, |
|||
} = req.body |
|||
|
|||
console.log('[sendMessage]',) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
const chat = await helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id, |
|||
}) |
|||
|
|||
const remoteMessageContent = remote_text_map?JSON.stringify(remote_text_map) : remote_text |
|||
const msg={ |
|||
chatId: chat.id, |
|||
type: constants.message_types.message, |
|||
sender: owner.id, |
|||
date: date, |
|||
messageContent: text, |
|||
remoteMessageContent, |
|||
status: constants.statuses.pending, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
} |
|||
// console.log(msg)
|
|||
const message = await models.Message.create(msg) |
|||
|
|||
success(res, jsonUtils.messageToJson(message, chat)) |
|||
|
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.message, |
|||
message: { |
|||
id: message.id, |
|||
content: remote_text_map || remote_text || text |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const receiveMessage = async (payload) => { |
|||
console.log('received message', { payload }) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const total_spent = 1 |
|||
const {owner, sender, chat, content, msg_id} = await helpers.parseReceiveParams(payload) |
|||
if(!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!') |
|||
} |
|||
const text = content |
|||
|
|||
const message = await models.Message.create({ |
|||
chatId: chat.id, |
|||
type: constants.message_types.message, |
|||
asciiEncodedTotal: total_spent, |
|||
sender: sender.id, |
|||
date: date, |
|||
messageContent: text, |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
status: constants.statuses.received |
|||
}) |
|||
|
|||
console.log('saved message', message.dataValues) |
|||
|
|||
socket.sendJson({ |
|||
type: 'message', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
|
|||
sendNotification(chat, sender.alias, 'message') |
|||
|
|||
sendConfirmation({ chat, sender: owner, msg_id }) |
|||
} |
|||
|
|||
const sendConfirmation = ({ chat, sender, msg_id }) => { |
|||
helpers.sendMessage({ |
|||
chat, |
|||
sender, |
|||
message: {id:msg_id}, |
|||
type: constants.message_types.confirmation, |
|||
}) |
|||
} |
|||
|
|||
const receiveConfirmation = async (payload) => { |
|||
console.log('received confirmation', { payload }) |
|||
|
|||
const dat = payload.content || payload |
|||
const chat_uuid = dat.chat.uuid |
|||
const msg_id = dat.message.id |
|||
const sender_pub_key = dat.sender.pub_key |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
const sender = await models.Contact.findOne({ where: { publicKey: sender_pub_key } }) |
|||
const chat = await models.Chat.findOne({ where: { uuid: chat_uuid } }) |
|||
|
|||
// new confirmation logic
|
|||
if(msg_id){ |
|||
lock.acquire('confirmation', async function(done){ |
|||
console.log("update status map") |
|||
const message = await models.Message.findOne({ where:{id:msg_id} }) |
|||
if(message){ |
|||
let statusMap = {} |
|||
try{ |
|||
statusMap = JSON.parse(message.statusMap||'{}') |
|||
} catch(e){} |
|||
statusMap[sender.id] = constants.statuses.received |
|||
|
|||
await message.update({ |
|||
status: constants.statuses.received, |
|||
statusMap: JSON.stringify(statusMap) |
|||
}) |
|||
socket.sendJson({ |
|||
type: 'confirmation', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
} |
|||
done() |
|||
}) |
|||
} else { // old logic
|
|||
const messages = await models.Message.findAll({ |
|||
limit: 1, |
|||
where: { |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: [ |
|||
constants.message_types.message, |
|||
constants.message_types.invoice, |
|||
constants.message_types.attachment, |
|||
], |
|||
status: constants.statuses.pending, |
|||
}, |
|||
order: [['createdAt', 'desc']] |
|||
}) |
|||
|
|||
const message = messages[0] |
|||
message.update({ status: constants.statuses.received }) |
|||
|
|||
socket.sendJson({ |
|||
type: 'confirmation', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
} |
|||
} |
|||
|
|||
const readMessages = async (req, res) => { |
|||
const chat_id = req.params.chat_id; |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
|
|||
models.Message.update({ seen: true }, { |
|||
where: { |
|||
sender: { |
|||
[Op.ne]: owner.id |
|||
}, |
|||
chatId: chat_id |
|||
} |
|||
}); |
|||
|
|||
success(res, {}) |
|||
} |
|||
|
|||
const clearMessages = (req, res) => { |
|||
models.Message.destroy({ where: {}, truncate: true }) |
|||
|
|||
success(res, {}) |
|||
} |
|||
|
|||
export { |
|||
getMessages, |
|||
sendMessage, |
|||
receiveMessage, |
|||
receiveConfirmation, |
|||
clearMessages, |
|||
readMessages |
|||
} |
@ -0,0 +1,199 @@ |
|||
import {models} from '../models' |
|||
import { sendNotification } from '../hub' |
|||
import * as socket from '../utils/socket' |
|||
import * as jsonUtils from '../utils/json' |
|||
import * as helpers from '../helpers' |
|||
import { success } from '../utils/res' |
|||
import * as lightning from '../utils/lightning' |
|||
import {tokenFromTerms} from '../utils/ldat' |
|||
|
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
|
|||
const sendPayment = async (req, res) => { |
|||
const { |
|||
amount, |
|||
chat_id, |
|||
contact_id, |
|||
destination_key, |
|||
media_type, |
|||
muid, |
|||
text, |
|||
remote_text, |
|||
dimensions, |
|||
} = req.body |
|||
|
|||
console.log('[send payment]', req.body) |
|||
|
|||
if (destination_key && !contact_id && !chat_id) { |
|||
return helpers.performKeysendMessage({ |
|||
destination_key, |
|||
amount, |
|||
msg:'{}', |
|||
success: () => { |
|||
console.log('payment sent!') |
|||
success(res, {destination_key, amount}) |
|||
}, |
|||
failure: (error) => { |
|||
res.status(200); |
|||
res.json({ success: false, error }); |
|||
res.end(); |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
|
|||
const chat = await helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id |
|||
}) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const msg: {[k:string]:any} = { |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.direct_payment, |
|||
amount: amount, |
|||
amountMsat: parseFloat(amount) * 1000, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
} |
|||
if(text) msg.messageContent = text |
|||
if(remote_text) msg.remoteMessageContent = remote_text |
|||
|
|||
if(muid){ |
|||
const myMediaToken = await tokenFromTerms({ |
|||
meta:{dim:dimensions}, host:'', |
|||
muid, ttl:null, // default one year
|
|||
pubkey: owner.publicKey |
|||
}) |
|||
msg.mediaToken = myMediaToken |
|||
msg.mediaType = media_type || '' |
|||
} |
|||
|
|||
const message = await models.Message.create(msg) |
|||
|
|||
const msgToSend: {[k:string]:any} = { |
|||
id:message.id, |
|||
amount, |
|||
} |
|||
if(muid) { |
|||
msgToSend.mediaType = media_type||'image/jpeg' |
|||
msgToSend.mediaTerms = {muid,meta:{dim:dimensions}} |
|||
} |
|||
if(remote_text) msgToSend.content = remote_text |
|||
|
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.direct_payment, |
|||
message: msgToSend, |
|||
amount: amount, |
|||
success: async (data) => { |
|||
// console.log('payment sent', { data })
|
|||
success(res, jsonUtils.messageToJson(message, chat)) |
|||
}, |
|||
failure: (error) => { |
|||
res.status(200); |
|||
res.json({ success: false, error }); |
|||
res.end(); |
|||
} |
|||
}) |
|||
}; |
|||
|
|||
const receivePayment = async (payload) => { |
|||
console.log('received payment', { payload }) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const {owner, sender, chat, amount, content, mediaType, mediaToken} = await helpers.parseReceiveParams(payload) |
|||
if(!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!') |
|||
} |
|||
|
|||
const msg: {[k:string]:any} = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.direct_payment, |
|||
sender: sender.id, |
|||
amount: amount, |
|||
amountMsat: parseFloat(amount) * 1000, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
} |
|||
if(content) msg.messageContent = content |
|||
if(mediaType) msg.mediaType = mediaType |
|||
if(mediaToken) msg.mediaToken = mediaToken |
|||
|
|||
const message = await models.Message.create(msg) |
|||
|
|||
console.log('saved message', message.dataValues) |
|||
|
|||
socket.sendJson({ |
|||
type: 'direct_payment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
|
|||
sendNotification(chat, sender.alias, 'message') |
|||
} |
|||
|
|||
const listPayments = async (req, res) => { |
|||
const limit = (req.query.limit && parseInt(req.query.limit)) || 100 |
|||
const offset = (req.query.offset && parseInt(req.query.offset)) || 0 |
|||
|
|||
const payments: any[] = [] |
|||
|
|||
const response:any = await lightning.listInvoices() |
|||
const invs = response && response.invoices |
|||
if(invs && invs.length){ |
|||
invs.forEach(inv=>{ |
|||
const val = inv.value && parseInt(inv.value) |
|||
if(val && val>1) { |
|||
let payment_hash='' |
|||
if(inv.r_hash){ |
|||
payment_hash = Buffer.from(inv.r_hash).toString('hex') |
|||
} |
|||
payments.push({ |
|||
type:'invoice', |
|||
amount:parseInt(inv.value), |
|||
date:parseInt(inv.creation_date), |
|||
payment_request:inv.payment_request, |
|||
payment_hash |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const res2:any = await lightning.listPayments() |
|||
const pays = res2 && res2.payments |
|||
if(pays && pays.length){ |
|||
pays.forEach(pay=>{ |
|||
const val = pay.value && parseInt(pay.value) |
|||
if(val && val>1) { |
|||
payments.push({ |
|||
type:'payment', |
|||
amount:parseInt(pay.value), |
|||
date:parseInt(pay.creation_date), |
|||
pubkey:pay.path[pay.path.length-1], |
|||
payment_hash: pay.payment_hash, |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// latest one first
|
|||
payments.sort((a,b)=> b.date - a.date) |
|||
|
|||
success(res, payments.splice(offset, limit)) |
|||
}; |
|||
|
|||
export { |
|||
sendPayment, |
|||
receivePayment, |
|||
listPayments, |
|||
} |
@ -0,0 +1,29 @@ |
|||
import * as yup from 'yup' |
|||
|
|||
/* |
|||
These schemas validate payloads coming from app, |
|||
do not necessarily match up with Models |
|||
*/ |
|||
|
|||
const attachment = yup.object().shape({ |
|||
muid: yup.string().required(), |
|||
media_type: yup.string().required(), |
|||
media_key_map: yup.object().required(), |
|||
}) |
|||
|
|||
const message = yup.object().shape({ |
|||
contact_id: yup.number().required(), |
|||
}) |
|||
|
|||
const purchase = yup.object().shape({ |
|||
chat_id: yup.number().required(), |
|||
contact_id: yup.number().required(), |
|||
mediaToken: yup.string().required(), |
|||
amount: yup.number().required() |
|||
}) |
|||
|
|||
export { |
|||
attachment, |
|||
purchase, |
|||
message, |
|||
} |
@ -0,0 +1,387 @@ |
|||
import {models} from '../models' |
|||
import {success, failure} from '../utils/res' |
|||
import {CronJob} from 'cron' |
|||
import {toCamel} from '../utils/case' |
|||
import * as cronUtils from '../utils/cron' |
|||
import * as socket from '../utils/socket' |
|||
import * as jsonUtils from '../utils/json' |
|||
import * as helpers from '../helpers' |
|||
import * as rsa from '../crypto/rsa' |
|||
import * as moment from 'moment' |
|||
|
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
|
|||
// store all current running jobs in memory
|
|||
let jobs = {} |
|||
|
|||
// init jobs from DB
|
|||
const initializeCronJobs = async () => { |
|||
await helpers.sleep(1000) |
|||
const subs = await getRawSubs({ where: { ended: false } }) |
|||
subs.length && subs.forEach(sub => { |
|||
console.log("=> starting subscription cron job",sub.id+":",sub.cron) |
|||
startCronJob(sub) |
|||
}) |
|||
} |
|||
|
|||
async function startCronJob(sub) { |
|||
jobs[sub.id] = new CronJob(sub.cron, async function () { |
|||
const subscription = await models.Subscription.findOne({ where: { id: sub.id } }) |
|||
if (!subscription) { |
|||
delete jobs[sub.id] |
|||
return this.stop() |
|||
} |
|||
|
|||
console.log('EXEC CRON =>', subscription.id) |
|||
if (subscription.paused) { // skip, still in jobs{} tho
|
|||
return this.stop() |
|||
} |
|||
let STOP = checkSubscriptionShouldAlreadyHaveEnded(subscription) |
|||
if (STOP) { // end the job and return
|
|||
console.log("stop") |
|||
subscription.update({ ended: true }) |
|||
delete jobs[subscription.id] |
|||
return this.stop() |
|||
} |
|||
// SEND PAYMENT!!!
|
|||
sendSubscriptionPayment(subscription, false) |
|||
}, null, true); |
|||
} |
|||
|
|||
function checkSubscriptionShouldAlreadyHaveEnded(sub) { |
|||
if (sub.endDate) { |
|||
const now = new Date() |
|||
if (now.getTime() > sub.endDate.getTime()) { |
|||
return true |
|||
} |
|||
} |
|||
if (sub.endNumber) { |
|||
if (sub.count >= sub.endNumber) { |
|||
return true |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
function checkSubscriptionShouldEndAfterThisPayment(sub) { |
|||
if (sub.endDate) { |
|||
const { ms } = cronUtils.parse(sub.cron) |
|||
const now = new Date() |
|||
if ((now.getTime() + ms) > sub.endDate.getTime()) { |
|||
return true |
|||
} |
|||
} |
|||
if (sub.endNumber) { |
|||
if (sub.count + 1 >= sub.endNumber) { |
|||
return true |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
function msgForSubPayment(owner, sub, isFirstMessage, forMe){ |
|||
let text = '' |
|||
if (isFirstMessage) { |
|||
const alias = forMe ? 'You' : owner.alias |
|||
text = `${alias} subscribed\n` |
|||
} else { |
|||
text = 'Subscription\n' |
|||
} |
|||
text += `Amount: ${sub.amount} sats\n` |
|||
text += `Interval: ${cronUtils.parse(sub.cron).interval}\n` |
|||
if(sub.endDate) { |
|||
text += `End: ${moment(sub.endDate).format('MM/DD/YY')}\n` |
|||
text += `Status: ${sub.count+1} sent` |
|||
} else if(sub.endNumber) { |
|||
text += `Status: ${sub.count+1} of ${sub.endNumber} sent` |
|||
} |
|||
return text |
|||
} |
|||
|
|||
async function sendSubscriptionPayment(sub, isFirstMessage) { |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
|
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
const subscription = await models.Subscription.findOne({ where: { id: sub.id } }) |
|||
if (!subscription) { |
|||
return |
|||
} |
|||
const chat = await models.Chat.findOne({ where: {id:subscription.chatId} }) |
|||
if (!subscription) { |
|||
console.log("=> no sub for this payment!!!") |
|||
return |
|||
} |
|||
|
|||
const forMe = false |
|||
const text = msgForSubPayment(owner, sub, isFirstMessage, forMe) |
|||
|
|||
const contact = await models.Contact.findByPk(sub.contactId) |
|||
const enc = rsa.encrypt(contact.contactKey, text) |
|||
|
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.direct_payment, |
|||
message: { amount: sub.amount, content: enc }, |
|||
amount: sub.amount, |
|||
success: async (data) => { |
|||
const shouldEnd = checkSubscriptionShouldEndAfterThisPayment(subscription) |
|||
const obj = { |
|||
totalPaid: parseFloat(subscription.totalPaid||0) + parseFloat(subscription.amount), |
|||
count: parseInt(subscription.count||0) + 1, |
|||
ended: false, |
|||
} |
|||
if(shouldEnd) { |
|||
obj.ended = true |
|||
if(jobs[sub.id]) jobs[subscription.id].stop() |
|||
delete jobs[subscription.id] |
|||
} |
|||
await subscription.update(obj) |
|||
|
|||
const forMe = true |
|||
const text2 = msgForSubPayment(owner, sub, isFirstMessage, forMe) |
|||
const encText = rsa.encrypt(owner.contactKey, text2) |
|||
const message = await models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.direct_payment, |
|||
status: constants.statuses.confirmed, |
|||
messageContent: encText, |
|||
amount: subscription.amount, |
|||
amountMsat: parseFloat(subscription.amount) * 1000, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
subscriptionId: subscription.id, |
|||
}) |
|||
socket.sendJson({ |
|||
type: 'direct_payment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
}, |
|||
failure: async (err) => { |
|||
console.log("SEND PAY ERROR") |
|||
let errMessage = constants.payment_errors[err] || 'Unknown' |
|||
errMessage = 'Payment Failed: ' + errMessage |
|||
const message = await models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.direct_payment, |
|||
status: constants.statuses.failed, |
|||
messageContent: errMessage, |
|||
amount: sub.amount, |
|||
amountMsat: parseFloat(sub.amount) * 1000, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
subscriptionId: sub.id, |
|||
}) |
|||
socket.sendJson({ |
|||
type: 'direct_payment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
// pause sub
|
|||
async function pauseSubscription(req, res) { |
|||
const id = parseInt(req.params.id) |
|||
try { |
|||
const sub = await models.Subscription.findOne({ where: { id } }) |
|||
if (sub) { |
|||
sub.update({ paused: true }) |
|||
if (jobs[id]) jobs[id].stop() |
|||
success(res, jsonUtils.subscriptionToJson(sub,null)) |
|||
} else { |
|||
failure(res, 'not found') |
|||
} |
|||
} catch (e) { |
|||
console.log('ERROR pauseSubscription', e) |
|||
failure(res, e) |
|||
} |
|||
}; |
|||
|
|||
// restart sub
|
|||
async function restartSubscription(req, res) { |
|||
const id = parseInt(req.params.id) |
|||
try { |
|||
const sub = await models.Subscription.findOne({ where: { id } }) |
|||
if (sub) { |
|||
sub.update({ paused: false }) |
|||
if (jobs[id]) jobs[id].start() |
|||
success(res, jsonUtils.subscriptionToJson(sub,null)) |
|||
} else { |
|||
failure(res, 'not found') |
|||
} |
|||
} catch (e) { |
|||
console.log('ERROR restartSubscription', e) |
|||
failure(res, e) |
|||
} |
|||
}; |
|||
|
|||
async function getRawSubs(opts = {}) { |
|||
const options: {[k: string]: any} = { order: [['id', 'asc']], ...opts } |
|||
try { |
|||
const subs = await models.Subscription.findAll(options) |
|||
return subs |
|||
} catch (e) { |
|||
throw e |
|||
} |
|||
} |
|||
|
|||
// all subs
|
|||
const getAllSubscriptions = async (req, res) => { |
|||
try { |
|||
const subs = await getRawSubs() |
|||
success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub,null))) |
|||
} catch (e) { |
|||
console.log('ERROR getAllSubscriptions', e) |
|||
failure(res, e) |
|||
} |
|||
}; |
|||
|
|||
// one sub by id
|
|||
async function getSubscription(req, res) { |
|||
try { |
|||
const sub = await models.Subscription.findOne({ where: { id: req.params.id } }) |
|||
success(res, jsonUtils.subscriptionToJson(sub,null)) |
|||
} catch (e) { |
|||
console.log('ERROR getSubscription', e) |
|||
failure(res, e) |
|||
} |
|||
}; |
|||
|
|||
// delete sub by id
|
|||
async function deleteSubscription(req, res) { |
|||
const id = req.params.id |
|||
if (!id) return |
|||
try { |
|||
if (jobs[id]) { |
|||
jobs[id].stop() |
|||
delete jobs[id] |
|||
} |
|||
models.Subscription.destroy({ where: { id } }) |
|||
success(res, true) |
|||
} catch (e) { |
|||
console.log('ERROR deleteSubscription', e) |
|||
failure(res, e) |
|||
} |
|||
}; |
|||
|
|||
// all subs for contact id
|
|||
const getSubscriptionsForContact = async (req, res) => { |
|||
try { |
|||
const subs = await getRawSubs({ where: { contactId: req.params.contactId } }) |
|||
success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub,null))) |
|||
} catch (e) { |
|||
console.log('ERROR getSubscriptionsForContact', e) |
|||
failure(res, e) |
|||
} |
|||
}; |
|||
|
|||
// create new sub
|
|||
async function createSubscription(req, res) { |
|||
const date = new Date() |
|||
date.setMilliseconds(0) |
|||
const s = jsonToSubscription({ |
|||
...req.body, |
|||
count: 0, |
|||
total_paid: 0, |
|||
createdAt: date, |
|||
ended: false, |
|||
paused: false |
|||
}) |
|||
if(!s.cron){ |
|||
return failure(res, 'Invalid interval') |
|||
} |
|||
try { |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
const chat = await helpers.findOrCreateChat({ |
|||
chat_id: req.body.chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: req.body.contact_id, |
|||
}) |
|||
s.chatId = chat.id // add chat id if newly created
|
|||
if(!owner || !chat){ |
|||
return failure(res, 'Invalid chat or contact') |
|||
} |
|||
const sub = await models.Subscription.create(s) |
|||
startCronJob(sub) |
|||
const isFirstMessage = true |
|||
sendSubscriptionPayment(sub, isFirstMessage) |
|||
success(res, jsonUtils.subscriptionToJson(sub, chat)) |
|||
} catch (e) { |
|||
console.log('ERROR createSubscription', e) |
|||
failure(res, e) |
|||
} |
|||
}; |
|||
|
|||
async function editSubscription(req, res) { |
|||
console.log('======> editSubscription') |
|||
const date = new Date() |
|||
date.setMilliseconds(0) |
|||
const id = parseInt(req.params.id) |
|||
const s = jsonToSubscription({ |
|||
...req.body, |
|||
count: 0, |
|||
createdAt: date, |
|||
ended: false, |
|||
paused: false |
|||
}) |
|||
try { |
|||
if(!id || !s.chatId || !s.cron){ |
|||
return failure(res, 'Invalid data') |
|||
} |
|||
const subRecord = await models.Subscription.findOne({ where: { id }}) |
|||
if(!subRecord) { |
|||
return failure(res, 'No subscription found') |
|||
} |
|||
// stop so it can be restarted
|
|||
if (jobs[id]) jobs[id].stop() |
|||
const obj: {[k: string]: any} = { |
|||
cron: s.cron, |
|||
updatedAt: date, |
|||
} |
|||
if(s.amount) obj.amount = s.amount |
|||
if(s.endDate) obj.endDate = s.endDate |
|||
if(s.endNumber) obj.endNumber = s.endNumber |
|||
|
|||
const sub = await subRecord.update(obj) |
|||
const end = checkSubscriptionShouldAlreadyHaveEnded(sub) |
|||
if(end) { |
|||
await subRecord.update({ended:true}) |
|||
delete jobs[id] |
|||
} else { |
|||
startCronJob(sub) // restart
|
|||
} |
|||
const chat = await models.Chat.findOne({ where: { id: s.chatId }}) |
|||
success(res, jsonUtils.subscriptionToJson(sub, chat)) |
|||
} catch (e) { |
|||
console.log('ERROR createSubscription', e) |
|||
failure(res, e) |
|||
} |
|||
}; |
|||
|
|||
function jsonToSubscription(j) { |
|||
console.log("=>",j) |
|||
const cron = cronUtils.make(j.interval) |
|||
return toCamel({ |
|||
...j, |
|||
cron, |
|||
}) |
|||
} |
|||
|
|||
export { |
|||
initializeCronJobs, |
|||
getAllSubscriptions, |
|||
getSubscription, |
|||
createSubscription, |
|||
getSubscriptionsForContact, |
|||
pauseSubscription, |
|||
restartSubscription, |
|||
deleteSubscription, |
|||
editSubscription, |
|||
} |
@ -0,0 +1,60 @@ |
|||
import {models} from '../models' |
|||
|
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/app.json')[env]; |
|||
|
|||
// setup disk storage
|
|||
var multer = require('multer') |
|||
var avatarStorage = multer.diskStorage({ |
|||
destination: (req, file, cb) => { |
|||
let dir = __dirname.includes('/dist/') ? __dirname+'/..' : __dirname |
|||
cb(null, dir + '/../../public/uploads') |
|||
}, |
|||
filename: (req, file, cb) => { |
|||
const mime = file.mimetype |
|||
const extA = mime.split("/") |
|||
const ext = extA[extA.length-1] |
|||
if(req.body.chat_id){ |
|||
cb(null, `chat_${req.body.chat_id}_picture.${ext}`) |
|||
} else { |
|||
cb(null, `${req.body.contact_id}_profile_picture.${ext}`) |
|||
} |
|||
} |
|||
}) |
|||
var avatarUpload = multer({ storage: avatarStorage }) |
|||
|
|||
const uploadFile = async (req, res) => { |
|||
const { contact_id, chat_id } = req.body |
|||
const { file } = req |
|||
|
|||
const photo_url = |
|||
config.node_http_protocol + |
|||
'://' + |
|||
process.env.NODE_IP + |
|||
'/static/uploads/' + |
|||
file.filename |
|||
|
|||
if(contact_id){ |
|||
const contact = await models.Contact.findOne({ where: { id: contact_id } }) |
|||
if(contact) contact.update({ photoUrl: photo_url }) |
|||
} |
|||
|
|||
if(chat_id){ |
|||
const chat = await models.Chat.findOne({ where: { id: chat_id } }) |
|||
if(chat) chat.update({ photoUrl: photo_url }) |
|||
} |
|||
|
|||
res.status(200) |
|||
res.json({ |
|||
success: true, |
|||
contact_id: parseInt(contact_id||0), |
|||
chat_id: parseInt(chat_id||0), |
|||
photo_url |
|||
}); |
|||
res.end(); |
|||
} |
|||
|
|||
export { |
|||
avatarUpload, |
|||
uploadFile |
|||
} |
@ -0,0 +1,63 @@ |
|||
import * as crypto from "crypto"; |
|||
|
|||
export function encrypt(key, txt){ |
|||
try{ |
|||
const pubc = cert.pub(key) |
|||
const buf = crypto.publicEncrypt({ |
|||
key:pubc, |
|||
padding:crypto.constants.RSA_PKCS1_PADDING, |
|||
}, Buffer.from(txt,'utf-8')) |
|||
return buf.toString('base64') |
|||
} catch(e) { |
|||
return '' |
|||
} |
|||
} |
|||
|
|||
export function decrypt(privateKey, enc){ |
|||
try{ |
|||
const privc = cert.priv(privateKey) |
|||
const buf = crypto.privateDecrypt({ |
|||
key:privc, |
|||
padding:crypto.constants.RSA_PKCS1_PADDING, |
|||
}, Buffer.from(enc,'base64')) |
|||
return buf.toString('utf-8') |
|||
} catch(e) { |
|||
return '' |
|||
} |
|||
} |
|||
|
|||
export function testRSA(){ |
|||
crypto.generateKeyPair('rsa', { |
|||
modulusLength: 2048 |
|||
}, (err, publicKey, priv)=>{ |
|||
const pubPEM = publicKey.export({ |
|||
type:'pkcs1',format:'pem' |
|||
}) |
|||
const pub = cert.unpub(pubPEM) |
|||
|
|||
const msg = 'hi' |
|||
const enc = encrypt(pub, msg) |
|||
|
|||
const dec = decrypt(priv, enc) |
|||
console.log("FINAL:",dec) |
|||
}) |
|||
} |
|||
|
|||
const cert = { |
|||
unpub: function(key){ |
|||
let s = key |
|||
s = s.replace('-----BEGIN RSA PUBLIC KEY-----','') |
|||
s = s.replace('-----END RSA PUBLIC KEY-----','') |
|||
return s.replace(/[\r\n]+/gm, '') |
|||
}, |
|||
pub:function(key){ |
|||
return '-----BEGIN RSA PUBLIC KEY-----\n' + |
|||
key + '\n' + |
|||
'-----END RSA PUBLIC KEY-----' |
|||
}, |
|||
priv:function(key){ |
|||
return '-----BEGIN RSA PRIVATE KEY-----\n' + |
|||
key + '\n' + |
|||
'-----END RSA PRIVATE KEY-----' |
|||
} |
|||
} |
@ -0,0 +1,144 @@ |
|||
import {models} from '../models' |
|||
import * as socket from '../utils/socket' |
|||
import { sendNotification } from '../hub' |
|||
import * as jsonUtils from '../utils/json' |
|||
import * as decodeUtils from '../utils/decode' |
|||
import {loadLightning, SPHINX_CUSTOM_RECORD_KEY} from '../utils/lightning' |
|||
|
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
|
|||
function parseKeysendInvoice(i, actions){ |
|||
const recs = i.htlcs && i.htlcs[0] && i.htlcs[0].custom_records |
|||
const buf = recs && recs[SPHINX_CUSTOM_RECORD_KEY] |
|||
const data = buf && buf.toString() |
|||
const value = i && i.value && parseInt(i.value) |
|||
if(!data) return |
|||
|
|||
let payload |
|||
if(data[0]==='{'){ |
|||
try { |
|||
payload = JSON.parse(data) |
|||
} catch(e){} |
|||
} else { |
|||
const threads = weave(data) |
|||
if(threads) payload = JSON.parse(threads) |
|||
} |
|||
if(payload){ |
|||
const dat = payload.content || payload |
|||
if(value && dat && dat.message){ |
|||
dat.message.amount = value |
|||
} |
|||
if(actions[payload.type]) { |
|||
actions[payload.type](payload) |
|||
} else { |
|||
console.log('Incorrect payload type:', payload.type) |
|||
} |
|||
} |
|||
} |
|||
|
|||
const chunks = {} |
|||
function weave(p){ |
|||
const pa = p.split('_') |
|||
if(pa.length<4) return |
|||
const ts = pa[0] |
|||
const i = pa[1] |
|||
const n = pa[2] |
|||
const m = pa.filter((u,i)=>i>2).join('_') |
|||
chunks[ts] = chunks[ts] ? [...chunks[ts], {i,n,m}] : [{i,n,m}] |
|||
if(chunks[ts].length===parseInt(n)){ |
|||
// got em all!
|
|||
const all = chunks[ts] |
|||
let payload = '' |
|||
all.slice().sort((a,b)=>a.i-b.i).forEach(obj=>{ |
|||
payload += obj.m |
|||
}) |
|||
delete chunks[ts] |
|||
return payload |
|||
} |
|||
} |
|||
|
|||
function subscribeInvoices(actions) { |
|||
return new Promise(async(resolve,reject)=>{ |
|||
const lightning = await loadLightning() |
|||
|
|||
var call = lightning.subscribeInvoices(); |
|||
call.on('data', async function(response) { |
|||
// console.log('subscribed invoices', { response })
|
|||
if (response['state'] !== 'SETTLED') { |
|||
return |
|||
} |
|||
// console.log("IS KEYSEND", response.is_keysend)
|
|||
if(response.is_keysend) { |
|||
parseKeysendInvoice(response, actions) |
|||
} else { |
|||
const invoice = await models.Message.findOne({ where: { type: constants.message_types.invoice, payment_request: response['payment_request'] } }) |
|||
if (invoice == null) { |
|||
// console.log("ERROR: Invoice " + response['payment_request'] + " not found");
|
|||
socket.sendJson({ |
|||
type: 'invoice_payment', |
|||
response: {invoice: response['payment_request']} |
|||
}) |
|||
return |
|||
} |
|||
models.Message.update({ status: constants.statuses.confirmed }, { where: { id: invoice.id } }) |
|||
|
|||
let decodedPaymentRequest = decodeUtils.decode(response['payment_request']); |
|||
|
|||
var paymentHash = ""; |
|||
for (var i=0; i<decodedPaymentRequest["data"]["tags"].length; i++) { |
|||
let tag = decodedPaymentRequest["data"]["tags"][i]; |
|||
if (tag['description'] == 'payment_hash') { |
|||
paymentHash = tag['value']; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
let settleDate = parseInt(response['settle_date'] + '000'); |
|||
|
|||
const chat = await models.Chat.findOne({ where: { id: invoice.chatId } }) |
|||
const contactIds = JSON.parse(chat.contactIds) |
|||
const senderId = contactIds.find(id => id != invoice.sender) |
|||
|
|||
const message = await models.Message.create({ |
|||
chatId: invoice.chatId, |
|||
type: constants.message_types.payment, |
|||
sender: senderId, |
|||
amount: response['amt_paid_sat'], |
|||
amountMsat: response['amt_paid_msat'], |
|||
paymentHash: paymentHash, |
|||
date: new Date(settleDate), |
|||
messageContent: response['memo'], |
|||
status: constants.statuses.confirmed, |
|||
createdAt: new Date(settleDate), |
|||
updatedAt: new Date(settleDate) |
|||
}) |
|||
socket.sendJson({ |
|||
type: 'payment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}) |
|||
|
|||
const sender = await models.Contact.findOne({ where: { id: senderId } }) |
|||
sendNotification(chat, sender.alias, 'message') |
|||
} |
|||
}); |
|||
call.on('status', function(status) { |
|||
console.log("Status", status); |
|||
resolve(status) |
|||
}); |
|||
call.on('error', function(err){ |
|||
// console.log(err)
|
|||
reject(err) |
|||
}) |
|||
call.on('end', function() { |
|||
console.log("Closed stream"); |
|||
// The server has closed the stream.
|
|||
}); |
|||
setTimeout(()=>{ |
|||
resolve(null) |
|||
},100) |
|||
}) |
|||
} |
|||
|
|||
export { |
|||
subscribeInvoices, |
|||
} |
@ -0,0 +1,247 @@ |
|||
import { models } from './models' |
|||
import * as md5 from 'md5' |
|||
import { keysendMessage } from './utils/lightning' |
|||
import {personalizeMessage} from './utils/msg' |
|||
|
|||
const constants = require('../config/constants.json'); |
|||
|
|||
const findOrCreateChat = async (params) => { |
|||
const { chat_id, owner_id, recipient_id } = params |
|||
let chat |
|||
let date = new Date(); |
|||
date.setMilliseconds(0) |
|||
|
|||
if (chat_id) { |
|||
chat = await models.Chat.findOne({ where: { id: chat_id } }) |
|||
// console.log('findOrCreateChat: chat_id exists')
|
|||
} else { |
|||
console.log("chat does not exists, create new") |
|||
const owner = await models.Contact.findOne({ where: { id: owner_id } }) |
|||
const recipient = await models.Contact.findOne({ where: { id: recipient_id } }) |
|||
const uuid = md5([owner.publicKey, recipient.publicKey].sort().join("-")) |
|||
|
|||
// find by uuid
|
|||
chat = await models.Chat.findOne({ where:{uuid} }) |
|||
|
|||
if(!chat){ // no chat! create new
|
|||
chat = await models.Chat.create({ |
|||
uuid: uuid, |
|||
contactIds: JSON.stringify([parseInt(owner_id), parseInt(recipient_id)]), |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
type: constants.chat_types.conversation |
|||
}) |
|||
} |
|||
} |
|||
return chat |
|||
} |
|||
|
|||
const sendContactKeys = async (args) => { |
|||
const { type, contactIds, contactPubKey, sender, success, failure } = args |
|||
const msg = newkeyexchangemsg(type, sender) |
|||
|
|||
let yes:any = null |
|||
let no:any = null |
|||
let cids = contactIds |
|||
|
|||
if(!contactIds) cids = [null] // nully
|
|||
await asyncForEach(cids, async contactId => { |
|||
let destination_key:string |
|||
if(!contactId){ // nully
|
|||
destination_key = contactPubKey |
|||
} else { |
|||
if (contactId == sender.id) { |
|||
return |
|||
} |
|||
const contact = await models.Contact.findOne({ where: { id: contactId } }) |
|||
destination_key = contact.publicKey |
|||
} |
|||
performKeysendMessage({ |
|||
destination_key, |
|||
amount: 1, |
|||
msg: JSON.stringify(msg), |
|||
success: (data) => { |
|||
yes = data |
|||
}, |
|||
failure: (error) => { |
|||
no = error |
|||
} |
|||
}) |
|||
}) |
|||
if(no && failure){ |
|||
failure(no) |
|||
} |
|||
if(!no && yes && success){ |
|||
success(yes) |
|||
} |
|||
} |
|||
|
|||
const sendMessage = async (params) => { |
|||
const { type, chat, message, sender, amount, success, failure } = params |
|||
const m = newmsg(type, chat, sender, message) |
|||
|
|||
const contactIds = typeof chat.contactIds==='string' ? JSON.parse(chat.contactIds) : chat.contactIds |
|||
|
|||
let yes:any = null |
|||
let no:any = null |
|||
console.log('all contactIds',contactIds) |
|||
await asyncForEach(contactIds, async contactId => { |
|||
if (contactId == sender.id) { |
|||
return |
|||
} |
|||
|
|||
const contact = await models.Contact.findOne({ where: { id: contactId } }) |
|||
const destkey = contact.publicKey |
|||
|
|||
const finalMsg = await personalizeMessage(m, contactId, destkey) |
|||
|
|||
const opts = { |
|||
dest: destkey, |
|||
data: JSON.stringify(finalMsg), |
|||
amt: amount || 1, |
|||
} |
|||
try { |
|||
const r = await keysendMessage(opts) |
|||
yes = r |
|||
} catch (e) { |
|||
console.log("KEYSEND ERROR", e) |
|||
no = e |
|||
} |
|||
}) |
|||
if(yes){ |
|||
if(success) success(yes) |
|||
} else { |
|||
if(failure) failure(no) |
|||
} |
|||
} |
|||
|
|||
const performKeysendMessage = async ({ destination_key, amount, msg, success, failure }) => { |
|||
const opts = { |
|||
dest: destination_key, |
|||
data: msg || JSON.stringify({}), |
|||
amt: amount || 1 |
|||
} |
|||
try { |
|||
const r = await keysendMessage(opts) |
|||
console.log("MESSAGE SENT outside SW!", r) |
|||
if (success) success(r) |
|||
} catch (e) { |
|||
console.log("MESSAGE ERROR", e) |
|||
if (failure) failure(e) |
|||
} |
|||
} |
|||
|
|||
async function findOrCreateContactByPubkey(senderPubKey) { |
|||
let sender = await models.Contact.findOne({ where: { publicKey: senderPubKey } }) |
|||
if (!sender) { |
|||
sender = await models.Contact.create({ |
|||
publicKey: senderPubKey, |
|||
alias: "Unknown", |
|||
status: 1 |
|||
}) |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
sendContactKeys({ |
|||
contactIds: [sender.id], |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}) |
|||
} |
|||
return sender |
|||
} |
|||
|
|||
async function findOrCreateChatByUUID(chat_uuid, contactIds) { |
|||
let chat = await models.Chat.findOne({ where: { uuid: chat_uuid } }) |
|||
if (!chat) { |
|||
var date = new Date(); |
|||
date.setMilliseconds(0) |
|||
chat = await models.Chat.create({ |
|||
uuid: chat_uuid, |
|||
contactIds: JSON.stringify(contactIds || []), |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
type: 0 // conversation
|
|||
}) |
|||
} |
|||
return chat |
|||
} |
|||
|
|||
async function sleep(ms) { |
|||
return new Promise(resolve => setTimeout(resolve, ms)) |
|||
} |
|||
|
|||
async function parseReceiveParams(payload) { |
|||
const dat = payload.content || payload |
|||
const sender_pub_key = dat.sender.pub_key |
|||
const chat_uuid = dat.chat.uuid |
|||
const chat_type = dat.chat.type |
|||
const chat_members: { [k: string]: any } = dat.chat.members || {} |
|||
const chat_name = dat.chat.name |
|||
const amount = dat.message.amount |
|||
const content = dat.message.content |
|||
const mediaToken = dat.message.mediaToken |
|||
const msg_id = dat.message.id||0 |
|||
const mediaKey = dat.message.mediaKey |
|||
const mediaType = dat.message.mediaType |
|||
|
|||
const isGroup = chat_type && chat_type == constants.chat_types.group |
|||
let sender |
|||
let chat |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true } }) |
|||
if (isGroup) { |
|||
sender = await models.Contact.findOne({ where: { publicKey: sender_pub_key } }) |
|||
chat = await models.Chat.findOne({ where: { uuid: chat_uuid } }) |
|||
} else { |
|||
sender = await findOrCreateContactByPubkey(sender_pub_key) |
|||
chat = await findOrCreateChatByUUID( |
|||
chat_uuid, [parseInt(owner.id), parseInt(sender.id)] |
|||
) |
|||
} |
|||
return { owner, sender, chat, sender_pub_key, chat_uuid, amount, content, mediaToken, mediaKey, mediaType, chat_type, msg_id, chat_members, chat_name } |
|||
} |
|||
|
|||
export { |
|||
findOrCreateChat, |
|||
sendMessage, |
|||
sendContactKeys, |
|||
findOrCreateContactByPubkey, |
|||
findOrCreateChatByUUID, |
|||
sleep, |
|||
parseReceiveParams, |
|||
performKeysendMessage |
|||
} |
|||
|
|||
async function asyncForEach(array, callback) { |
|||
for (let index = 0; index < array.length; index++) { |
|||
await callback(array[index], index, array); |
|||
} |
|||
} |
|||
|
|||
function newmsg(type, chat, sender, message){ |
|||
return { |
|||
type: type, |
|||
chat: { |
|||
uuid: chat.uuid, |
|||
...chat.name && { name: chat.name }, |
|||
...chat.type && { type: chat.type }, |
|||
...chat.members && { members: chat.members }, |
|||
}, |
|||
message: message, |
|||
sender: { |
|||
pub_key: sender.publicKey, |
|||
// ...sender.contactKey && {contact_key: sender.contactKey}
|
|||
} |
|||
} |
|||
} |
|||
|
|||
function newkeyexchangemsg(type, sender){ |
|||
return { |
|||
type: type, |
|||
sender: { |
|||
pub_key: sender.publicKey, |
|||
contact_key: sender.contactKey, |
|||
...sender.alias && {alias: sender.alias}, |
|||
// ...sender.photoUrl && {photoUrl: sender.photoUrl}
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,219 @@ |
|||
import {models} from './models' |
|||
import * as fetch from 'node-fetch' |
|||
import { Op } from 'sequelize' |
|||
import * as socket from './utils/socket' |
|||
import * as jsonUtils from './utils/json' |
|||
import * as helpers from './helpers' |
|||
import {nodeinfo} from './utils/nodeinfo' |
|||
|
|||
const constants = require(__dirname + '/../config/constants.json'); |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require('../config/app.json')[env]; |
|||
|
|||
const checkInviteHub = async (params = {}) => { |
|||
if (env != "production") { |
|||
return |
|||
} |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
|
|||
//console.log('[hub] checking invites ping')
|
|||
|
|||
const inviteStrings = await models.Invite.findAll({ where: { status: { [Op.notIn]: [constants.invite_statuses.complete, constants.invite_statuses.expired] } } }).map(invite => invite.inviteString) |
|||
|
|||
fetch(config.hub_api_url + '/invites/check', { |
|||
method: 'POST' , |
|||
body: JSON.stringify({ invite_strings: inviteStrings }), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
if (json.object) { |
|||
json.object.invites.map(async object => { |
|||
const invite = object.invite |
|||
const pubkey = object.pubkey |
|||
const price = object.price |
|||
|
|||
const dbInvite = await models.Invite.findOne({ where: { inviteString: invite.pin }}) |
|||
const contact = await models.Contact.findOne({ where: { id: dbInvite.contactId } }) |
|||
|
|||
if (dbInvite.status != invite.invite_status) { |
|||
dbInvite.update({ status: invite.invite_status, price: price }) |
|||
socket.sendJson({ |
|||
type: 'invite', |
|||
response: jsonUtils.inviteToJson(dbInvite) |
|||
}) |
|||
|
|||
if (dbInvite.status == constants.invite_statuses.ready && contact) { |
|||
sendNotification(-1, contact.alias, 'invite') |
|||
} |
|||
} |
|||
|
|||
if (pubkey && dbInvite.status == constants.invite_statuses.complete && contact) { |
|||
contact.update({ publicKey: pubkey, status: constants.contact_statuses.confirmed }) |
|||
|
|||
var contactJson = jsonUtils.contactToJson(contact) |
|||
contactJson.invite = jsonUtils.inviteToJson(dbInvite) |
|||
|
|||
socket.sendJson({ |
|||
type: 'contact', |
|||
response: contactJson |
|||
}) |
|||
|
|||
helpers.sendContactKeys({ |
|||
contactIds: [contact.id], |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.log('[hub error]', error) |
|||
}) |
|||
} |
|||
|
|||
const pingHub = async (params = {}) => { |
|||
if (env != "production") { |
|||
return |
|||
} |
|||
|
|||
const node = await nodeinfo() |
|||
sendHubCall({ ...params, node }) |
|||
} |
|||
|
|||
const sendHubCall = (params) => { |
|||
// console.log('[hub] sending ping')
|
|||
fetch(config.hub_api_url + '/ping', { |
|||
method: 'POST', |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
// ?
|
|||
}) |
|||
.catch(error => { |
|||
console.log('[hub error]', error) |
|||
}) |
|||
} |
|||
|
|||
const pingHubInterval = (ms) => { |
|||
setInterval(pingHub, ms) |
|||
} |
|||
|
|||
const checkInvitesHubInterval = (ms) => { |
|||
setInterval(checkInviteHub, ms) |
|||
} |
|||
|
|||
const finishInviteInHub = (params, onSuccess, onFailure) => { |
|||
fetch(config.hub_api_url + '/invites/finish', { |
|||
method: 'POST' , |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
if (json.object) { |
|||
console.log('[hub] finished invite to hub') |
|||
onSuccess(json) |
|||
} else { |
|||
console.log('[hub] fail to finish invite in hub') |
|||
onFailure(json) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const payInviteInHub = (invite_string, params, onSuccess, onFailure) => { |
|||
fetch(config.hub_api_url + '/invites/' + invite_string + '/pay', { |
|||
method: 'POST' , |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
if (json.object) { |
|||
console.log('[hub] finished pay to hub') |
|||
onSuccess(json) |
|||
} else { |
|||
console.log('[hub] fail to pay invite in hub') |
|||
onFailure(json) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const createInviteInHub = (params, onSuccess, onFailure) => { |
|||
fetch(config.hub_api_url + '/invites', { |
|||
method: 'POST' , |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
if (json.object) { |
|||
console.log('[hub] sent invite to be created to hub') |
|||
onSuccess(json) |
|||
} else { |
|||
console.log('[hub] fail to create invite in hub') |
|||
onFailure(json) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const sendNotification = async (chat, name, type) => { |
|||
|
|||
let message = `You have a new message from ${name}` |
|||
if(type==='invite'){ |
|||
message = `Your invite to ${name} is ready` |
|||
} |
|||
if(type==='group'){ |
|||
message = `You have been added to group ${name}` |
|||
} |
|||
|
|||
if(type==='message' && chat.type==constants.chat_types.group && chat.name && chat.name.length){ |
|||
message += ` on ${chat.name}` |
|||
} |
|||
|
|||
console.log('[send notification]', { chat_id:chat.id, message }) |
|||
|
|||
if (chat.isMuted) { |
|||
console.log('[send notification] skipping. chat is muted.') |
|||
return |
|||
} |
|||
|
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
|
|||
if (!owner.deviceId) { |
|||
console.log('[send notification] skipping. owner.deviceId not set.') |
|||
return |
|||
} |
|||
|
|||
const unseenMessages = await models.Message.findAll({ where: { sender: { [Op.ne]: owner.id }, seen: false } }) |
|||
|
|||
const params = { |
|||
device_id: owner.deviceId, |
|||
notification: { |
|||
chat_id: chat.id, |
|||
message, |
|||
badge: unseenMessages.length |
|||
} |
|||
} |
|||
|
|||
fetch("http://hub.sphinx.chat/api/v1/nodes/notify", { |
|||
method: 'POST' , |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => console.log('[hub notification]', json)) |
|||
} |
|||
|
|||
export { |
|||
pingHubInterval, |
|||
checkInvitesHubInterval, |
|||
sendHubCall, |
|||
sendNotification, |
|||
createInviteInHub, |
|||
finishInviteInHub, |
|||
payInviteInHub |
|||
} |
@ -0,0 +1,16 @@ |
|||
import {Sequelize} from 'sequelize-typescript'; |
|||
|
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/config.json')[env]; |
|||
|
|||
const sequelize = new Sequelize({ |
|||
...config, |
|||
logging: process.env.SQL_LOG==='true' ? console.log : false, |
|||
models: [__dirname + '/ts'] |
|||
}) |
|||
const models = sequelize.models |
|||
|
|||
export { |
|||
sequelize, |
|||
models, |
|||
} |
@ -0,0 +1,48 @@ |
|||
import { Table, Column, Model, DataType } from 'sequelize-typescript'; |
|||
|
|||
@Table({tableName: 'sphinx_chats', underscored: true}) |
|||
export default class Chat extends Model<Chat> { |
|||
|
|||
@Column({ |
|||
type: DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}) |
|||
id: number |
|||
|
|||
@Column |
|||
uuid: string |
|||
|
|||
@Column |
|||
name: string |
|||
|
|||
@Column |
|||
photoUrl: string |
|||
|
|||
@Column(DataType.BIGINT) |
|||
type: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
status: number |
|||
|
|||
@Column |
|||
contactIds: string |
|||
|
|||
@Column |
|||
isMuted: boolean |
|||
|
|||
@Column |
|||
createdAt: Date |
|||
|
|||
@Column |
|||
updatedAt: Date |
|||
|
|||
@Column({ |
|||
type: DataType.BOOLEAN, |
|||
defaultValue: false, |
|||
allowNull: false |
|||
}) |
|||
deleted: boolean |
|||
|
|||
} |
@ -0,0 +1,57 @@ |
|||
import { Table, Column, Model, DataType } from 'sequelize-typescript'; |
|||
|
|||
@Table({tableName: 'sphinx_contacts', underscored: true}) |
|||
export default class Contact extends Model<Contact> { |
|||
|
|||
@Column({ |
|||
type: DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}) |
|||
id: number |
|||
|
|||
@Column |
|||
publicKey: string |
|||
|
|||
@Column |
|||
nodeAlias: string |
|||
|
|||
@Column |
|||
alias: string |
|||
|
|||
@Column |
|||
photoUrl: string |
|||
|
|||
@Column |
|||
isOwner: boolean |
|||
|
|||
@Column({ |
|||
type: DataType.BOOLEAN, |
|||
defaultValue: false, |
|||
allowNull: false |
|||
}) |
|||
deleted: boolean |
|||
|
|||
@Column |
|||
authToken: string |
|||
|
|||
@Column(DataType.BIGINT) |
|||
remoteId: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
status: number |
|||
|
|||
@Column(DataType.TEXT) |
|||
contactKey: string |
|||
|
|||
@Column |
|||
deviceId: string |
|||
|
|||
@Column |
|||
createdAt: Date |
|||
|
|||
@Column |
|||
updatedAt: Date |
|||
|
|||
} |
@ -0,0 +1,35 @@ |
|||
import { Table, Column, Model, DataType } from 'sequelize-typescript'; |
|||
|
|||
@Table({tableName: 'sphinx_invites', underscored: true}) |
|||
export default class Invite extends Model<Invite> { |
|||
|
|||
@Column({ |
|||
type: DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}) |
|||
id: number |
|||
|
|||
@Column |
|||
inviteString: string |
|||
|
|||
@Column |
|||
welcomeMessage: string |
|||
|
|||
@Column(DataType.BIGINT) |
|||
contactId: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
status: number |
|||
|
|||
@Column(DataType.DECIMAL(10, 2)) |
|||
price: number |
|||
|
|||
@Column |
|||
createdAt: Date |
|||
|
|||
@Column |
|||
updatedAt: Date |
|||
|
|||
} |
@ -0,0 +1,39 @@ |
|||
import { Table, Column, Model, DataType } from 'sequelize-typescript'; |
|||
|
|||
/* |
|||
Used for media uploads. When you upload a file, |
|||
also upload the symetric key encrypted for each chat member. |
|||
When they buy the file, they can retrieve the key from here. |
|||
|
|||
"received" media keys are not stored here, only in Message |
|||
*/ |
|||
|
|||
@Table({tableName: 'sphinx_media_keys', underscored: true}) |
|||
export default class MediaKey extends Model<MediaKey> { |
|||
|
|||
@Column({ |
|||
type: DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}) |
|||
id: number |
|||
|
|||
@Column |
|||
muid: string |
|||
|
|||
@Column(DataType.BIGINT) |
|||
chatId: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
receiver: number |
|||
|
|||
@Column |
|||
key: string |
|||
|
|||
@Column(DataType.BIGINT) |
|||
messageId: number |
|||
|
|||
@Column |
|||
createdAt: Date |
|||
} |
@ -0,0 +1,89 @@ |
|||
import { Table, Column, Model, DataType } from 'sequelize-typescript'; |
|||
|
|||
@Table({tableName: 'sphinx_messages', underscored: true}) |
|||
export default class Message extends Model<Message> { |
|||
|
|||
@Column({ |
|||
type: DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}) |
|||
id: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
chatId: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
type: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
sender: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
receiver: number |
|||
|
|||
@Column(DataType.DECIMAL) |
|||
amount: number |
|||
|
|||
@Column(DataType.DECIMAL) |
|||
amountMsat: number |
|||
|
|||
@Column |
|||
paymentHash: string |
|||
|
|||
@Column(DataType.TEXT) |
|||
paymentRequest: string |
|||
|
|||
@Column |
|||
date: Date |
|||
|
|||
@Column |
|||
expirationDate: Date |
|||
|
|||
@Column(DataType.TEXT) |
|||
messageContent: string |
|||
|
|||
@Column(DataType.TEXT) |
|||
remoteMessageContent: string |
|||
|
|||
@Column(DataType.BIGINT) |
|||
status: number |
|||
|
|||
@Column(DataType.TEXT) |
|||
statusMap: string |
|||
|
|||
@Column(DataType.BIGINT) |
|||
parentId: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
subscriptionId: number |
|||
|
|||
@Column |
|||
mediaTerms: string |
|||
|
|||
@Column |
|||
receipt: string |
|||
|
|||
@Column |
|||
mediaKey: string |
|||
|
|||
@Column |
|||
mediaType: string |
|||
|
|||
@Column |
|||
mediaToken: string |
|||
|
|||
@Column({ |
|||
type: DataType.BOOLEAN, |
|||
defaultValue: false, |
|||
allowNull: false |
|||
}) |
|||
seen: boolean |
|||
|
|||
@Column |
|||
createdAt: Date |
|||
|
|||
@Column |
|||
updatedAt: Date |
|||
} |
@ -0,0 +1,49 @@ |
|||
import { Table, Column, Model, DataType } from 'sequelize-typescript'; |
|||
|
|||
@Table({tableName: 'sphinx_subscriptions', underscored: true}) |
|||
export default class Subscription extends Model<Subscription> { |
|||
|
|||
@Column({ |
|||
type: DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}) |
|||
id: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
chatId: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
contactId: number |
|||
|
|||
@Column(DataType.TEXT) |
|||
cron: string |
|||
|
|||
@Column(DataType.DECIMAL) |
|||
amount: number |
|||
|
|||
@Column(DataType.DECIMAL) |
|||
totalPaid: number |
|||
|
|||
@Column(DataType.BIGINT) |
|||
endNumber: number |
|||
|
|||
@Column |
|||
endDate: Date |
|||
|
|||
@Column(DataType.BIGINT) |
|||
count: number |
|||
|
|||
@Column |
|||
ended: boolean |
|||
|
|||
@Column |
|||
paused: boolean |
|||
|
|||
@Column |
|||
createdAt: Date |
|||
|
|||
@Column |
|||
updatedAt: Date |
|||
} |
@ -0,0 +1,27 @@ |
|||
import * as changeCase from "change-case"; |
|||
|
|||
const dateKeys = ['date','createdAt','updatedAt','created_at','updated_at'] |
|||
|
|||
function toSnake(obj) { |
|||
const ret: {[k: string]: any} = {} |
|||
for (let [key, value] of Object.entries(obj)) { |
|||
if(dateKeys.includes(key) && value){ |
|||
const v: any = value |
|||
const d = new Date(v) |
|||
ret[changeCase.snakeCase(key)] = d.toISOString() |
|||
} else { |
|||
ret[changeCase.snakeCase(key)] = value |
|||
} |
|||
} |
|||
return ret |
|||
} |
|||
|
|||
function toCamel(obj) { |
|||
const ret: {[k: string]: any} = {} |
|||
for (let [key, value] of Object.entries(obj)) { |
|||
ret[changeCase.camelCase(key)] = value |
|||
} |
|||
return ret |
|||
} |
|||
|
|||
export {toSnake, toCamel} |
@ -0,0 +1,49 @@ |
|||
import * as parser from 'cron-parser' |
|||
|
|||
function daily() { |
|||
const now = new Date() |
|||
const minute = now.getMinutes() |
|||
const hour = now.getHours() |
|||
return `${minute} ${hour} * * *` |
|||
} |
|||
|
|||
function weekly() { |
|||
const now = new Date() |
|||
const minute = now.getMinutes() |
|||
const hour = now.getHours() |
|||
const dayOfWeek = now.getDay() |
|||
return `${minute} ${hour} * * ${dayOfWeek}` |
|||
} |
|||
|
|||
function monthly() { |
|||
const now = new Date() |
|||
const minute = now.getMinutes() |
|||
const hour = now.getHours() |
|||
const dayOfMonth = now.getDate() |
|||
return `${minute} ${hour} ${dayOfMonth} * *` |
|||
} |
|||
|
|||
function parse(s) { |
|||
var interval = parser.parseExpression(s); |
|||
const next = interval.next().toString() |
|||
|
|||
if(s.endsWith(' * * *')) { |
|||
return {interval: 'daily', next, ms:86400000} |
|||
} |
|||
if(s.endsWith(' * *')) { |
|||
return {interval: 'monthly', next, ms:86400000*30} |
|||
} |
|||
return {interval: 'weekly', next, ms:86400000*7} |
|||
} |
|||
|
|||
function make(interval) { |
|||
if(interval==='daily') return daily() |
|||
if(interval==='weekly') return weekly() |
|||
if(interval==='monthly') return monthly() |
|||
} |
|||
|
|||
export { |
|||
parse, |
|||
make, |
|||
} |
|||
|
@ -0,0 +1,312 @@ |
|||
const bech32CharValues = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; |
|||
|
|||
module.exports = { |
|||
decode: function(paymentRequest) { |
|||
let input = paymentRequest.toLowerCase(); |
|||
let splitPosition = input.lastIndexOf('1'); |
|||
let humanReadablePart = input.substring(0, splitPosition); |
|||
let data = input.substring(splitPosition + 1, input.length - 6); |
|||
let checksum = input.substring(input.length - 6, input.length); |
|||
|
|||
if (!this.verify_checksum(humanReadablePart, this.bech32ToFiveBitArray(data + checksum))) { |
|||
return 'error'; |
|||
} |
|||
|
|||
return { |
|||
'human_readable_part': this.decodeHumanReadablePart(humanReadablePart), |
|||
'data': this.decodeData(data, humanReadablePart), |
|||
'checksum': checksum |
|||
} |
|||
}, |
|||
|
|||
decodeHumanReadablePart: function(humanReadablePart) { |
|||
let prefixes = ['lnbc', 'lntb', 'lnbcrt']; |
|||
let prefix; |
|||
prefixes.forEach(value => { |
|||
if (humanReadablePart.substring(0, value.length) === value) { |
|||
prefix = value; |
|||
} |
|||
}); |
|||
if (prefix == null) return 'error'; // A reader MUST fail if it does not understand the prefix.
|
|||
let amount = this.decodeAmount(humanReadablePart.substring(prefix.length, humanReadablePart.length)); |
|||
|
|||
return { |
|||
'prefix': prefix, |
|||
'amount': amount |
|||
} |
|||
}, |
|||
|
|||
decodeData: function(data, humanReadablePart) { |
|||
let date32 = data.substring(0, 7); |
|||
let dateEpoch = this.bech32ToInt(date32); |
|||
let signature = data.substring(data.length - 104, data.length); |
|||
let tagData = data.substring(7, data.length - 104); |
|||
let decodedTags = this.decodeTags(tagData); |
|||
let value = this.bech32ToFiveBitArray(date32 + tagData); |
|||
value = this.fiveBitArrayTo8BitArray(value, true); |
|||
value = this.textToHexString(humanReadablePart).concat(this.byteArrayToHexString(value)); |
|||
|
|||
return { |
|||
'time_stamp': dateEpoch, |
|||
'tags': decodedTags, |
|||
'signature': this.decodeSignature(signature), |
|||
'signing_data': value |
|||
} |
|||
}, |
|||
|
|||
decodeSignature: function(signature) { |
|||
let data = this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(signature)); |
|||
let recoveryFlag = data[data.length - 1]; |
|||
let r = this.byteArrayToHexString(data.slice(0, 32)); |
|||
let s = this.byteArrayToHexString(data.slice(32, data.length - 1)); |
|||
return { |
|||
'r': r, |
|||
's': s, |
|||
'recovery_flag': recoveryFlag |
|||
} |
|||
}, |
|||
|
|||
decodeAmount: function(str) { |
|||
let multiplier = str.charAt(str.length - 1); |
|||
let amount = str.substring(0, str.length - 1); |
|||
if (amount.substring(0, 1) === '0') { |
|||
return 'error'; |
|||
} |
|||
amount = Number(amount); |
|||
if (amount < 0 || !Number.isInteger(amount)) { |
|||
return 'error'; |
|||
} |
|||
|
|||
switch (multiplier) { |
|||
case '': |
|||
return 'Any amount'; // A reader SHOULD indicate if amount is unspecified
|
|||
case 'p': |
|||
return amount / 10; |
|||
case 'n': |
|||
return amount * 100; |
|||
case 'u': |
|||
return amount * 100000; |
|||
case 'm': |
|||
return amount * 100000000; |
|||
default: |
|||
// A reader SHOULD fail if amount is followed by anything except a defined multiplier.
|
|||
return 'error'; |
|||
} |
|||
}, |
|||
|
|||
decodeTags: function(tagData) { |
|||
let tags = this.extractTags(tagData); |
|||
let decodedTags = []; |
|||
tags.forEach(value => decodedTags.push(this.decodeTag(value.type, value.length, value.data))); |
|||
return decodedTags; |
|||
}, |
|||
|
|||
extractTags: function(str) { |
|||
let tags = []; |
|||
while (str.length > 0) { |
|||
let type = str.charAt(0); |
|||
let dataLength = this.bech32ToInt(str.substring(1, 3)); |
|||
let data = str.substring(3, dataLength + 3); |
|||
tags.push({ |
|||
'type': type, |
|||
'length': dataLength, |
|||
'data': data |
|||
}); |
|||
str = str.substring(3 + dataLength, str.length); |
|||
} |
|||
return tags; |
|||
}, |
|||
|
|||
decodeTag: function(type, length, data) { |
|||
switch (type) { |
|||
case 'p': |
|||
if (length !== 52) break; // A reader MUST skip over a 'p' field that does not have data_length 52
|
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'payment_hash', |
|||
'value': this.byteArrayToHexString(this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data))) |
|||
}; |
|||
case 'd': |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'description', |
|||
'value': this.bech32ToUTF8String(data) |
|||
}; |
|||
case 'n': |
|||
if (length !== 53) break; // A reader MUST skip over a 'n' field that does not have data_length 53
|
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'payee_public_key', |
|||
'value': this.byteArrayToHexString(this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data))) |
|||
}; |
|||
case 'h': |
|||
if (length !== 52) break; // A reader MUST skip over a 'h' field that does not have data_length 52
|
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'description_hash', |
|||
'value': data |
|||
}; |
|||
case 'x': |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'expiry', |
|||
'value': this.bech32ToInt(data) |
|||
}; |
|||
case 'c': |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'min_final_cltv_expiry', |
|||
'value': this.bech32ToInt(data) |
|||
}; |
|||
case 'f': |
|||
let version = this.bech32ToFiveBitArray(data.charAt(0))[0]; |
|||
if (version < 0 || version > 18) break; // a reader MUST skip over an f field with unknown version.
|
|||
data = data.substring(1, data.length); |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'fallback_address', |
|||
'value': { |
|||
'version': version, |
|||
'fallback_address': data |
|||
} |
|||
}; |
|||
case 'r': |
|||
data = this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data)); |
|||
let pubkey = data.slice(0, 33); |
|||
let shortChannelId = data.slice(33, 41); |
|||
let feeBaseMsat = data.slice(41, 45); |
|||
let feeProportionalMillionths = data.slice(45, 49); |
|||
let cltvExpiryDelta = data.slice(49, 51); |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'routing_information', |
|||
'value': { |
|||
'public_key': this.byteArrayToHexString(pubkey), |
|||
'short_channel_id': this.byteArrayToHexString(shortChannelId), |
|||
'fee_base_msat': this.byteArrayToInt(feeBaseMsat), |
|||
'fee_proportional_millionths': this.byteArrayToInt(feeProportionalMillionths), |
|||
'cltv_expiry_delta': this.byteArrayToInt(cltvExpiryDelta) |
|||
} |
|||
}; |
|||
default: |
|||
// reader MUST skip over unknown fields
|
|||
} |
|||
}, |
|||
|
|||
polymod: function(values) { |
|||
let GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; |
|||
let chk = 1; |
|||
values.forEach((value) => { |
|||
let b = (chk >> 25); |
|||
chk = (chk & 0x1ffffff) << 5 ^ value; |
|||
for (let i = 0; i < 5; i++) { |
|||
if (((b >> i) & 1) === 1) { |
|||
chk ^= GEN[i]; |
|||
} else { |
|||
chk ^= 0; |
|||
} |
|||
} |
|||
}); |
|||
return chk; |
|||
}, |
|||
|
|||
expand: function(str) { |
|||
let array = []; |
|||
for (let i = 0; i < str.length; i++) { |
|||
array.push(str.charCodeAt(i) >> 5); |
|||
} |
|||
array.push(0); |
|||
for (let i = 0; i < str.length; i++) { |
|||
array.push(str.charCodeAt(i) & 31); |
|||
} |
|||
return array; |
|||
}, |
|||
|
|||
verify_checksum: function(hrp, data) { |
|||
hrp = this.expand(hrp); |
|||
let all = hrp.concat(data); |
|||
let bool = this.polymod(all); |
|||
return bool === 1; |
|||
}, |
|||
|
|||
byteArrayToInt: function(byteArray) { |
|||
let value = 0; |
|||
for (let i = 0; i < byteArray.length; ++i) { |
|||
value = (value << 8) + byteArray[i]; |
|||
} |
|||
return value; |
|||
}, |
|||
|
|||
bech32ToInt: function(str) { |
|||
let sum = 0; |
|||
for (let i = 0; i < str.length; i++) { |
|||
sum = sum * 32; |
|||
sum = sum + bech32CharValues.indexOf(str.charAt(i)); |
|||
} |
|||
return sum; |
|||
}, |
|||
|
|||
bech32ToFiveBitArray: function(str) { |
|||
let array = []; |
|||
for (let i = 0; i < str.length; i++) { |
|||
array.push(bech32CharValues.indexOf(str.charAt(i))); |
|||
} |
|||
return array; |
|||
}, |
|||
|
|||
fiveBitArrayTo8BitArray: function(int5Array, includeOverflow) { |
|||
let count = 0; |
|||
let buffer = 0; |
|||
let byteArray = []; |
|||
int5Array.forEach((value) => { |
|||
buffer = (buffer << 5) + value; |
|||
count += 5; |
|||
if (count >= 8) { |
|||
byteArray.push(buffer >> (count - 8) & 255); |
|||
count -= 8; |
|||
} |
|||
}); |
|||
if (includeOverflow && count > 0) { |
|||
byteArray.push(buffer << (8 - count) & 255); |
|||
} |
|||
return byteArray; |
|||
}, |
|||
|
|||
bech32ToUTF8String: function(str) { |
|||
let int5Array = this.bech32ToFiveBitArray(str); |
|||
let byteArray = this.fiveBitArrayTo8BitArray(int5Array); |
|||
|
|||
let utf8String = ''; |
|||
for (let i = 0; i < byteArray.length; i++) { |
|||
utf8String += '%' + ('0' + byteArray[i].toString(16)).slice(-2); |
|||
} |
|||
return decodeURIComponent(utf8String); |
|||
}, |
|||
|
|||
byteArrayToHexString: function(byteArray) { |
|||
return Array.prototype.map.call(byteArray, function (byte) { |
|||
return ('0' + (byte & 0xFF).toString(16)).slice(-2); |
|||
}).join(''); |
|||
}, |
|||
|
|||
textToHexString: function(text) { |
|||
let hexString = ''; |
|||
for (let i = 0; i < text.length; i++) { |
|||
hexString += text.charCodeAt(i).toString(16); |
|||
} |
|||
return hexString; |
|||
}, |
|||
|
|||
epochToDate: function(int) { |
|||
let date = new Date(int * 1000); |
|||
return date.toUTCString(); |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
import { exec } from 'child_process' |
|||
|
|||
let commitHash |
|||
function checkCommitHash(){ |
|||
return new Promise((resolve, reject)=>{ |
|||
if(commitHash) { |
|||
return resolve(commitHash) |
|||
} |
|||
try{ |
|||
exec(`git log -1 --pretty=format:%h`, {timeout:999}, (error, stdout, stderr) => { |
|||
if(stdout){ |
|||
commitHash = stdout.trim() |
|||
return resolve(commitHash) |
|||
} else { |
|||
resolve('') |
|||
} |
|||
}) |
|||
} catch(e) { |
|||
console.log(e) |
|||
resolve('') |
|||
} |
|||
}) |
|||
} |
|||
|
|||
let tag |
|||
function checkTag(){ |
|||
return new Promise((resolve, reject)=>{ |
|||
if(tag) { |
|||
return resolve(tag) |
|||
} |
|||
try{ |
|||
exec(`git describe --abbrev=0 --tags`, {timeout:999}, (error, stdout, stderr) => { |
|||
if(stdout){ |
|||
tag = stdout.trim() |
|||
return resolve(tag) |
|||
} else { |
|||
resolve('') |
|||
} |
|||
}) |
|||
} catch(e) { |
|||
console.log(e) |
|||
resolve('') |
|||
} |
|||
}) |
|||
} |
|||
|
|||
export { |
|||
checkCommitHash, checkTag |
|||
} |
@ -0,0 +1,53 @@ |
|||
import {toSnake,toCamel} from '../utils/case' |
|||
import * as cronUtils from './cron' |
|||
|
|||
function chatToJson(c) { |
|||
const chat = c.dataValues||c |
|||
let contactIds = chat.contactIds || null |
|||
if(chat.contactIds && typeof chat.contactIds==='string'){ |
|||
contactIds = JSON.parse(chat.contactIds) |
|||
} |
|||
return toSnake({ |
|||
...chat, |
|||
contactIds |
|||
}) |
|||
} |
|||
|
|||
function messageToJson(msg, chat = null) { |
|||
const message = msg.dataValues||msg |
|||
let statusMap = message.statusMap || null |
|||
if(message.statusMap && typeof message.statusMap==='string'){ |
|||
statusMap = JSON.parse(message.statusMap) |
|||
} |
|||
return toSnake({ |
|||
...message, |
|||
statusMap, |
|||
chat: chat ? chatToJson(chat) : null, |
|||
}) |
|||
} |
|||
|
|||
const contactToJson = (contact) => toSnake(contact.dataValues||contact) |
|||
|
|||
const inviteToJson = (invite) => toSnake(invite.dataValues||invite) |
|||
|
|||
const jsonToContact = (json) => toCamel(json) |
|||
|
|||
function subscriptionToJson(subscription, chat) { |
|||
const sub = subscription.dataValues || subscription |
|||
const { interval, next } = cronUtils.parse(sub.cron) |
|||
return toSnake({ |
|||
...sub, |
|||
interval, |
|||
next, |
|||
chat: chat ? chatToJson(chat) : null, |
|||
}) |
|||
} |
|||
|
|||
export { |
|||
messageToJson, |
|||
contactToJson, |
|||
inviteToJson, |
|||
jsonToContact, |
|||
chatToJson, |
|||
subscriptionToJson, |
|||
} |
@ -0,0 +1,153 @@ |
|||
import * as zbase32 from './zbase32' |
|||
import {signBuffer} from './lightning' |
|||
|
|||
const env = process.env.NODE_ENV || 'development' |
|||
const config = require(__dirname + '/../../config/app.json')[env] |
|||
|
|||
/* |
|||
Lightning Data Access Token |
|||
Base64 strings separated by dots: |
|||
{host}.{muid}.{buyerPubKey}.{exp}.{metadata}.{signature} |
|||
|
|||
- host: web host for data (ascii->base64) |
|||
- muid: ID of media |
|||
- buyerPubKey |
|||
- exp: unix timestamp expiration (encoded into 4 bytes) |
|||
- meta: key/value pairs, url query encoded (alphabetically ordered, ascii->base64) |
|||
- signature of all that (concatenated bytes of each) |
|||
*/ |
|||
|
|||
async function tokenFromTerms({host,muid,ttl,pubkey,meta}){ |
|||
const theHost = host || config.media_host || '' |
|||
|
|||
const pubkeyBytes = Buffer.from(pubkey, 'hex') |
|||
const pubkey64 = urlBase64FromBytes(pubkeyBytes) |
|||
|
|||
const now = Math.floor(Date.now()/1000) |
|||
const exp = ttl ? now + (60*60*24*365) : 0 |
|||
|
|||
const ldat = startLDAT(theHost,muid,pubkey64,exp,meta) |
|||
if(pubkey!=''){ |
|||
const sig = await signBuffer(ldat.bytes) |
|||
const sigBytes = zbase32.decode(sig) |
|||
return ldat.terms + "." + urlBase64FromBytes(sigBytes) |
|||
} else { |
|||
return ldat.terms |
|||
} |
|||
} |
|||
|
|||
// host.muid.pk.exp.meta
|
|||
function startLDAT(host:string,muid:string,pk:string,exp:number,meta:{[k:string]:any}={}){ |
|||
const empty = Buffer.from([]) |
|||
var hostBuf = Buffer.from(host, 'ascii') |
|||
var muidBuf = Buffer.from(muid, 'base64') |
|||
var pkBuf = pk ? Buffer.from(pk, 'base64') : empty |
|||
var expBuf = exp ? Buffer.from(exp.toString(16), 'hex') : empty |
|||
var metaBuf = meta ? Buffer.from(serializeMeta(meta), 'ascii') : empty |
|||
|
|||
const totalLength = hostBuf.length + muidBuf.length + pkBuf.length + expBuf.length + metaBuf.length |
|||
const buf = Buffer.concat([hostBuf, muidBuf, pkBuf, expBuf, metaBuf], totalLength) |
|||
let terms = `${urlBase64(hostBuf)}.${urlBase64(muidBuf)}.${urlBase64(pkBuf)}.${urlBase64(expBuf)}.${urlBase64(metaBuf)}` |
|||
return {terms, bytes: buf} |
|||
} |
|||
|
|||
const termKeys = [{ |
|||
key:'host', |
|||
func: buf=> buf.toString('ascii') |
|||
},{ |
|||
key:'muid', |
|||
func: buf=> urlBase64(buf) |
|||
},{ |
|||
key:'pubkey', |
|||
func: buf=> buf.toString('hex') |
|||
},{ |
|||
key:'ts', |
|||
func: buf=> parseInt('0x' + buf.toString('hex')) |
|||
},{ |
|||
key:'meta', |
|||
func: buf=> { |
|||
const ascii = buf.toString('ascii') |
|||
return ascii?deserializeMeta(ascii):{} // parse this
|
|||
} |
|||
},{ |
|||
key:'sig', |
|||
func: buf=> urlBase64(buf) |
|||
}] |
|||
|
|||
function parseLDAT(ldat){ |
|||
const a = ldat.split('.') |
|||
const o: {[k:string]:any} = {} |
|||
termKeys.forEach((t,i)=>{ |
|||
if(a[i]) o[t.key] = t.func(Buffer.from(a[i], 'base64')) |
|||
}) |
|||
return o |
|||
} |
|||
|
|||
export { |
|||
startLDAT, parseLDAT, tokenFromTerms, |
|||
urlBase64, urlBase64FromAscii, |
|||
urlBase64FromBytes, testLDAT |
|||
} |
|||
|
|||
async function testLDAT(){ |
|||
console.log('testLDAT') |
|||
const terms = { |
|||
host:'', |
|||
ttl:31536000, //one year
|
|||
muid:'qFSOa50yWeGSG8oelsMvctLYdejPRD090dsypBSx_xg=', |
|||
pubkey:'0373ca36a331d8fd847f190908715a34997b15dc3c5d560ca032cf3412fcf494e4', |
|||
meta:{ |
|||
amt:100, |
|||
ttl:31536000, |
|||
dim:'1500x1300' |
|||
} |
|||
} |
|||
const token = await tokenFromTerms(terms) |
|||
console.log(token) |
|||
|
|||
const terms2 = { |
|||
host:'', |
|||
ttl:0, //one year
|
|||
muid:'qFSOa50yWeGSG8oelsMvctLYdejPRD090dsypBSx_xg=', |
|||
pubkey:'', |
|||
meta:{ |
|||
amt:100, |
|||
ttl:31536000, |
|||
} |
|||
} |
|||
const token2 = await tokenFromTerms(terms2) |
|||
console.log(token2) |
|||
|
|||
console.log(parseLDAT(token2)) |
|||
} |
|||
|
|||
function serializeMeta(obj) { |
|||
var str: string[] = [] |
|||
for (var p in obj) { |
|||
if (obj.hasOwnProperty(p)) { |
|||
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); |
|||
} |
|||
} |
|||
str.sort((a,b)=>(a > b ? 1 : -1)) |
|||
return str.join("&"); |
|||
} |
|||
|
|||
function deserializeMeta(str){ |
|||
const json = str && str.length>2 ? JSON.parse('{"' + str.replace(/&/g, '","').replace(/=/g,'":"') + '"}', function(key, value) { return key===""?value:decodeURIComponent(value) }) : {} |
|||
const ret = {} |
|||
for (let [k, v] of Object.entries(json)) { |
|||
const value = (typeof v==='string' && parseInt(v)) || v |
|||
ret[k] = value |
|||
} |
|||
return ret |
|||
} |
|||
|
|||
function urlBase64(buf){ |
|||
return buf.toString('base64').replace(/\//g, '_').replace(/\+/g, '-') |
|||
} |
|||
function urlBase64FromBytes(buf){ |
|||
return Buffer.from(buf).toString('base64').replace(/\//g, '_').replace(/\+/g, '-') |
|||
} |
|||
function urlBase64FromAscii(ascii){ |
|||
return Buffer.from(ascii,'ascii').toString('base64').replace(/\//g, '_').replace(/\+/g, '-') |
|||
} |
@ -0,0 +1,321 @@ |
|||
import * as ByteBuffer from 'bytebuffer' |
|||
import * as fs from 'fs' |
|||
import * as grpc from 'grpc' |
|||
import { sleep } from '../helpers'; |
|||
import * as sha from 'js-sha256' |
|||
import * as crypto from 'crypto' |
|||
// var protoLoader = require('@grpc/proto-loader')
|
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/app.json')[env]; |
|||
|
|||
const LND_KEYSEND_KEY = 5482373484 |
|||
const SPHINX_CUSTOM_RECORD_KEY = 133773310 |
|||
|
|||
var lightningClient = <any> null; |
|||
var walletUnlocker = <any> null; |
|||
|
|||
const loadCredentials = () => { |
|||
var lndCert = fs.readFileSync(config.tls_location); |
|||
var sslCreds = grpc.credentials.createSsl(lndCert); |
|||
var m = fs.readFileSync(config.macaroon_location); |
|||
var macaroon = m.toString('hex'); |
|||
var metadata = new grpc.Metadata() |
|||
metadata.add('macaroon', macaroon) |
|||
var macaroonCreds = grpc.credentials.createFromMetadataGenerator((_args, callback) => { |
|||
callback(null, metadata); |
|||
}); |
|||
|
|||
return grpc.credentials.combineChannelCredentials(sslCreds, macaroonCreds); |
|||
} |
|||
|
|||
// async function loadLightningNew() {
|
|||
// if (lightningClient) {
|
|||
// return lightningClient
|
|||
// } else {
|
|||
// var credentials = loadCredentials()
|
|||
// const packageDefinition = await protoLoader.load("rpc.proto", {})
|
|||
// const lnrpcDescriptor = grpc.loadPackageDefinition(packageDefinition);
|
|||
// var { lnrpc } = lnrpcDescriptor;
|
|||
// lightningClient = new lnrpc.Lightning(config.node_ip + ':' + config.lnd_port, credentials);
|
|||
// return lightningClient
|
|||
// }
|
|||
// }
|
|||
|
|||
const loadLightning = () => { |
|||
if (lightningClient) { |
|||
return lightningClient |
|||
} else { |
|||
try{ |
|||
var credentials = loadCredentials() |
|||
var lnrpcDescriptor = grpc.load("rpc.proto"); |
|||
var lnrpc: any = lnrpcDescriptor.lnrpc |
|||
lightningClient = new lnrpc.Lightning(config.node_ip + ':' + config.lnd_port, credentials); |
|||
return lightningClient |
|||
} catch(e) { |
|||
throw e |
|||
} |
|||
} |
|||
} |
|||
|
|||
const loadWalletUnlocker = () => { |
|||
if (walletUnlocker) { |
|||
return walletUnlocker |
|||
} else { |
|||
var credentials = loadCredentials() |
|||
try{ |
|||
var lnrpcDescriptor = grpc.load("rpc.proto"); |
|||
var lnrpc: any = lnrpcDescriptor.lnrpc |
|||
walletUnlocker = new lnrpc.WalletUnlocker(config.node_ip + ':' + config.lnd_port, credentials); |
|||
return walletUnlocker |
|||
} catch(e) { |
|||
console.log(e) |
|||
} |
|||
} |
|||
} |
|||
|
|||
const getHeaders = (req) => { |
|||
return { |
|||
"X-User-Token": req.headers['x-user-token'], |
|||
"X-User-Email": req.headers['x-user-email'] |
|||
} |
|||
} |
|||
|
|||
var isLocked = false |
|||
let lockTimeout: ReturnType<typeof setTimeout>; |
|||
const getLock = () => isLocked |
|||
const setLock = (value) => { |
|||
isLocked = value |
|||
console.log({ isLocked }) |
|||
if (lockTimeout) clearTimeout(lockTimeout) |
|||
lockTimeout = setTimeout(() => { |
|||
isLocked = false |
|||
console.log({ isLocked }) |
|||
}, 1000 * 60 * 2) |
|||
} |
|||
|
|||
const getRoute = async (pub_key, amt, callback) => { |
|||
let lightning = await loadLightning() |
|||
lightning.queryRoutes( |
|||
{ pub_key, amt }, |
|||
(err, response) => callback(err, response) |
|||
) |
|||
} |
|||
|
|||
const keysend = (opts) => { |
|||
return new Promise(async function(resolve, reject) { |
|||
let lightning = await loadLightning() |
|||
|
|||
const randoStr = crypto.randomBytes(32).toString('hex'); |
|||
const preimage = ByteBuffer.fromHex(randoStr) |
|||
const options = { |
|||
amt: opts.amt, |
|||
final_cltv_delta: 10, |
|||
dest: ByteBuffer.fromHex(opts.dest), |
|||
dest_custom_records: { |
|||
[`${LND_KEYSEND_KEY}`]: preimage, |
|||
[`${SPHINX_CUSTOM_RECORD_KEY}`]: ByteBuffer.fromUTF8(opts.data), |
|||
}, |
|||
payment_hash: sha.sha256.arrayBuffer(preimage.toBuffer()), |
|||
dest_features:[9], |
|||
} |
|||
const call = lightning.sendPayment() |
|||
call.on('data', function(payment) { |
|||
if(payment.payment_error){ |
|||
reject(payment.payment_error) |
|||
} else { |
|||
resolve(payment) |
|||
} |
|||
}) |
|||
call.on('error', function(err) { |
|||
reject(err) |
|||
}) |
|||
call.write(options) |
|||
}) |
|||
} |
|||
|
|||
const MAX_MSG_LENGTH = 972 // 1146 - 20
|
|||
async function keysendMessage(opts) { |
|||
return new Promise(async function(resolve, reject) { |
|||
if(!opts.data || typeof opts.data!=='string') { |
|||
return reject('string plz') |
|||
} |
|||
if(opts.data.length<MAX_MSG_LENGTH){ |
|||
try { |
|||
const res = await keysend(opts) |
|||
resolve(res) |
|||
} catch(e) { |
|||
reject(e) |
|||
} |
|||
return |
|||
} |
|||
// too long! need to send serial
|
|||
const n = Math.ceil(opts.data.length / MAX_MSG_LENGTH) |
|||
let success = false |
|||
let fail = false |
|||
let res:any = null |
|||
const ts = new Date().valueOf() |
|||
await asyncForEach(Array.from(Array(n)), async(u,i)=> { |
|||
const spliti = Math.ceil(opts.data.length/n) |
|||
const m = opts.data.substr(i*spliti, spliti) |
|||
try { |
|||
res = await keysend({...opts, |
|||
data: `${ts}_${i}_${n}_${m}` |
|||
}) |
|||
success = true |
|||
await sleep(432) |
|||
} catch(e) { |
|||
console.log(e) |
|||
fail = true |
|||
} |
|||
}) |
|||
if(success && !fail) { |
|||
resolve(res) |
|||
} else { |
|||
reject(new Error('fail')) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
async function asyncForEach(array, callback) { |
|||
for (let index = 0; index < array.length; index++) { |
|||
await callback(array[index], index, array); |
|||
} |
|||
} |
|||
|
|||
async function signAscii(ascii) { |
|||
try { |
|||
const sig = await signMessage(ascii_to_hexa(ascii)) |
|||
return sig |
|||
} catch(e) { |
|||
throw e |
|||
} |
|||
} |
|||
|
|||
function listInvoices() { |
|||
return new Promise(async(resolve, reject)=> { |
|||
const lightning = await loadLightning() |
|||
lightning.listInvoices({ |
|||
num_max_invoices:100000, |
|||
reversed:true, |
|||
}, (err, response) => { |
|||
if(!err) { |
|||
resolve(response) |
|||
} else { |
|||
reject(err) |
|||
} |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
function listPayments() { |
|||
return new Promise(async(resolve, reject)=> { |
|||
const lightning = await loadLightning() |
|||
lightning.listPayments({}, (err, response) => { |
|||
if(!err) { |
|||
resolve(response) |
|||
} else { |
|||
reject(err) |
|||
} |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
const signMessage = (msg) => { |
|||
return new Promise(async(resolve, reject)=> { |
|||
let lightning = await loadLightning() |
|||
try { |
|||
const options = {msg:ByteBuffer.fromHex(msg)} |
|||
lightning.signMessage(options, function(err,sig){ |
|||
if(err || !sig.signature) { |
|||
reject(err) |
|||
} else { |
|||
resolve(sig.signature) |
|||
} |
|||
}) |
|||
} catch(e) { |
|||
reject(e) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const signBuffer = (msg) => { |
|||
return new Promise(async (resolve, reject)=> { |
|||
let lightning = await loadLightning() |
|||
try { |
|||
const options = {msg} |
|||
lightning.signMessage(options, function(err,sig){ |
|||
if(err || !sig.signature) { |
|||
reject(err) |
|||
} else { |
|||
resolve(sig.signature) |
|||
} |
|||
}) |
|||
} catch(e) { |
|||
reject(e) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const verifyMessage = (msg,sig) => { |
|||
return new Promise(async(resolve, reject)=> { |
|||
let lightning = await loadLightning() |
|||
try { |
|||
const options = { |
|||
msg:ByteBuffer.fromHex(msg), |
|||
signature:sig, |
|||
} |
|||
console.log(options) |
|||
lightning.verifyMessage(options, function(err,res){ |
|||
if(err || !res.pubkey) { |
|||
reject(err) |
|||
} else { |
|||
resolve(res) |
|||
} |
|||
}) |
|||
} catch(e) { |
|||
reject(e) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
async function checkConnection(){ |
|||
return new Promise((resolve,reject)=>{ |
|||
const lightning = loadLightning() |
|||
lightning.getInfo({}, function(err, response) { |
|||
if (err == null) { |
|||
resolve(response) |
|||
} else { |
|||
reject(err) |
|||
} |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
function ascii_to_hexa(str){ |
|||
var arr1 = <string[]> []; |
|||
for (var n = 0, l = str.length; n < l; n ++) { |
|||
var hex = Number(str.charCodeAt(n)).toString(16); |
|||
arr1.push(hex); |
|||
} |
|||
return arr1.join(''); |
|||
} |
|||
|
|||
export { |
|||
loadCredentials, |
|||
loadLightning, |
|||
loadWalletUnlocker, |
|||
getHeaders, |
|||
getLock, |
|||
setLock, |
|||
getRoute, |
|||
keysendMessage, |
|||
signMessage, |
|||
verifyMessage, |
|||
signAscii, |
|||
signBuffer, |
|||
LND_KEYSEND_KEY, |
|||
SPHINX_CUSTOM_RECORD_KEY, |
|||
listInvoices, |
|||
listPayments, |
|||
checkConnection, |
|||
} |
@ -0,0 +1,5 @@ |
|||
import * as AsyncLock from 'async-lock' |
|||
|
|||
const lock = new AsyncLock() |
|||
|
|||
export default lock |
@ -0,0 +1,28 @@ |
|||
import * as expressWinston from 'express-winston' |
|||
import * as winston from 'winston' |
|||
import * as moment from 'moment' |
|||
|
|||
const tsFormat = (ts) => moment(ts).format('YYYY-MM-DD hh:mm:ss').trim(); |
|||
|
|||
const logger = expressWinston.logger({ |
|||
transports: [ |
|||
new winston.transports.Console() |
|||
], |
|||
format: winston.format.combine( |
|||
winston.format.timestamp(), |
|||
winston.format.colorize(), |
|||
winston.format.printf(info=>{ |
|||
return `-> ${tsFormat(info.timestamp)}: ${info.message}` |
|||
}) |
|||
), |
|||
meta: false, // optional: control whether you want to log the meta data about the request (default to true)
|
|||
// msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}"
|
|||
expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true
|
|||
colorize: true, // Color the text and status code, using the Express/morgan color palette (text: gray, status: default green, 3XX cyan, 4XX yellow, 5XX red).
|
|||
ignoreRoute: function (req, res) { |
|||
if(req.path.startsWith('/json')) return true // debugger
|
|||
return false; |
|||
} // optional: allows to skip some log messages based on request and/or response
|
|||
}) |
|||
|
|||
export default logger |
@ -0,0 +1,79 @@ |
|||
|
|||
import { tokenFromTerms } from './ldat' |
|||
|
|||
function addInRemoteText(full:{[k:string]:any}, contactId){ |
|||
const m = full && full.message |
|||
if (!(m && m.content)) return full |
|||
if (!(typeof m.content==='object')) return full |
|||
return fillmsg(full, {content: m.content[contactId+'']}) |
|||
} |
|||
|
|||
function removeRecipientFromChatMembers(full:{[k:string]:any}, destkey){ |
|||
const c = full && full.chat |
|||
if (!(c && c.members)) return full |
|||
if (!(typeof c.members==='object')) return full |
|||
|
|||
const members = {...c.members} |
|||
if(members[destkey]) delete members[destkey] |
|||
return fillchatmsg(full, {members}) |
|||
} |
|||
|
|||
function addInMediaKey(full:{[k:string]:any}, contactId){ |
|||
const m = full && full.message |
|||
if (!(m && m.mediaKey)) return full |
|||
if (!(m && m.mediaTerms)) return full |
|||
const mediaKey = m.mediaTerms.skipSigning ? '' : m.mediaKey[contactId+''] |
|||
return fillmsg(full, {mediaKey}) |
|||
} |
|||
|
|||
// add the token if its free, but if a price just the base64(host).muid
|
|||
async function finishTermsAndReceipt(full:{[k:string]:any}, destkey) { |
|||
const m = full && full.message |
|||
if (!(m && m.mediaTerms)) return full |
|||
|
|||
const t = m.mediaTerms |
|||
const meta = t.meta || {} |
|||
t.ttl = t.ttl || 31536000 |
|||
meta.ttl = t.ttl |
|||
const mediaToken = await tokenFromTerms({ |
|||
host: t.host || '', |
|||
muid: t.muid, |
|||
ttl: t.skipSigning ? 0 : t.ttl, |
|||
pubkey: t.skipSigning ? '' : destkey, |
|||
meta |
|||
}) |
|||
const fullmsg = fillmsg(full, {mediaToken}) |
|||
delete fullmsg.message.mediaTerms |
|||
return fullmsg |
|||
} |
|||
|
|||
async function personalizeMessage(m,contactId,destkey){ |
|||
const cloned = JSON.parse(JSON.stringify(m)) |
|||
const msg = addInRemoteText(cloned, contactId) |
|||
const cleanMsg = removeRecipientFromChatMembers(msg, destkey) |
|||
const msgWithMediaKey = addInMediaKey(cleanMsg, contactId) |
|||
const finalMsg = await finishTermsAndReceipt(msgWithMediaKey, destkey) |
|||
return finalMsg |
|||
} |
|||
|
|||
function fillmsg(full, props){ |
|||
return { |
|||
...full, message: { |
|||
...full.message, |
|||
...props, |
|||
} |
|||
} |
|||
} |
|||
|
|||
function fillchatmsg(full, props){ |
|||
return { |
|||
...full, chat: { |
|||
...full.chat, |
|||
...props, |
|||
} |
|||
} |
|||
} |
|||
|
|||
export { |
|||
personalizeMessage |
|||
} |
@ -0,0 +1,84 @@ |
|||
|
|||
import {loadLightning} from '../utils/lightning' |
|||
import * as publicIp from 'public-ip' |
|||
import {checkTag, checkCommitHash} from '../utils/gitinfo' |
|||
import {models} from '../models' |
|||
|
|||
function nodeinfo(){ |
|||
return new Promise(async (resolve, reject)=>{ |
|||
let public_ip = "" |
|||
try { |
|||
public_ip = await publicIp.v4() |
|||
} catch(e){ |
|||
console.log(e) |
|||
} |
|||
|
|||
const commitHash = await checkCommitHash() |
|||
|
|||
const tag = await checkTag() |
|||
|
|||
const lightning = loadLightning() |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
|
|||
const clean = await isClean() |
|||
|
|||
lightning.channelBalance({}, (err, channelBalance) => { |
|||
if(err) console.log(err) |
|||
// const { balance, pending_open_balance } = channelBalance
|
|||
lightning.listChannels({}, (err, channelList) => { |
|||
if(err) console.log(err) |
|||
const { channels } = channelList |
|||
|
|||
const localBalances = channels.map(c => c.local_balance) |
|||
const remoteBalances = channels.map(c => c.remote_balance) |
|||
const largestLocalBalance = Math.max(...localBalances) |
|||
const largestRemoteBalance = Math.max(...remoteBalances) |
|||
const totalLocalBalance = localBalances.reduce((a, b) => parseInt(a) + parseInt(b), 0) |
|||
|
|||
lightning.pendingChannels({}, (err, pendingChannels) => { |
|||
if(err) console.log(err) |
|||
lightning.getInfo({}, (err, info) => { |
|||
if(err) console.log(err) |
|||
if(!err && info){ |
|||
const node = { |
|||
node_alias: process.env.NODE_ALIAS, |
|||
ip: process.env.NODE_IP, |
|||
relay_commit: commitHash, |
|||
public_ip: public_ip, |
|||
pubkey: owner.publicKey, |
|||
number_channels: channels.length, |
|||
number_active_channels: info.num_active_channels, |
|||
number_pending_channels: info.num_pending_channels, |
|||
number_peers: info.num_peers, |
|||
largest_local_balance: largestLocalBalance, |
|||
largest_remote_balance: largestRemoteBalance, |
|||
total_local_balance: totalLocalBalance, |
|||
lnd_version: info.version, |
|||
relay_version: tag, |
|||
payment_channel: '', // ?
|
|||
hosting_provider: '', // ?
|
|||
open_channel_data: channels, |
|||
pending_channel_data: pendingChannels, |
|||
synced_to_chain: info.synced_to_chain, |
|||
synced_to_graph: info.synced_to_graph, |
|||
best_header_timestamp: info.best_header_timestamp, |
|||
testnet: info.testnet, |
|||
clean, |
|||
} |
|||
resolve(node) |
|||
} |
|||
}) |
|||
}) |
|||
}) |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
export {nodeinfo} |
|||
|
|||
async function isClean(){ |
|||
// has owner but with no auth token
|
|||
const cleanOwner = await models.Contact.findOne({ where: { isOwner: true, authToken: null }}) |
|||
if(cleanOwner) return true |
|||
return false |
|||
} |
@ -0,0 +1,19 @@ |
|||
function success(res, json) { |
|||
res.status(200); |
|||
res.json({ |
|||
success: true, |
|||
response: json, |
|||
}); |
|||
res.end(); |
|||
} |
|||
|
|||
function failure(res, e) { |
|||
res.status(400); |
|||
res.json({ |
|||
success: false, |
|||
error: (e&&e.message) || e, |
|||
}); |
|||
res.end(); |
|||
} |
|||
|
|||
export {success, failure} |
@ -0,0 +1,86 @@ |
|||
import { loadLightning } from './lightning' |
|||
import {sequelize, models} from '../models' |
|||
import { exec } from 'child_process' |
|||
|
|||
const USER_VERSION = 1 |
|||
|
|||
const setupDatabase = async () => { |
|||
console.log('=> [db] starting setup...') |
|||
await setVersion() |
|||
try { |
|||
await sequelize.sync() |
|||
console.log("=> [db] done syncing") |
|||
} catch(e) { |
|||
console.log("db sync failed",e) |
|||
} |
|||
await migrate() |
|||
setupOwnerContact() |
|||
console.log('=> [db] setup done') |
|||
} |
|||
|
|||
async function setVersion(){ |
|||
try { |
|||
await sequelize.query(`PRAGMA user_version = ${USER_VERSION}`) |
|||
} catch(e) { |
|||
console.log('=> setVersion failed',e) |
|||
} |
|||
} |
|||
|
|||
async function migrate(){ |
|||
try { |
|||
await sequelize.query(`update sphinx_chats SET deleted=false where deleted is null`) |
|||
} catch(e) { |
|||
console.log('=> migrate failed',e) |
|||
} |
|||
} |
|||
|
|||
const setupOwnerContact = async () => { |
|||
const owner = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
if (!owner) { |
|||
const lightning = await loadLightning() |
|||
lightning.getInfo({}, async (err, info) => { |
|||
if (err) { |
|||
console.log('[db] error creating node owner due to lnd failure', err) |
|||
} else { |
|||
try { |
|||
const one = await models.Contact.findOne({ where: { id: 1 }}) |
|||
if(!one){ |
|||
const contact = await models.Contact.create({ |
|||
id: 1, |
|||
publicKey: info.identity_pubkey, |
|||
isOwner: true, |
|||
authToken: null |
|||
}) |
|||
console.log('[db] created node owner contact, id:', contact.id) |
|||
} |
|||
} catch(error) { |
|||
console.log('[db] error creating owner contact', error) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
|
|||
const runMigrations = async () => { |
|||
await new Promise((resolve, reject) => { |
|||
const migrate: any = exec('node_modules/.bin/sequelize db:migrate', |
|||
{env: process.env}, |
|||
(err, stdout, stderr) => { |
|||
if (err) { |
|||
reject(err); |
|||
} else { |
|||
resolve(); |
|||
} |
|||
} |
|||
); |
|||
|
|||
// Forward stdout+stderr to this process
|
|||
migrate.stdout.pipe(process.stdout); |
|||
migrate.stderr.pipe(process.stderr); |
|||
}); |
|||
} |
|||
|
|||
export { setupDatabase, setupOwnerContact, runMigrations } |
|||
|
|||
|
|||
|
@ -0,0 +1,33 @@ |
|||
import * as WebSocket from 'ws' |
|||
|
|||
let connections = new Map() |
|||
let connectionCounter = 0 |
|||
|
|||
const connect = (server) => { |
|||
server = new WebSocket.Server({ server }) |
|||
|
|||
console.log('[socket] connected to server') |
|||
|
|||
server.on('connection', socket => { |
|||
console.log('[socket] connection received') |
|||
var id = connectionCounter++; |
|||
connections.set(id, socket) |
|||
}) |
|||
|
|||
} |
|||
|
|||
const send = (body) => { |
|||
connections.forEach((socket, index) => { |
|||
socket.send(body) |
|||
}) |
|||
} |
|||
|
|||
const sendJson = (object) => { |
|||
send(JSON.stringify(object)) |
|||
} |
|||
|
|||
export { |
|||
connect, |
|||
send, |
|||
sendJson |
|||
} |
@ -0,0 +1,12 @@ |
|||
import './tv42_zbase32_gopherjs' |
|||
|
|||
function encode(b){ |
|||
return global['zbase32'].Encode(b) |
|||
} |
|||
function decode(txt){ |
|||
return global['zbase32'].Decode(txt) |
|||
} |
|||
export { |
|||
encode, |
|||
decode, |
|||
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,114 @@ |
|||
import * as express from 'express' |
|||
import * as bodyParser from 'body-parser' |
|||
import * as helmet from 'helmet' |
|||
import * as cookieParser from 'cookie-parser' |
|||
import * as crypto from 'crypto' |
|||
import {models} from './api/models' |
|||
import logger from './api/utils/logger' |
|||
import {pingHubInterval, checkInvitesHubInterval} from './api/hub' |
|||
import {setupDatabase} from './api/utils/setup' |
|||
import * as controllers from './api/controllers' |
|||
import * as socket from './api/utils/socket' |
|||
|
|||
let server: any = null |
|||
const port = process.env.PORT || 3001; |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/config/app.json')[env]; |
|||
|
|||
process.env.GRPC_SSL_CIPHER_SUITES = 'HIGH+ECDSA' |
|||
|
|||
var i = 0 |
|||
|
|||
// START SETUP!
|
|||
connectToLND() |
|||
|
|||
async function connectToLND(){ |
|||
i++ |
|||
console.log(`=> [lnd] connecting... attempt #${i}`) |
|||
try { |
|||
await controllers.iniGrpcSubscriptions() |
|||
mainSetup() |
|||
} catch(e) { |
|||
setTimeout(async()=>{ // retry each 2 secs
|
|||
await connectToLND() |
|||
},2000) |
|||
} |
|||
} |
|||
|
|||
async function mainSetup(){ |
|||
await setupDatabase(); |
|||
if (config.hub_api_url) { |
|||
pingHubInterval(5000) |
|||
checkInvitesHubInterval(5000) |
|||
} |
|||
await setupApp() |
|||
} |
|||
|
|||
async function setupApp(){ |
|||
const app = express(); |
|||
const server = require("http").Server(app); |
|||
|
|||
app.use(helmet()); |
|||
app.use(bodyParser.json()); |
|||
app.use(bodyParser.urlencoded({ extended: true })); |
|||
app.use(logger) |
|||
app.options('*', (req, res) => res.send(200)); |
|||
app.use((req, res, next) => { |
|||
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:8080'); |
|||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); |
|||
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept'); |
|||
res.setHeader('Cache-Control', 'private, no-cache, no-store, must-revalidate'); |
|||
res.setHeader('Expires', '-1'); |
|||
res.setHeader('Pragma', 'no-cache'); |
|||
next(); |
|||
}); |
|||
app.use(cookieParser()) |
|||
if (env != 'development') { |
|||
app.use(authModule); |
|||
} |
|||
app.use('/static', express.static('public')); |
|||
app.get('/app', (req, res) => res.sendFile(__dirname + '/public/index.html')) |
|||
|
|||
server.listen(port, (err) => { |
|||
if (err) throw err; |
|||
/* eslint-disable no-console */ |
|||
console.log(`Node listening on ${port}.`); |
|||
}); |
|||
|
|||
controllers.set(app); |
|||
|
|||
socket.connect(server) |
|||
} |
|||
|
|||
async function authModule(req, res, next) { |
|||
if ( |
|||
req.path == '/app' || |
|||
req.path == '/' || |
|||
req.path == '/info' || |
|||
req.path == '/contacts/tokens' || |
|||
req.path == '/login' || |
|||
req.path.startsWith('/static') || |
|||
req.path == '/contacts/set_dev' |
|||
) { |
|||
next() |
|||
return |
|||
} |
|||
|
|||
const token = req.headers['x-user-token'] || req.cookies['x-user-token'] |
|||
|
|||
if (token == null) { |
|||
res.writeHead(401, 'Access invalid for user', {'Content-Type' : 'text/plain'}); |
|||
res.end('Invalid credentials'); |
|||
} else { |
|||
const user = await models.Contact.findOne({ where: { isOwner: true }}) |
|||
const hashedToken = crypto.createHash('sha256').update(token).digest('base64'); |
|||
if (user.authToken == null || user.authToken != hashedToken) { |
|||
res.writeHead(401, 'Access invalid for user', {'Content-Type' : 'text/plain'}); |
|||
res.end('Invalid credentials'); |
|||
} else { |
|||
next(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
export default server |
@ -0,0 +1,32 @@ |
|||
{ |
|||
"development": { |
|||
"senza_url": "http://localhost:3000/api/v2", |
|||
"macaroon_location": "/Users/evanfeenstra/code/lnd-dev/alice/data/chain/bitcoin/simnet/admin.macaroon", |
|||
"tls_location": "/Users/evanfeenstra/Library/Application Support/Lnd/tls.cert", |
|||
"node_ip": "127.0.0.1", |
|||
"lnd_port": "10001", |
|||
"node_http_protocol": "http", |
|||
"node_http_port": "3001", |
|||
"hub_api_url": "http://lvh.me/api/v1", |
|||
"hub_url": "http://lvh.me/ping", |
|||
"hub_invite_url": "http://lvh.me/invites", |
|||
"hub_check_invite_url": "http://lvh.me/check_invite", |
|||
"media_host": "localhost:5000" |
|||
}, |
|||
"production": { |
|||
"senza_url": "https://staging.senza.us/api/v2/", |
|||
"macaroon_location": "/home/ubuntu/.lnd/data/chain/bitcoin/mainnet/admin.macaroon", |
|||
"tls_location": "/home/ubuntu/.lnd/tls.cert", |
|||
"lnd_log_location": "/home/ubuntu/.lnd/logs/bitcoin/mainnet/lnd.log", |
|||
"lncli_location": "/home/ubuntu/go/bin", |
|||
"node_ip": "localhost", |
|||
"node_http_protocol": "http", |
|||
"node_http_port": "80", |
|||
"lnd_port": "10009", |
|||
"hub_api_url": "http://hub.sphinx.chat/api/v1", |
|||
"hub_url": "http://hub.sphinx.chat/ping", |
|||
"hub_invite_url": "http://hub.sphinx.chat/invites", |
|||
"hub_check_invite_url": "http://hub.sphinx.chat/check_invite", |
|||
"media_host": "memes.sphinx.chat" |
|||
} |
|||
} |
@ -0,0 +1,18 @@ |
|||
{ |
|||
"development": { |
|||
"dialect": "sqlite", |
|||
"storage": "/Users/Shared/sphinx.db" |
|||
}, |
|||
"docker_development": { |
|||
"dialect": "sqlite", |
|||
"storage": "./sphinx.db" |
|||
}, |
|||
"test": { |
|||
"dialect": "sqlite", |
|||
"storage": "/home/ubuntu/sphinx.db" |
|||
}, |
|||
"production": { |
|||
"dialect": "sqlite", |
|||
"storage": "/home/ubuntu/sphinx.db" |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
{ |
|||
"invite_statuses": { |
|||
"pending": 0, |
|||
"ready": 1, |
|||
"delivered": 2, |
|||
"in_progress": 3, |
|||
"complete": 4, |
|||
"expired": 5, |
|||
"payment_pending": 6 |
|||
}, |
|||
"contact_statuses": { |
|||
"pending": 0, |
|||
"confirmed": 1 |
|||
}, |
|||
"statuses": { |
|||
"pending": 0, |
|||
"confirmed": 1, |
|||
"cancelled": 2, |
|||
"received": 3, |
|||
"failed": 4 |
|||
}, |
|||
"message_types": { |
|||
"message": 0, |
|||
"confirmation": 1, |
|||
"invoice": 2, |
|||
"payment": 3, |
|||
"cancellation": 4, |
|||
"direct_payment": 5, |
|||
"attachment": 6, |
|||
"purchase": 7, |
|||
"purchase_accept": 8, |
|||
"purchase_deny": 9, |
|||
"contact_key": 10, |
|||
"contact_key_confirmation": 11, |
|||
"group_create": 12, |
|||
"group_invite": 13, |
|||
"group_join": 14, |
|||
"group_leave": 15, |
|||
"group_query": 16 |
|||
}, |
|||
"payment_errors": { |
|||
"timeout": "Timed Out", |
|||
"no_route": "No Route To Receiver", |
|||
"error": "Error", |
|||
"incorrect_payment_details": "Incorrect Payment Details", |
|||
"unknown": "Unknown" |
|||
}, |
|||
"chat_types": { |
|||
"conversation": 0, |
|||
"group": 1 |
|||
} |
|||
} |
@ -0,0 +1,308 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const res_1 = require("../utils/res"); |
|||
const helpers = require("../helpers"); |
|||
const socket = require("../utils/socket"); |
|||
const hub_1 = require("../hub"); |
|||
const md5 = require("md5"); |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
function getChats(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const chats = yield models_1.models.Chat.findAll({ where: { deleted: false }, raw: true }); |
|||
const c = chats.map(chat => jsonUtils.chatToJson(chat)); |
|||
res_1.success(res, c); |
|||
}); |
|||
} |
|||
exports.getChats = getChats; |
|||
function mute(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const chatId = req.params['chat_id']; |
|||
const mute = req.params['mute_unmute']; |
|||
if (!["mute", "unmute"].includes(mute)) { |
|||
return res_1.failure(res, "invalid option for mute"); |
|||
} |
|||
const chat = yield models_1.models.Chat.findOne({ where: { id: chatId } }); |
|||
if (!chat) { |
|||
return res_1.failure(res, 'chat not found'); |
|||
} |
|||
chat.update({ isMuted: (mute == "mute") }); |
|||
res_1.success(res, jsonUtils.chatToJson(chat)); |
|||
}); |
|||
} |
|||
exports.mute = mute; |
|||
function createGroupChat(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const { name, contact_ids, } = req.body; |
|||
const members = {}; //{pubkey:{key,alias}, ...}
|
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
members[owner.publicKey] = { |
|||
key: owner.contactKey, alias: owner.alias |
|||
}; |
|||
yield asyncForEach(contact_ids, (cid) => __awaiter(this, void 0, void 0, function* () { |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id: cid } }); |
|||
members[contact.publicKey] = { |
|||
key: contact.contactKey, |
|||
alias: contact.alias || '' |
|||
}; |
|||
})); |
|||
const chatParams = createGroupChatParams(owner, contact_ids, members, name); |
|||
helpers.sendMessage({ |
|||
chat: Object.assign(Object.assign({}, chatParams), { members }), |
|||
sender: owner, |
|||
type: constants.message_types.group_create, |
|||
message: {}, |
|||
failure: function (e) { |
|||
res_1.failure(res, e); |
|||
}, |
|||
success: function () { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const chat = yield models_1.models.Chat.create(chatParams); |
|||
res_1.success(res, jsonUtils.chatToJson(chat)); |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
exports.createGroupChat = createGroupChat; |
|||
function addGroupMembers(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const { contact_ids, } = req.body; |
|||
const { id } = req.params; |
|||
const members = {}; //{pubkey:{key,alias}, ...}
|
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
let chat = yield models_1.models.Chat.findOne({ where: { id } }); |
|||
const contactIds = JSON.parse(chat.contactIds || '[]'); |
|||
// for all members (existing and new)
|
|||
members[owner.publicKey] = { key: owner.contactKey, alias: owner.alias }; |
|||
const allContactIds = contactIds.concat(contact_ids); |
|||
yield asyncForEach(allContactIds, (cid) => __awaiter(this, void 0, void 0, function* () { |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id: cid } }); |
|||
if (contact) { |
|||
members[contact.publicKey] = { |
|||
key: contact.contactKey, |
|||
alias: contact.alias |
|||
}; |
|||
} |
|||
})); |
|||
res_1.success(res, jsonUtils.chatToJson(chat)); |
|||
helpers.sendMessage({ |
|||
chat: Object.assign(Object.assign({}, chat.dataValues), { contactIds: contact_ids, members }), |
|||
sender: owner, |
|||
type: constants.message_types.group_invite, |
|||
message: {} |
|||
}); |
|||
}); |
|||
} |
|||
exports.addGroupMembers = addGroupMembers; |
|||
const deleteChat = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { id } = req.params; |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const chat = yield models_1.models.Chat.findOne({ where: { id } }); |
|||
helpers.sendMessage({ |
|||
chat, |
|||
sender: owner, |
|||
message: {}, |
|||
type: constants.message_types.group_leave, |
|||
}); |
|||
yield chat.update({ |
|||
deleted: true, |
|||
uuid: '', |
|||
contactIds: '[]', |
|||
name: '' |
|||
}); |
|||
yield models_1.models.Message.destroy({ where: { chatId: id } }); |
|||
res_1.success(res, { chat_id: id }); |
|||
}); |
|||
exports.deleteChat = deleteChat; |
|||
function receiveGroupLeave(payload) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
console.log('=> receiveGroupLeave'); |
|||
const { sender_pub_key, chat_uuid } = yield helpers.parseReceiveParams(payload); |
|||
const chat = yield models_1.models.Chat.findOne({ where: { uuid: chat_uuid } }); |
|||
if (!chat) |
|||
return; |
|||
const sender = yield models_1.models.Contact.findOne({ where: { publicKey: sender_pub_key } }); |
|||
if (!sender) |
|||
return; |
|||
const oldContactIds = JSON.parse(chat.contactIds || '[]'); |
|||
const contactIds = oldContactIds.filter(cid => cid !== sender.id); |
|||
yield chat.update({ contactIds: JSON.stringify(contactIds) }); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const msg = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.group_leave, |
|||
sender: sender.id, |
|||
date: date, |
|||
messageContent: '', |
|||
remoteMessageContent: '', |
|||
status: constants.statuses.confirmed, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}; |
|||
const message = yield models_1.models.Message.create(msg); |
|||
socket.sendJson({ |
|||
type: 'group_leave', |
|||
response: { |
|||
contact: jsonUtils.contactToJson(sender), |
|||
chat: jsonUtils.chatToJson(chat), |
|||
message: jsonUtils.messageToJson(message, null) |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
exports.receiveGroupLeave = receiveGroupLeave; |
|||
function receiveGroupJoin(payload) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
console.log('=> receiveGroupJoin'); |
|||
const { sender_pub_key, chat_uuid, chat_members } = yield helpers.parseReceiveParams(payload); |
|||
const chat = yield models_1.models.Chat.findOne({ where: { uuid: chat_uuid } }); |
|||
if (!chat) |
|||
return; |
|||
let theSender = null; |
|||
const sender = yield models_1.models.Contact.findOne({ where: { publicKey: sender_pub_key } }); |
|||
const contactIds = JSON.parse(chat.contactIds || '[]'); |
|||
if (sender) { |
|||
theSender = sender; // might already include??
|
|||
if (!contactIds.includes(sender.id)) |
|||
contactIds.push(sender.id); |
|||
} |
|||
else { |
|||
const member = chat_members[sender_pub_key]; |
|||
if (member && member.key) { |
|||
const createdContact = yield models_1.models.Contact.create({ |
|||
publicKey: sender_pub_key, |
|||
contactKey: member.key, |
|||
alias: member.alias || 'Unknown', |
|||
status: 1 |
|||
}); |
|||
theSender = createdContact; |
|||
contactIds.push(createdContact.id); |
|||
} |
|||
} |
|||
yield chat.update({ contactIds: JSON.stringify(contactIds) }); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const msg = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.group_join, |
|||
sender: sender.id, |
|||
date: date, |
|||
messageContent: '', |
|||
remoteMessageContent: '', |
|||
status: constants.statuses.confirmed, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}; |
|||
const message = yield models_1.models.Message.create(msg); |
|||
socket.sendJson({ |
|||
type: 'group_join', |
|||
response: { |
|||
contact: jsonUtils.contactToJson(theSender), |
|||
chat: jsonUtils.chatToJson(chat), |
|||
message: jsonUtils.messageToJson(message, null) |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
exports.receiveGroupJoin = receiveGroupJoin; |
|||
function receiveGroupCreateOrInvite(payload) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const { chat_members, chat_name, chat_uuid } = yield helpers.parseReceiveParams(payload); |
|||
const contactIds = []; |
|||
const newContacts = []; |
|||
for (let [pubkey, member] of Object.entries(chat_members)) { |
|||
const contact = yield models_1.models.Contact.findOne({ where: { publicKey: pubkey } }); |
|||
if (!contact && member && member.key) { |
|||
const createdContact = yield models_1.models.Contact.create({ |
|||
publicKey: pubkey, |
|||
contactKey: member.key, |
|||
alias: member.alias || 'Unknown', |
|||
status: 1 |
|||
}); |
|||
contactIds.push(createdContact.id); |
|||
newContacts.push(createdContact.dataValues); |
|||
} |
|||
else { |
|||
contactIds.push(contact.id); |
|||
} |
|||
} |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
if (!contactIds.includes(owner.id)) |
|||
contactIds.push(owner.id); |
|||
// make chat
|
|||
let date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const chat = yield models_1.models.Chat.create({ |
|||
uuid: chat_uuid, |
|||
contactIds: JSON.stringify(contactIds), |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
name: chat_name, |
|||
type: constants.chat_types.group |
|||
}); |
|||
socket.sendJson({ |
|||
type: 'group_create', |
|||
response: jsonUtils.messageToJson({ newContacts }, chat) |
|||
}); |
|||
hub_1.sendNotification(chat, chat_name, 'group'); |
|||
if (payload.type === constants.message_types.group_invite) { |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
helpers.sendMessage({ |
|||
chat: Object.assign(Object.assign({}, chat.dataValues), { members: { |
|||
[owner.publicKey]: { |
|||
key: owner.contactKey, |
|||
alias: owner.alias || '' |
|||
} |
|||
} }), |
|||
sender: owner, |
|||
message: {}, |
|||
type: constants.message_types.group_join, |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
exports.receiveGroupCreateOrInvite = receiveGroupCreateOrInvite; |
|||
function createGroupChatParams(owner, contactIds, members, name) { |
|||
let date = new Date(); |
|||
date.setMilliseconds(0); |
|||
if (!(owner && members && contactIds && Array.isArray(contactIds))) { |
|||
return; |
|||
} |
|||
const pubkeys = []; |
|||
for (let pubkey of Object.keys(members)) { // just the key
|
|||
pubkeys.push(String(pubkey)); |
|||
} |
|||
if (!(pubkeys && pubkeys.length)) |
|||
return; |
|||
const allkeys = pubkeys.includes(owner.publicKey) ? pubkeys : [owner.publicKey].concat(pubkeys); |
|||
const hash = md5(allkeys.sort().join("-")); |
|||
const theContactIds = contactIds.includes(owner.id) ? contactIds : [owner.id].concat(contactIds); |
|||
return { |
|||
uuid: `${new Date().valueOf()}-${hash}`, |
|||
contactIds: JSON.stringify(theContactIds), |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
name: name, |
|||
type: constants.chat_types.group |
|||
}; |
|||
} |
|||
function asyncForEach(array, callback) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
for (let index = 0; index < array.length; index++) { |
|||
yield callback(array[index], index, array); |
|||
} |
|||
}); |
|||
} |
|||
//# sourceMappingURL=chats.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,205 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const crypto = require("crypto"); |
|||
const socket = require("../utils/socket"); |
|||
const helpers = require("../helpers"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const res_1 = require("../utils/res"); |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
const getContacts = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const contacts = yield models_1.models.Contact.findAll({ where: { deleted: false }, raw: true }); |
|||
const invites = yield models_1.models.Invite.findAll({ raw: true }); |
|||
const chats = yield models_1.models.Chat.findAll({ where: { deleted: false }, raw: true }); |
|||
const subscriptions = yield models_1.models.Subscription.findAll({ raw: true }); |
|||
const contactsResponse = contacts.map(contact => { |
|||
let contactJson = jsonUtils.contactToJson(contact); |
|||
let invite = invites.find(invite => invite.contactId == contact.id); |
|||
if (invite) { |
|||
contactJson.invite = jsonUtils.inviteToJson(invite); |
|||
} |
|||
return contactJson; |
|||
}); |
|||
const subsResponse = subscriptions.map(s => jsonUtils.subscriptionToJson(s, null)); |
|||
const chatsResponse = chats.map(chat => jsonUtils.chatToJson(chat)); |
|||
res_1.success(res, { |
|||
contacts: contactsResponse, |
|||
chats: chatsResponse, |
|||
subscriptions: subsResponse |
|||
}); |
|||
}); |
|||
exports.getContacts = getContacts; |
|||
const generateToken = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('=> generateToken called', { body: req.body, params: req.params, query: req.query }); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true, authToken: null } }); |
|||
if (owner) { |
|||
const hash = crypto.createHash('sha256').update(req.body['token']).digest('base64'); |
|||
console.log("req.params['token']", req.params['token']); |
|||
console.log("hash", hash); |
|||
owner.update({ authToken: hash }); |
|||
res_1.success(res, {}); |
|||
} |
|||
else { |
|||
res_1.failure(res, {}); |
|||
} |
|||
}); |
|||
exports.generateToken = generateToken; |
|||
const updateContact = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('=> updateContact called', { body: req.body, params: req.params, query: req.query }); |
|||
let attrs = extractAttrs(req.body); |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id: req.params.id } }); |
|||
let shouldUpdateContactKey = (contact.isOwner && contact.contactKey == null && attrs["contact_key"] != null); |
|||
const owner = yield contact.update(jsonUtils.jsonToContact(attrs)); |
|||
res_1.success(res, jsonUtils.contactToJson(owner)); |
|||
if (!shouldUpdateContactKey) { |
|||
return; |
|||
} |
|||
// definitely "owner" now
|
|||
const contactIds = yield models_1.models.Contact.findAll({ where: { deleted: false } }).map(c => c.id); |
|||
if (contactIds.length == 0) { |
|||
return; |
|||
} |
|||
helpers.sendContactKeys({ |
|||
contactIds: contactIds, |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}); |
|||
}); |
|||
exports.updateContact = updateContact; |
|||
const exchangeKeys = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('=> exchangeKeys called', { body: req.body, params: req.params, query: req.query }); |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id: req.params.id } }); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
res_1.success(res, jsonUtils.contactToJson(contact)); |
|||
helpers.sendContactKeys({ |
|||
contactIds: [contact.id], |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}); |
|||
}); |
|||
exports.exchangeKeys = exchangeKeys; |
|||
const createContact = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('=> createContact called', { body: req.body, params: req.params, query: req.query }); |
|||
let attrs = extractAttrs(req.body); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const createdContact = yield models_1.models.Contact.create(attrs); |
|||
const contact = yield createdContact.update(jsonUtils.jsonToContact(attrs)); |
|||
res_1.success(res, jsonUtils.contactToJson(contact)); |
|||
helpers.sendContactKeys({ |
|||
contactIds: [contact.id], |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}); |
|||
}); |
|||
exports.createContact = createContact; |
|||
const deleteContact = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const id = parseInt(req.params.id || '0'); |
|||
if (!id || id === 1) { |
|||
res_1.failure(res, 'Cannot delete self'); |
|||
return; |
|||
} |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id } }); |
|||
yield contact.update({ |
|||
deleted: true, |
|||
publicKey: '', |
|||
photoUrl: '', |
|||
alias: 'Unknown', |
|||
contactKey: '', |
|||
}); |
|||
// find and destroy chat & messages
|
|||
const chats = yield models_1.models.Chat.findAll({ where: { deleted: false } }); |
|||
chats.map((chat) => __awaiter(void 0, void 0, void 0, function* () { |
|||
if (chat.type === constants.chat_types.conversation) { |
|||
const contactIds = JSON.parse(chat.contactIds); |
|||
if (contactIds.includes(id)) { |
|||
yield chat.update({ |
|||
deleted: true, |
|||
uuid: '', |
|||
contactIds: '[]', |
|||
name: '' |
|||
}); |
|||
yield models_1.models.Message.destroy({ where: { chatId: chat.id } }); |
|||
} |
|||
} |
|||
})); |
|||
yield models_1.models.Invite.destroy({ where: { contactId: id } }); |
|||
yield models_1.models.Subscription.destroy({ where: { contactId: id } }); |
|||
res_1.success(res, {}); |
|||
}); |
|||
exports.deleteContact = deleteContact; |
|||
const receiveConfirmContactKey = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('=> confirm contact key', { payload }); |
|||
const dat = payload.content || payload; |
|||
const sender_pub_key = dat.sender.pub_key; |
|||
const sender_contact_key = dat.sender.contact_key; |
|||
const sender_alias = dat.sender.alias || 'Unknown'; |
|||
const sender_photo_url = dat.sender.photoUrl; |
|||
if (sender_photo_url) { |
|||
// download and store photo locally
|
|||
} |
|||
const sender = yield models_1.models.Contact.findOne({ where: { publicKey: sender_pub_key, status: constants.contact_statuses.confirmed } }); |
|||
if (sender_contact_key && sender) { |
|||
if (!sender.alias || sender.alias === 'Unknown') { |
|||
sender.update({ contactKey: sender_contact_key, alias: sender_alias }); |
|||
} |
|||
else { |
|||
sender.update({ contactKey: sender_contact_key }); |
|||
} |
|||
socket.sendJson({ |
|||
type: 'contact', |
|||
response: jsonUtils.contactToJson(sender) |
|||
}); |
|||
} |
|||
}); |
|||
exports.receiveConfirmContactKey = receiveConfirmContactKey; |
|||
const receiveContactKey = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('=> received contact key', JSON.stringify(payload)); |
|||
const dat = payload.content || payload; |
|||
const sender_pub_key = dat.sender.pub_key; |
|||
const sender_contact_key = dat.sender.contact_key; |
|||
const sender_alias = dat.sender.alias || 'Unknown'; |
|||
const sender_photo_url = dat.sender.photoUrl; |
|||
if (sender_photo_url) { |
|||
// download and store photo locally
|
|||
} |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const sender = yield models_1.models.Contact.findOne({ where: { publicKey: sender_pub_key, status: constants.contact_statuses.confirmed } }); |
|||
if (sender_contact_key && sender) { |
|||
if (!sender.alias || sender.alias === 'Unknown') { |
|||
sender.update({ contactKey: sender_contact_key, alias: sender_alias }); |
|||
} |
|||
else { |
|||
sender.update({ contactKey: sender_contact_key }); |
|||
} |
|||
socket.sendJson({ |
|||
type: 'contact', |
|||
response: jsonUtils.contactToJson(sender) |
|||
}); |
|||
} |
|||
helpers.sendContactKeys({ |
|||
contactPubKey: sender_pub_key, |
|||
sender: owner, |
|||
type: constants.message_types.contact_key_confirmation, |
|||
}); |
|||
}); |
|||
exports.receiveContactKey = receiveContactKey; |
|||
const extractAttrs = body => { |
|||
let fields_to_update = ["public_key", "node_alias", "alias", "photo_url", "device_id", "status", "contact_key"]; |
|||
let attrs = {}; |
|||
Object.keys(body).forEach(key => { |
|||
if (fields_to_update.includes(key)) { |
|||
attrs[key] = body[key]; |
|||
} |
|||
}); |
|||
return attrs; |
|||
}; |
|||
//# sourceMappingURL=contacts.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,133 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const lightning_1 = require("../utils/lightning"); |
|||
const res_1 = require("../utils/res"); |
|||
const readLastLines = require("read-last-lines"); |
|||
const nodeinfo_1 = require("../utils/nodeinfo"); |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/app.json')[env]; |
|||
const defaultLogFiles = [ |
|||
'/home/lnd/.pm2/logs/app-error.log', |
|||
'/var/log/syslog', |
|||
]; |
|||
function getLogsSince(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const logFiles = config.log_file ? [config.log_file] : defaultLogFiles; |
|||
let txt; |
|||
let err; |
|||
yield asyncForEach(logFiles, (filepath) => __awaiter(this, void 0, void 0, function* () { |
|||
if (!txt) { |
|||
try { |
|||
const lines = yield readLastLines.read(filepath, 500); |
|||
if (lines) { |
|||
var linesArray = lines.split('\n'); |
|||
linesArray.reverse(); |
|||
txt = linesArray.join('\n'); |
|||
} |
|||
} |
|||
catch (e) { |
|||
err = e; |
|||
} |
|||
} |
|||
})); |
|||
if (txt) |
|||
res_1.success(res, txt); |
|||
else |
|||
res_1.failure(res, err); |
|||
}); |
|||
} |
|||
exports.getLogsSince = getLogsSince; |
|||
const getInfo = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const lightning = lightning_1.loadLightning(); |
|||
var request = {}; |
|||
lightning.getInfo(request, function (err, response) { |
|||
res.status(200); |
|||
if (err == null) { |
|||
res.json({ success: true, response }); |
|||
} |
|||
else { |
|||
res.json({ success: false }); |
|||
} |
|||
res.end(); |
|||
}); |
|||
}); |
|||
exports.getInfo = getInfo; |
|||
const getChannels = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const lightning = lightning_1.loadLightning(); |
|||
var request = {}; |
|||
lightning.listChannels(request, function (err, response) { |
|||
res.status(200); |
|||
if (err == null) { |
|||
res.json({ success: true, response }); |
|||
} |
|||
else { |
|||
res.json({ success: false }); |
|||
} |
|||
res.end(); |
|||
}); |
|||
}); |
|||
exports.getChannels = getChannels; |
|||
const getBalance = (req, res) => { |
|||
const lightning = lightning_1.loadLightning(); |
|||
var request = {}; |
|||
lightning.channelBalance(request, function (err, response) { |
|||
res.status(200); |
|||
if (err == null) { |
|||
res.json({ success: true, response }); |
|||
} |
|||
else { |
|||
res.json({ success: false }); |
|||
} |
|||
res.end(); |
|||
}); |
|||
}; |
|||
exports.getBalance = getBalance; |
|||
const getLocalRemoteBalance = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const lightning = lightning_1.loadLightning(); |
|||
lightning.listChannels({}, (err, channelList) => { |
|||
const { channels } = channelList; |
|||
const localBalances = channels.map(c => c.local_balance); |
|||
const remoteBalances = channels.map(c => c.remote_balance); |
|||
const totalLocalBalance = localBalances.reduce((a, b) => parseInt(a) + parseInt(b), 0); |
|||
const totalRemoteBalance = remoteBalances.reduce((a, b) => parseInt(a) + parseInt(b), 0); |
|||
res.status(200); |
|||
if (err == null) { |
|||
res.json({ success: true, response: { local_balance: totalLocalBalance, remote_balance: totalRemoteBalance } }); |
|||
} |
|||
else { |
|||
res.json({ success: false }); |
|||
} |
|||
res.end(); |
|||
}); |
|||
}); |
|||
exports.getLocalRemoteBalance = getLocalRemoteBalance; |
|||
const getNodeInfo = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
var ipOfSource = req.connection.remoteAddress; |
|||
if (!(ipOfSource.includes('127.0.0.1') || ipOfSource.includes('localhost'))) { |
|||
res.status(401); |
|||
res.end(); |
|||
return; |
|||
} |
|||
const node = yield nodeinfo_1.nodeinfo(); |
|||
res.status(200); |
|||
res.json(node); |
|||
res.end(); |
|||
}); |
|||
exports.getNodeInfo = getNodeInfo; |
|||
function asyncForEach(array, callback) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
for (let index = 0; index < array.length; index++) { |
|||
yield callback(array[index], index, array); |
|||
} |
|||
}); |
|||
} |
|||
//# sourceMappingURL=details.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"details.js","sourceRoot":"","sources":["../../../api/controllers/details.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,kDAAgD;AAChD,sCAA+C;AAC/C,iDAAgD;AAChD,gDAA6C;AAE7C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa,CAAC;AAClD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,wBAAwB,CAAC,CAAC,GAAG,CAAC,CAAC;AAElE,MAAM,eAAe,GAAG;IACvB,mCAAmC;IACnC,iBAAiB;CACjB,CAAA;AACD,SAAe,YAAY,CAAC,GAAG,EAAE,GAAG;;QACnC,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,eAAe,CAAA;QACtE,IAAI,GAAG,CAAA;QACP,IAAI,GAAG,CAAA;QACP,MAAM,YAAY,CAAC,QAAQ,EAAE,CAAM,QAAQ,EAAA,EAAE;YAC5C,IAAG,CAAC,GAAG,EAAC;gBACP,IAAI;oBACH,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAA;oBACrD,IAAG,KAAK,EAAE;wBACT,IAAI,UAAU,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;wBAClC,UAAU,CAAC,OAAO,EAAE,CAAA;wBACpB,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;qBAC3B;iBACD;gBAAC,OAAM,CAAC,EAAE;oBACV,GAAG,GAAG,CAAC,CAAA;iBACP;aACD;QACF,CAAC,CAAA,CAAC,CAAA;QACF,IAAG,GAAG;YAAE,aAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;;YACpB,aAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;IACvB,CAAC;CAAA;AAkFA,oCAAY;AAhFb,MAAM,OAAO,GAAG,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IAClC,MAAM,SAAS,GAAG,yBAAa,EAAE,CAAA;IACjC,IAAI,OAAO,GAAG,EAAE,CAAA;IAChB,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,UAAS,GAAG,EAAE,QAAQ;QAChD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,GAAG,IAAI,IAAI,EAAE;YAChB,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;SACtC;aAAM;YACN,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;SAC7B;QACD,GAAG,CAAC,GAAG,EAAE,CAAC;IACX,CAAC,CAAC,CAAC;AACJ,CAAC,CAAA,CAAC;AAgED,0BAAO;AA9DR,MAAM,WAAW,GAAG,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IACrC,MAAM,SAAS,GAAG,yBAAa,EAAE,CAAA;IAClC,IAAI,OAAO,GAAG,EAAE,CAAA;IAChB,SAAS,CAAC,YAAY,CAAC,OAAO,EAAE,UAAS,GAAG,EAAE,QAAQ;QACrD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,GAAG,IAAI,IAAI,EAAE;YAChB,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;SACtC;aAAM;YACN,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;SAC7B;QACD,GAAG,CAAC,GAAG,EAAE,CAAC;IACX,CAAC,CAAC,CAAC;AACJ,CAAC,CAAA,CAAC;AAoDD,kCAAW;AAlDZ,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC9B,MAAM,SAAS,GAAG,yBAAa,EAAE,CAAA;IAClC,IAAI,OAAO,GAAG,EAAE,CAAA;IAChB,SAAS,CAAC,cAAc,CAAC,OAAO,EAAE,UAAS,GAAG,EAAE,QAAQ;QACvD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,GAAG,IAAI,IAAI,EAAE;YAChB,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;SACtC;aAAM;YACN,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;SAC7B;QACD,GAAG,CAAC,GAAG,EAAE,CAAC;IACX,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC;AAqCD,gCAAU;AAnCX,MAAM,qBAAqB,GAAG,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IAChD,MAAM,SAAS,GAAG,yBAAa,EAAE,CAAA;IACjC,SAAS,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,WAAW,EAAE,EAAE;QAC/C,MAAM,EAAE,QAAQ,EAAE,GAAG,WAAW,CAAA;QAEhC,MAAM,aAAa,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAA;QACxD,MAAM,cAAc,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAA;QAC1D,MAAM,iBAAiB,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QACtF,MAAM,kBAAkB,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;QAExF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAChB,IAAI,GAAG,IAAI,IAAI,EAAE;YAChB,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,aAAa,EAAE,iBAAiB,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;SAChH;aAAM;YACN,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;SAC7B;QACD,GAAG,CAAC,GAAG,EAAE,CAAC;IACT,CAAC,CAAC,CAAA;AACL,CAAC,CAAA,CAAC;AAmBD,sDAAqB;AAjBtB,MAAM,WAAW,GAAG,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IACtC,IAAI,UAAU,GAAG,GAAG,CAAC,UAAU,CAAC,aAAa,CAAC;IAC9C,IAAG,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAAC;QAC1E,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACf,GAAG,CAAC,GAAG,EAAE,CAAA;QACT,OAAM;KACN;IACD,MAAM,IAAI,GAAG,MAAM,mBAAQ,EAAE,CAAA;IAC7B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACf,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACd,GAAG,CAAC,GAAG,EAAE,CAAA;AACV,CAAC,CAAA,CAAA;AAQA,kCAAW;AAGZ,SAAe,YAAY,CAAC,KAAK,EAAE,QAAQ;;QAC1C,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YAChD,MAAM,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;SAC7C;IACF,CAAC;CAAA"} |
@ -0,0 +1,139 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const lndService = require("../grpc"); |
|||
const gitinfo_1 = require("../utils/gitinfo"); |
|||
const lightning_1 = require("../utils/lightning"); |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
console.log("=> env:", env); |
|||
let controllers = { |
|||
messages: require('./messages'), |
|||
invoices: require('./invoices'), |
|||
uploads: require('./uploads'), |
|||
contacts: require('./contacts'), |
|||
invites: require('./invites'), |
|||
payments: require('./payment'), |
|||
details: require('./details'), |
|||
chats: require('./chats'), |
|||
subcriptions: require('./subscriptions'), |
|||
media: require('./media'), |
|||
}; |
|||
function iniGrpcSubscriptions() { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
try { |
|||
yield lightning_1.checkConnection(); |
|||
const types = constants.message_types; |
|||
yield lndService.subscribeInvoices({ |
|||
[types.contact_key]: controllers.contacts.receiveContactKey, |
|||
[types.contact_key_confirmation]: controllers.contacts.receiveConfirmContactKey, |
|||
[types.message]: controllers.messages.receiveMessage, |
|||
[types.invoice]: controllers.invoices.receiveInvoice, |
|||
[types.direct_payment]: controllers.payments.receivePayment, |
|||
[types.confirmation]: controllers.messages.receiveConfirmation, |
|||
[types.attachment]: controllers.media.receiveAttachment, |
|||
[types.purchase]: controllers.media.receivePurchase, |
|||
[types.purchase_accept]: controllers.media.receivePurchaseAccept, |
|||
[types.purchase_deny]: controllers.media.receivePurchaseDeny, |
|||
[types.group_create]: controllers.chats.receiveGroupCreateOrInvite, |
|||
[types.group_invite]: controllers.chats.receiveGroupCreateOrInvite, |
|||
[types.group_join]: controllers.chats.receiveGroupJoin, |
|||
[types.group_leave]: controllers.chats.receiveGroupLeave, |
|||
}); |
|||
} |
|||
catch (e) { |
|||
throw e; |
|||
} |
|||
}); |
|||
} |
|||
exports.iniGrpcSubscriptions = iniGrpcSubscriptions; |
|||
function set(app) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
if (models_1.models && models_1.models.Subscription) { |
|||
controllers.subcriptions.initializeCronJobs(); |
|||
} |
|||
try { |
|||
yield controllers.media.cycleMediaToken(); |
|||
} |
|||
catch (e) { |
|||
console.log('=> could not auth with media server', e.message); |
|||
} |
|||
app.get('/chats', controllers.chats.getChats); |
|||
app.post('/group', controllers.chats.createGroupChat); |
|||
app.post('/chats/:chat_id/:mute_unmute', controllers.chats.mute); |
|||
app.delete('/chat/:id', controllers.chats.deleteChat); |
|||
app.put('/chat/:id', controllers.chats.addGroupMembers); |
|||
app.post('/contacts/tokens', controllers.contacts.generateToken); |
|||
app.post('/upload', controllers.uploads.avatarUpload.single('file'), controllers.uploads.uploadFile); |
|||
app.post('/invites', controllers.invites.createInvite); |
|||
app.post('/invites/:invite_string/pay', controllers.invites.payInvite); |
|||
app.post('/invites/finish', controllers.invites.finishInvite); |
|||
app.get('/contacts', controllers.contacts.getContacts); |
|||
app.put('/contacts/:id', controllers.contacts.updateContact); |
|||
app.post('/contacts/:id/keys', controllers.contacts.exchangeKeys); |
|||
app.post('/contacts', controllers.contacts.createContact); |
|||
app.delete('/contacts/:id', controllers.contacts.deleteContact); |
|||
app.get('/messages', controllers.messages.getMessages); |
|||
app.post('/messages', controllers.messages.sendMessage); |
|||
app.post('/messages/:chat_id/read', controllers.messages.readMessages); |
|||
app.post('/messages/clear', controllers.messages.clearMessages); |
|||
app.get('/subscriptions', controllers.subcriptions.getAllSubscriptions); |
|||
app.get('/subscription/:id', controllers.subcriptions.getSubscription); |
|||
app.delete('/subscription/:id', controllers.subcriptions.deleteSubscription); |
|||
app.post('/subscriptions', controllers.subcriptions.createSubscription); |
|||
app.put('/subscription/:id', controllers.subcriptions.editSubscription); |
|||
app.get('/subscriptions/contact/:contactId', controllers.subcriptions.getSubscriptionsForContact); |
|||
app.put('/subscription/:id/pause', controllers.subcriptions.pauseSubscription); |
|||
app.put('/subscription/:id/restart', controllers.subcriptions.restartSubscription); |
|||
app.post('/attachment', controllers.media.sendAttachmentMessage); |
|||
app.post('/purchase', controllers.media.purchase); |
|||
app.get('/signer/:challenge', controllers.media.signer); |
|||
app.post('/invoices', controllers.invoices.createInvoice); |
|||
app.get('/invoices', controllers.invoices.listInvoices); |
|||
app.put('/invoices', controllers.invoices.payInvoice); |
|||
app.post('/invoices/cancel', controllers.invoices.cancelInvoice); |
|||
app.post('/payment', controllers.payments.sendPayment); |
|||
app.get('/payments', controllers.payments.listPayments); |
|||
app.get('/channels', controllers.details.getChannels); |
|||
app.get('/balance', controllers.details.getBalance); |
|||
app.get('/balance/all', controllers.details.getLocalRemoteBalance); |
|||
app.get('/getinfo', controllers.details.getInfo); |
|||
app.get('/logs', controllers.details.getLogsSince); |
|||
app.get('/info', controllers.details.getNodeInfo); |
|||
app.get('/version', function (req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const version = yield gitinfo_1.checkTag(); |
|||
res.send({ version }); |
|||
}); |
|||
}); |
|||
if (env != "production") { // web dashboard login
|
|||
app.post('/login', login); |
|||
} |
|||
}); |
|||
} |
|||
exports.set = set; |
|||
const login = (req, res) => { |
|||
const { code } = req.body; |
|||
if (code == "sphinx") { |
|||
models_1.models.Contact.findOne({ where: { isOwner: true } }).then(owner => { |
|||
res.status(200); |
|||
res.json({ success: true, token: owner.authToken }); |
|||
res.end(); |
|||
}); |
|||
} |
|||
else { |
|||
res.status(200); |
|||
res.json({ success: false }); |
|||
res.end(); |
|||
} |
|||
}; |
|||
//# sourceMappingURL=index.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,101 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const crypto = require("crypto"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const hub_1 = require("../hub"); |
|||
const finishInvite = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { invite_string } = req.body; |
|||
const params = { |
|||
invite: { |
|||
pin: invite_string |
|||
} |
|||
}; |
|||
function onSuccess() { |
|||
res.status(200); |
|||
res.json({ success: true }); |
|||
res.end(); |
|||
} |
|||
function onFailure() { |
|||
res.status(200); |
|||
res.json({ success: false }); |
|||
res.end(); |
|||
} |
|||
hub_1.finishInviteInHub(params, onSuccess, onFailure); |
|||
}); |
|||
exports.finishInvite = finishInvite; |
|||
const payInvite = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const params = { |
|||
node_ip: process.env.NODE_IP |
|||
}; |
|||
const invite_string = req.params['invite_string']; |
|||
const onSuccess = (response) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const invite = response.object; |
|||
console.log("response", invite); |
|||
const dbInvite = yield models_1.models.Invite.findOne({ where: { inviteString: invite.pin } }); |
|||
if (dbInvite.status != invite.invite_status) { |
|||
dbInvite.update({ status: invite.invite_status }); |
|||
} |
|||
res.status(200); |
|||
res.json({ success: true, response: { invite: jsonUtils.inviteToJson(dbInvite) } }); |
|||
res.end(); |
|||
}); |
|||
const onFailure = (response) => { |
|||
res.status(200); |
|||
res.json({ success: false }); |
|||
res.end(); |
|||
}; |
|||
hub_1.payInviteInHub(invite_string, params, onSuccess, onFailure); |
|||
}); |
|||
exports.payInvite = payInvite; |
|||
const createInvite = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { nickname, welcome_message } = req.body; |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const params = { |
|||
invite: { |
|||
nickname: owner.alias, |
|||
pubkey: owner.publicKey, |
|||
contact_nickname: nickname, |
|||
message: welcome_message, |
|||
pin: crypto.randomBytes(20).toString('hex') |
|||
} |
|||
}; |
|||
const onSuccess = (response) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log("response", response); |
|||
const inviteCreated = response.object; |
|||
const contact = yield models_1.models.Contact.create({ |
|||
alias: nickname, |
|||
status: 0 |
|||
}); |
|||
const invite = yield models_1.models.Invite.create({ |
|||
welcomeMessage: inviteCreated.message, |
|||
contactId: contact.id, |
|||
status: inviteCreated.invite_status, |
|||
inviteString: inviteCreated.pin |
|||
}); |
|||
let contactJson = jsonUtils.contactToJson(contact); |
|||
if (invite) { |
|||
contactJson.invite = jsonUtils.inviteToJson(invite); |
|||
} |
|||
res.status(200); |
|||
res.json({ success: true, contact: contactJson }); |
|||
res.end(); |
|||
}); |
|||
const onFailure = (response) => { |
|||
res.status(200); |
|||
res.json(response); |
|||
res.end(); |
|||
}; |
|||
hub_1.createInviteInHub(params, onSuccess, onFailure); |
|||
}); |
|||
exports.createInvite = createInvite; |
|||
//# sourceMappingURL=invites.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"invites.js","sourceRoot":"","sources":["../../../api/controllers/invites.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,sCAAgC;AAChC,iCAAgC;AAChC,2CAA0C;AAC1C,gCAA2E;AAE3E,MAAM,YAAY,GAAG,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IACvC,MAAM,EACL,aAAa,EACX,GAAG,GAAG,CAAC,IAAI,CAAA;IACd,MAAM,MAAM,GAAG;QACd,MAAM,EAAE;YACP,GAAG,EAAE,aAAa;SAClB;KACD,CAAA;IAED,SAAS,SAAS;QACjB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;QAC3B,GAAG,CAAC,GAAG,EAAE,CAAA;IACV,CAAC;IACD,SAAS,SAAS;QACjB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;QAC5B,GAAG,CAAC,GAAG,EAAE,CAAA;IACV,CAAC;IAEE,uBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;AACnD,CAAC,CAAA,CAAA;AAwFA,oCAAY;AAtFb,MAAM,SAAS,GAAG,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IACpC,MAAM,MAAM,GAAG;QACd,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO;KAC5B,CAAA;IAED,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;IAEjD,MAAM,SAAS,GAAG,CAAO,QAAQ,EAAE,EAAE;QACpC,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAA;QAE9B,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAA;QAE/B,MAAM,QAAQ,GAAG,MAAM,eAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,YAAY,EAAE,MAAM,CAAC,GAAG,EAAE,EAAC,CAAC,CAAA;QAEpF,IAAI,QAAQ,CAAC,MAAM,IAAI,MAAM,CAAC,aAAa,EAAE;YAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,CAAC,CAAA;SACjD;QAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAA;QACnF,GAAG,CAAC,GAAG,EAAE,CAAA;IACV,CAAC,CAAA,CAAA;IAED,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,EAAE;QAC9B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;QAC5B,GAAG,CAAC,GAAG,EAAE,CAAA;IACV,CAAC,CAAA;IAEE,oBAAc,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;AAC/D,CAAC,CAAA,CAAA;AAyDA,8BAAS;AAvDV,MAAM,YAAY,GAAG,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IACvC,MAAM,EACL,QAAQ,EACR,eAAe,EACd,GAAG,GAAG,CAAC,IAAI,CAAA;IAEZ,MAAM,KAAK,GAAG,MAAM,eAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,EAAC,CAAC,CAAA;IAExE,MAAM,MAAM,GAAG;QACd,MAAM,EAAE;YACP,QAAQ,EAAE,KAAK,CAAC,KAAK;YACrB,MAAM,EAAE,KAAK,CAAC,SAAS;YACvB,gBAAgB,EAAE,QAAQ;YAC1B,OAAO,EAAE,eAAe;YACxB,GAAG,EAAE,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;SAC3C;KACD,CAAA;IAED,MAAM,SAAS,GAAG,CAAO,QAAQ,EAAE,EAAE;QACpC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;QAEjC,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAA;QAErC,MAAM,OAAO,GAAG,MAAM,eAAM,CAAC,OAAO,CAAC,MAAM,CAAC;YAC3C,KAAK,EAAE,QAAQ;YACf,MAAM,EAAE,CAAC;SACT,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,MAAM,eAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YACzC,cAAc,EAAE,aAAa,CAAC,OAAO;YACrC,SAAS,EAAE,OAAO,CAAC,EAAE;YACrB,MAAM,EAAE,aAAa,CAAC,aAAa;YACnC,YAAY,EAAE,aAAa,CAAC,GAAG;SAC/B,CAAC,CAAA;QACF,IAAI,WAAW,GAAG,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;QAClD,IAAI,MAAM,EAAE;YACX,WAAW,CAAC,MAAM,GAAG,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,CAAA;SACnD;QAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAA;QACjD,GAAG,CAAC,GAAG,EAAE,CAAA;IACV,CAAC,CAAA,CAAA;IAED,MAAM,SAAS,GAAG,CAAC,QAAQ,EAAE,EAAE;QAC9B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACf,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;QAClB,GAAG,CAAC,GAAG,EAAE,CAAA;IACV,CAAC,CAAA;IAEE,uBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;AACnD,CAAC,CAAA,CAAA;AAGA,oCAAY"} |
@ -0,0 +1,241 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const lightning_1 = require("../utils/lightning"); |
|||
const socket = require("../utils/socket"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const decodeUtils = require("../utils/decode"); |
|||
const helpers = require("../helpers"); |
|||
const hub_1 = require("../hub"); |
|||
const res_1 = require("../utils/res"); |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
const payInvoice = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const lightning = yield lightning_1.loadLightning(); |
|||
const { payment_request } = req.body; |
|||
var call = lightning.sendPayment({}); |
|||
call.on('data', (response) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('[pay invoice data]', response); |
|||
const message = yield models_1.models.Message.findOne({ where: { payment_request } }); |
|||
if (!message) { // invoice still paid
|
|||
return res_1.success(res, { |
|||
success: true, |
|||
response: { payment_request } |
|||
}); |
|||
} |
|||
message.status = constants.statuses.confirmed; |
|||
message.save(); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const chat = yield models_1.models.Chat.findOne({ where: { id: message.chatId } }); |
|||
const contactIds = JSON.parse(chat.contactIds); |
|||
const senderId = contactIds.find(id => id != message.sender); |
|||
const paidMessage = yield models_1.models.Message.create({ |
|||
chatId: message.chatId, |
|||
sender: senderId, |
|||
type: constants.message_types.payment, |
|||
amount: message.amount, |
|||
amountMsat: message.amountMsat, |
|||
paymentHash: message.paymentHash, |
|||
date: date, |
|||
expirationDate: null, |
|||
messageContent: null, |
|||
status: constants.statuses.confirmed, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}); |
|||
console.log('[pay invoice] stored message', paidMessage); |
|||
res_1.success(res, jsonUtils.messageToJson(paidMessage, chat)); |
|||
})); |
|||
call.write({ payment_request }); |
|||
}); |
|||
exports.payInvoice = payInvoice; |
|||
const cancelInvoice = (req, res) => { |
|||
res.status(200); |
|||
res.json({ success: false }); |
|||
res.end(); |
|||
}; |
|||
exports.cancelInvoice = cancelInvoice; |
|||
const createInvoice = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const lightning = yield lightning_1.loadLightning(); |
|||
const { amount, memo, remote_memo, chat_id, contact_id } = req.body; |
|||
var request = { |
|||
value: amount, |
|||
memo: remote_memo || memo |
|||
}; |
|||
if (amount == null) { |
|||
res.status(200); |
|||
res.json({ err: "no amount specified", }); |
|||
res.end(); |
|||
} |
|||
else { |
|||
lightning.addInvoice(request, function (err, response) { |
|||
console.log({ err, response }); |
|||
if (err == null) { |
|||
const { payment_request } = response; |
|||
if (!contact_id && !chat_id) { // if no contact
|
|||
res_1.success(res, { |
|||
invoice: payment_request |
|||
}); |
|||
return; // end here
|
|||
} |
|||
lightning.decodePayReq({ pay_req: payment_request }, (error, invoice) => __awaiter(this, void 0, void 0, function* () { |
|||
if (res) { |
|||
console.log('decoded pay req', { invoice }); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const chat = yield helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id |
|||
}); |
|||
let timestamp = parseInt(invoice.timestamp + '000'); |
|||
let expiry = parseInt(invoice.expiry + '000'); |
|||
if (error) { |
|||
res.status(200); |
|||
res.json({ success: false, error }); |
|||
res.end(); |
|||
} |
|||
else { |
|||
const message = yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.invoice, |
|||
amount: parseInt(invoice.num_satoshis), |
|||
amountMsat: parseInt(invoice.num_satoshis) * 1000, |
|||
paymentHash: invoice.payment_hash, |
|||
paymentRequest: payment_request, |
|||
date: new Date(timestamp), |
|||
expirationDate: new Date(timestamp + expiry), |
|||
messageContent: memo, |
|||
remoteMessageContent: remote_memo, |
|||
status: constants.statuses.pending, |
|||
createdAt: new Date(timestamp), |
|||
updatedAt: new Date(timestamp) |
|||
}); |
|||
res_1.success(res, jsonUtils.messageToJson(message, chat)); |
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.invoice, |
|||
message: { |
|||
id: message.id, |
|||
invoice: message.paymentRequest |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
else { |
|||
console.log('error decoding pay req', { err, res }); |
|||
res.status(500); |
|||
res.json({ err, res }); |
|||
res.end(); |
|||
} |
|||
})); |
|||
} |
|||
else { |
|||
console.log({ err, response }); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
exports.createInvoice = createInvoice; |
|||
const listInvoices = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const lightning = yield lightning_1.loadLightning(); |
|||
lightning.listInvoices({}, (err, response) => { |
|||
console.log({ err, response }); |
|||
if (err == null) { |
|||
res.status(200); |
|||
res.json(response); |
|||
res.end(); |
|||
} |
|||
else { |
|||
console.log({ err, response }); |
|||
} |
|||
}); |
|||
}); |
|||
exports.listInvoices = listInvoices; |
|||
const receiveInvoice = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('received invoice', payload); |
|||
const total_spent = 1; |
|||
const dat = payload.content || payload; |
|||
const payment_request = dat.message.invoice; |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const { owner, sender, chat, msg_id } = yield helpers.parseReceiveParams(payload); |
|||
if (!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!'); |
|||
} |
|||
const { memo, sat, msat, paymentHash, invoiceDate, expirationSeconds } = decodePaymentRequest(payment_request); |
|||
const message = yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
type: constants.message_types.invoice, |
|||
sender: sender.id, |
|||
amount: sat, |
|||
amountMsat: msat, |
|||
paymentRequest: payment_request, |
|||
asciiEncodedTotal: total_spent, |
|||
paymentHash: paymentHash, |
|||
messageContent: memo, |
|||
expirationDate: new Date(invoiceDate + expirationSeconds), |
|||
date: new Date(invoiceDate), |
|||
status: constants.statuses.pending, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}); |
|||
console.log('received keysend invoice message', message.id); |
|||
socket.sendJson({ |
|||
type: 'invoice', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
hub_1.sendNotification(chat, sender.alias, 'message'); |
|||
sendConfirmation({ chat, sender: owner, msg_id }); |
|||
}); |
|||
exports.receiveInvoice = receiveInvoice; |
|||
const sendConfirmation = ({ chat, sender, msg_id }) => { |
|||
helpers.sendMessage({ |
|||
chat, |
|||
sender, |
|||
message: { id: msg_id }, |
|||
type: constants.message_types.confirmation, |
|||
}); |
|||
}; |
|||
// lnd invoice stuff
|
|||
function decodePaymentRequest(paymentRequest) { |
|||
var decodedPaymentRequest = decodeUtils.decode(paymentRequest); |
|||
var expirationSeconds = 3600; |
|||
var paymentHash = ""; |
|||
var memo = ""; |
|||
for (var i = 0; i < decodedPaymentRequest.data.tags.length; i++) { |
|||
let tag = decodedPaymentRequest.data.tags[i]; |
|||
if (tag) { |
|||
if (tag.description == 'payment_hash') { |
|||
paymentHash = tag.value; |
|||
} |
|||
else if (tag.description == 'description') { |
|||
memo = tag.value; |
|||
} |
|||
else if (tag.description == 'expiry') { |
|||
expirationSeconds = tag.value; |
|||
} |
|||
} |
|||
} |
|||
expirationSeconds = parseInt(expirationSeconds.toString() + '000'); |
|||
let invoiceDate = parseInt(decodedPaymentRequest.data.time_stamp.toString() + '000'); |
|||
let amount = decodedPaymentRequest['human_readable_part']['amount']; |
|||
var msat = 0; |
|||
var sat = 0; |
|||
if (Number.isInteger(amount)) { |
|||
msat = amount; |
|||
sat = amount / 1000; |
|||
} |
|||
return { sat, msat, paymentHash, invoiceDate, expirationSeconds, memo }; |
|||
} |
|||
//# sourceMappingURL=invoices.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,481 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const socket = require("../utils/socket"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const resUtils = require("../utils/res"); |
|||
const helpers = require("../helpers"); |
|||
const hub_1 = require("../hub"); |
|||
const lightning_1 = require("../utils/lightning"); |
|||
const rp = require("request-promise"); |
|||
const lightning_2 = require("../utils/lightning"); |
|||
const ldat_1 = require("../utils/ldat"); |
|||
const cron_1 = require("cron"); |
|||
const zbase32 = require("../utils/zbase32"); |
|||
const schemas = require("./schemas"); |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/app.json')[env]; |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
/* |
|||
|
|||
TODO line 233: parse that from token itself, dont use getMediaInfo at all |
|||
|
|||
"attachment": sends a message to a chat with a signed receipt for a file, which can be accessed from sphinx-meme server |
|||
If the attachment has a price, then the media must be purchased to get the receipt |
|||
|
|||
"purchase" sends sats. |
|||
if the amount matches the price, the media owner |
|||
will respond ("purchase_accept" or "purchase_deny" type) |
|||
with the signed token, which can only be used by the buyer |
|||
|
|||
purchase_accept should update the original attachment message with the terms and receipt |
|||
(both Relay and client need to do this) or make new??? |
|||
|
|||
purchase_deny returns the sats |
|||
*/ |
|||
const sendAttachmentMessage = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
// try {
|
|||
// schemas.attachment.validateSync(req.body)
|
|||
// } catch(e) {
|
|||
// return resUtils.failure(res, e.message)
|
|||
// }
|
|||
const { chat_id, contact_id, muid, text, remote_text, remote_text_map, media_key_map, media_type, file_name, ttl, price, } = req.body; |
|||
console.log('[send attachment]', req.body); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const chat = yield helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id |
|||
}); |
|||
let TTL = ttl; |
|||
if (ttl) { |
|||
TTL = parseInt(ttl); |
|||
} |
|||
if (!TTL) |
|||
TTL = 31536000; // default year
|
|||
const amt = price || 0; |
|||
// generate media token for self!
|
|||
const myMediaToken = yield ldat_1.tokenFromTerms({ |
|||
muid, ttl: TTL, host: '', |
|||
pubkey: owner.publicKey, |
|||
meta: Object.assign(Object.assign({}, amt && { amt }), { ttl }) |
|||
}); |
|||
const date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const myMediaKey = (media_key_map && media_key_map[owner.id]) || ''; |
|||
const mediaType = media_type || ''; |
|||
const remoteMessageContent = remote_text_map ? JSON.stringify(remote_text_map) : remote_text; |
|||
const message = yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.attachment, |
|||
status: constants.statuses.pending, |
|||
messageContent: text || file_name || '', |
|||
remoteMessageContent, |
|||
mediaToken: myMediaToken, |
|||
mediaKey: myMediaKey, |
|||
mediaType: mediaType, |
|||
date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}); |
|||
saveMediaKeys(muid, media_key_map, chat.id, message.id); |
|||
const mediaTerms = { |
|||
muid, ttl: TTL, |
|||
meta: Object.assign({}, amt && { amt }), |
|||
skipSigning: amt ? true : false // only sign if its free
|
|||
}; |
|||
const msg = { |
|||
mediaTerms, |
|||
id: message.id, |
|||
content: remote_text_map || remote_text || text || file_name || '', |
|||
mediaKey: media_key_map, |
|||
mediaType: mediaType, |
|||
}; |
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.attachment, |
|||
message: msg, |
|||
success: (data) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('attachment sent', { data }); |
|||
resUtils.success(res, jsonUtils.messageToJson(message, chat)); |
|||
}), |
|||
failure: error => resUtils.failure(res, error.message), |
|||
}); |
|||
}); |
|||
exports.sendAttachmentMessage = sendAttachmentMessage; |
|||
function saveMediaKeys(muid, mediaKeyMap, chatId, messageId) { |
|||
if (typeof mediaKeyMap !== 'object') { |
|||
console.log('wrong type for mediaKeyMap'); |
|||
return; |
|||
} |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
for (let [contactId, key] of Object.entries(mediaKeyMap)) { |
|||
models_1.models.MediaKey.create({ |
|||
muid, chatId, contactId, key, messageId, |
|||
createdAt: date, |
|||
}); |
|||
} |
|||
} |
|||
const purchase = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { chat_id, contact_id, amount, mediaToken, } = req.body; |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
try { |
|||
schemas.purchase.validateSync(req.body); |
|||
} |
|||
catch (e) { |
|||
return resUtils.failure(res, e.message); |
|||
} |
|||
console.log('purchase!'); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const chat = yield helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id |
|||
}); |
|||
const message = yield models_1.models.Message.create({ |
|||
sender: owner.id, |
|||
type: constants.message_types.purchase, |
|||
mediaToken: mediaToken, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}); |
|||
const msg = { |
|||
amount, mediaToken, id: message.id, |
|||
}; |
|||
helpers.sendMessage({ |
|||
chat: Object.assign(Object.assign({}, chat), { contactIds: [contact_id] }), |
|||
sender: owner, |
|||
type: constants.message_types.purchase, |
|||
message: msg, |
|||
success: (data) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('purchase sent', { data }); |
|||
resUtils.success(res, jsonUtils.messageToJson(message)); |
|||
}), |
|||
failure: error => resUtils.failure(res, error.message), |
|||
}); |
|||
}); |
|||
exports.purchase = purchase; |
|||
/* RECEIVERS */ |
|||
const receivePurchase = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('received purchase', { payload }); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const { owner, sender, chat, amount, mediaToken } = yield helpers.parseReceiveParams(payload); |
|||
if (!owner || !sender || !chat) { |
|||
return console.log('=> group chat not found!'); |
|||
} |
|||
yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: sender.id, |
|||
type: constants.message_types.purchase, |
|||
mediaToken: mediaToken, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}); |
|||
const muid = mediaToken && mediaToken.split('.').length && mediaToken.split('.')[1]; |
|||
if (!muid) { |
|||
return console.log('no muid'); |
|||
} |
|||
const ogMessage = models_1.models.Message.findOne({ |
|||
where: { mediaToken } |
|||
}); |
|||
if (!ogMessage) { |
|||
return console.log('no original message'); |
|||
} |
|||
// find mediaKey for who sent
|
|||
const mediaKey = models_1.models.MediaKey.findOne({ where: { |
|||
muid, receiver: sender.id, |
|||
} }); |
|||
const terms = ldat_1.parseLDAT(mediaToken); |
|||
// get info
|
|||
let TTL = terms.meta && terms.meta.ttl; |
|||
let price = terms.meta && terms.meta.amt; |
|||
if (!TTL || !price) { |
|||
const media = yield getMediaInfo(muid); |
|||
console.log("GOT MEDIA", media); |
|||
if (media) { |
|||
TTL = media.ttl && parseInt(media.ttl); |
|||
price = media.price; |
|||
} |
|||
if (!TTL) |
|||
TTL = 31536000; |
|||
if (!price) |
|||
price = 0; |
|||
} |
|||
if (amount < price) { // didnt pay enough
|
|||
return helpers.sendMessage({ |
|||
chat: Object.assign(Object.assign({}, chat), { contactIds: [sender.id] }), |
|||
sender: owner, |
|||
amount: amount, |
|||
type: constants.message_types.purchase_deny, |
|||
message: { amount, content: 'Payment Denied' }, |
|||
success: (data) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('purchase_deny sent', { data }); |
|||
}), |
|||
failure: error => console.log('=> couldnt send purcahse deny', error), |
|||
}); |
|||
} |
|||
const acceptTerms = { |
|||
muid, ttl: TTL, |
|||
meta: { amt: amount }, |
|||
}; |
|||
helpers.sendMessage({ |
|||
chat: Object.assign(Object.assign({}, chat), { contactIds: [sender.id] }), |
|||
sender: owner, |
|||
type: constants.message_types.purchase_accept, |
|||
message: { |
|||
mediaTerms: acceptTerms, |
|||
mediaKey: mediaKey.key, |
|||
mediaType: ogMessage.mediaType, |
|||
}, |
|||
success: (data) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('purchase_accept sent', { data }); |
|||
}), |
|||
failure: error => console.log('=> couldnt send purchase accept', error), |
|||
}); |
|||
}); |
|||
exports.receivePurchase = receivePurchase; |
|||
const receivePurchaseAccept = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const { owner, sender, chat, mediaToken, mediaKey, mediaType } = yield helpers.parseReceiveParams(payload); |
|||
if (!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!'); |
|||
} |
|||
const termsArray = mediaToken.split('.'); |
|||
// const host = termsArray[0]
|
|||
const muid = termsArray[1]; |
|||
if (!muid) { |
|||
return console.log('wtf no muid'); |
|||
} |
|||
// const attachmentMessage = await models.Message.findOne({where:{
|
|||
// mediaToken: {$like: `${host}.${muid}%`}
|
|||
// }})
|
|||
// if(attachmentMessage){
|
|||
// console.log('=> updated msg!')
|
|||
// attachmentMessage.update({
|
|||
// mediaToken, mediaKey
|
|||
// })
|
|||
// }
|
|||
const msg = yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: sender.id, |
|||
type: constants.message_types.purchase_accept, |
|||
status: constants.statuses.received, |
|||
mediaToken, |
|||
mediaKey, |
|||
mediaType, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}); |
|||
socket.sendJson({ |
|||
type: 'purchase_accept', |
|||
response: jsonUtils.messageToJson(msg, chat) |
|||
}); |
|||
}); |
|||
exports.receivePurchaseAccept = receivePurchaseAccept; |
|||
const receivePurchaseDeny = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const { owner, sender, chat, amount, mediaToken } = yield helpers.parseReceiveParams(payload); |
|||
if (!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!'); |
|||
} |
|||
const msg = yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: sender.id, |
|||
type: constants.message_types.purchase_deny, |
|||
status: constants.statuses.received, |
|||
messageContent: 'Purchase has been denied and sats returned to you', |
|||
amount: amount, |
|||
amountMsat: parseFloat(amount) * 1000, |
|||
mediaToken, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}); |
|||
socket.sendJson({ |
|||
type: 'purchase_deny', |
|||
response: jsonUtils.messageToJson(msg, chat) |
|||
}); |
|||
}); |
|||
exports.receivePurchaseDeny = receivePurchaseDeny; |
|||
const receiveAttachment = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('received attachment', { payload }); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const { owner, sender, chat, mediaToken, mediaKey, mediaType, content, msg_id } = yield helpers.parseReceiveParams(payload); |
|||
if (!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!'); |
|||
} |
|||
const msg = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.attachment, |
|||
sender: sender.id, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}; |
|||
if (content) |
|||
msg.messageContent = content; |
|||
if (mediaToken) |
|||
msg.mediaToken = mediaToken; |
|||
if (mediaKey) |
|||
msg.mediaKey = mediaKey; |
|||
if (mediaType) |
|||
msg.mediaType = mediaType; |
|||
const message = yield models_1.models.Message.create(msg); |
|||
console.log('saved attachment', message.dataValues); |
|||
socket.sendJson({ |
|||
type: 'attachment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
hub_1.sendNotification(chat, sender.alias, 'message'); |
|||
sendConfirmation({ chat, sender: owner, msg_id }); |
|||
}); |
|||
exports.receiveAttachment = receiveAttachment; |
|||
const sendConfirmation = ({ chat, sender, msg_id }) => { |
|||
helpers.sendMessage({ |
|||
chat, |
|||
sender, |
|||
message: { id: msg_id }, |
|||
type: constants.message_types.confirmation, |
|||
}); |
|||
}; |
|||
function signer(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
if (!req.params.challenge) |
|||
return resUtils.failure(res, "no challenge"); |
|||
try { |
|||
const sig = yield lightning_1.signBuffer(Buffer.from(req.params.challenge, 'base64')); |
|||
const sigBytes = zbase32.decode(sig); |
|||
const sigBase64 = ldat_1.urlBase64FromBytes(sigBytes); |
|||
resUtils.success(res, { |
|||
sig: sigBase64 |
|||
}); |
|||
} |
|||
catch (e) { |
|||
resUtils.failure(res, e); |
|||
} |
|||
}); |
|||
} |
|||
exports.signer = signer; |
|||
function verifier(msg, sig) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
try { |
|||
const res = yield lightning_1.verifyMessage(msg, sig); |
|||
return res; |
|||
} |
|||
catch (e) { |
|||
console.log(e); |
|||
} |
|||
}); |
|||
} |
|||
exports.verifier = verifier; |
|||
function getMyPubKey() { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
return new Promise((resolve, reject) => { |
|||
const lightning = lightning_2.loadLightning(); |
|||
var request = {}; |
|||
lightning.getInfo(request, function (err, response) { |
|||
if (err) |
|||
reject(err); |
|||
if (!response.identity_pubkey) |
|||
reject('no pub key'); |
|||
else |
|||
resolve(response.identity_pubkey); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
function cycleMediaToken() { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
try { |
|||
if (process.env.TEST_LDAT) |
|||
ldat_1.testLDAT(); |
|||
const mt = yield getMediaToken(null); |
|||
if (mt) |
|||
console.log('=> [meme] authed!'); |
|||
new cron_1.CronJob('1 * * * *', function () { |
|||
getMediaToken(true); |
|||
}); |
|||
} |
|||
catch (e) { |
|||
console.log(e.message); |
|||
} |
|||
}); |
|||
} |
|||
exports.cycleMediaToken = cycleMediaToken; |
|||
const mediaURL = 'http://' + config.media_host + '/'; |
|||
let mediaToken; |
|||
function getMediaToken(force) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
if (!force && mediaToken) |
|||
return mediaToken; |
|||
yield helpers.sleep(3000); |
|||
try { |
|||
const res = yield rp.get(mediaURL + 'ask'); |
|||
const r = JSON.parse(res); |
|||
if (!(r && r.challenge && r.id)) { |
|||
throw new Error('no challenge'); |
|||
} |
|||
const sig = yield lightning_1.signBuffer(Buffer.from(r.challenge, 'base64')); |
|||
if (!sig) |
|||
throw new Error('no signature'); |
|||
const pubkey = yield getMyPubKey(); |
|||
if (!pubkey) { |
|||
throw new Error('no pub key!'); |
|||
} |
|||
const sigBytes = zbase32.decode(sig); |
|||
const sigBase64 = ldat_1.urlBase64FromBytes(sigBytes); |
|||
const bod = yield rp.post(mediaURL + 'verify', { |
|||
form: { id: r.id, sig: sigBase64, pubkey } |
|||
}); |
|||
const body = JSON.parse(bod); |
|||
if (!(body && body.token)) { |
|||
throw new Error('no token'); |
|||
} |
|||
mediaToken = body.token; |
|||
return body.token; |
|||
} |
|||
catch (e) { |
|||
throw e; |
|||
} |
|||
}); |
|||
} |
|||
exports.getMediaToken = getMediaToken; |
|||
function getMediaInfo(muid) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
try { |
|||
const token = yield getMediaToken(null); |
|||
const res = yield rp.get(mediaURL + 'mymedia/' + muid, { |
|||
headers: { |
|||
'Authorization': `Bearer ${token}`, |
|||
'Content-Type': 'application/json' |
|||
}, |
|||
json: true |
|||
}); |
|||
return res; |
|||
} |
|||
catch (e) { |
|||
return null; |
|||
} |
|||
}); |
|||
} |
|||
//# sourceMappingURL=media.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,239 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const sequelize_1 = require("sequelize"); |
|||
const underscore_1 = require("underscore"); |
|||
const hub_1 = require("../hub"); |
|||
const socket = require("../utils/socket"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const helpers = require("../helpers"); |
|||
const res_1 = require("../utils/res"); |
|||
const lock_1 = require("../utils/lock"); |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
const getMessages = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const dateToReturn = req.query.date; |
|||
if (!dateToReturn) { |
|||
return getAllMessages(req, res); |
|||
} |
|||
console.log(dateToReturn); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
// const chatId = req.query.chat_id
|
|||
let newMessagesWhere = { |
|||
date: { [sequelize_1.Op.gte]: dateToReturn }, |
|||
[sequelize_1.Op.or]: [ |
|||
{ receiver: owner.id }, |
|||
{ receiver: null } |
|||
] |
|||
}; |
|||
let confirmedMessagesWhere = { |
|||
updated_at: { [sequelize_1.Op.gte]: dateToReturn }, |
|||
status: constants.statuses.received, |
|||
sender: owner.id |
|||
}; |
|||
// if (chatId) {
|
|||
// newMessagesWhere.chat_id = chatId
|
|||
// confirmedMessagesWhere.chat_id = chatId
|
|||
// }
|
|||
const newMessages = yield models_1.models.Message.findAll({ where: newMessagesWhere }); |
|||
const confirmedMessages = yield models_1.models.Message.findAll({ where: confirmedMessagesWhere }); |
|||
const chatIds = []; |
|||
newMessages.forEach(m => { |
|||
if (!chatIds.includes(m.chatId)) |
|||
chatIds.push(m.chatId); |
|||
}); |
|||
confirmedMessages.forEach(m => { |
|||
if (!chatIds.includes(m.chatId)) |
|||
chatIds.push(m.chatId); |
|||
}); |
|||
let chats = chatIds.length > 0 ? yield models_1.models.Chat.findAll({ where: { deleted: false, id: chatIds } }) : []; |
|||
const chatsById = underscore_1.indexBy(chats, 'id'); |
|||
res.json({ |
|||
success: true, |
|||
response: { |
|||
new_messages: newMessages.map(message => jsonUtils.messageToJson(message, chatsById[parseInt(message.chatId)])), |
|||
confirmed_messages: confirmedMessages.map(message => jsonUtils.messageToJson(message, chatsById[parseInt(message.chatId)])) |
|||
} |
|||
}); |
|||
res.status(200); |
|||
res.end(); |
|||
}); |
|||
exports.getMessages = getMessages; |
|||
const getAllMessages = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const messages = yield models_1.models.Message.findAll({ order: [['id', 'asc']] }); |
|||
const chatIds = messages.map(m => m.chatId); |
|||
console.log('=> getAllMessages, chatIds', chatIds); |
|||
let chats = chatIds.length > 0 ? yield models_1.models.Chat.findAll({ where: { deleted: false, id: chatIds } }) : []; |
|||
const chatsById = underscore_1.indexBy(chats, 'id'); |
|||
res_1.success(res, { |
|||
new_messages: messages.map(message => jsonUtils.messageToJson(message, chatsById[parseInt(message.chatId)])), |
|||
confirmed_messages: [] |
|||
}); |
|||
}); |
|||
const sendMessage = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
// try {
|
|||
// schemas.message.validateSync(req.body)
|
|||
// } catch(e) {
|
|||
// return failure(res, e.message)
|
|||
// }
|
|||
const { contact_id, text, remote_text, chat_id, remote_text_map, } = req.body; |
|||
console.log('[sendMessage]'); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const chat = yield helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id, |
|||
}); |
|||
const remoteMessageContent = remote_text_map ? JSON.stringify(remote_text_map) : remote_text; |
|||
const msg = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.message, |
|||
sender: owner.id, |
|||
date: date, |
|||
messageContent: text, |
|||
remoteMessageContent, |
|||
status: constants.statuses.pending, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}; |
|||
// console.log(msg)
|
|||
const message = yield models_1.models.Message.create(msg); |
|||
res_1.success(res, jsonUtils.messageToJson(message, chat)); |
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.message, |
|||
message: { |
|||
id: message.id, |
|||
content: remote_text_map || remote_text || text |
|||
} |
|||
}); |
|||
}); |
|||
exports.sendMessage = sendMessage; |
|||
const receiveMessage = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('received message', { payload }); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const total_spent = 1; |
|||
const { owner, sender, chat, content, msg_id } = yield helpers.parseReceiveParams(payload); |
|||
if (!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!'); |
|||
} |
|||
const text = content; |
|||
const message = yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
type: constants.message_types.message, |
|||
asciiEncodedTotal: total_spent, |
|||
sender: sender.id, |
|||
date: date, |
|||
messageContent: text, |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
status: constants.statuses.received |
|||
}); |
|||
console.log('saved message', message.dataValues); |
|||
socket.sendJson({ |
|||
type: 'message', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
hub_1.sendNotification(chat, sender.alias, 'message'); |
|||
sendConfirmation({ chat, sender: owner, msg_id }); |
|||
}); |
|||
exports.receiveMessage = receiveMessage; |
|||
const sendConfirmation = ({ chat, sender, msg_id }) => { |
|||
helpers.sendMessage({ |
|||
chat, |
|||
sender, |
|||
message: { id: msg_id }, |
|||
type: constants.message_types.confirmation, |
|||
}); |
|||
}; |
|||
const receiveConfirmation = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('received confirmation', { payload }); |
|||
const dat = payload.content || payload; |
|||
const chat_uuid = dat.chat.uuid; |
|||
const msg_id = dat.message.id; |
|||
const sender_pub_key = dat.sender.pub_key; |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const sender = yield models_1.models.Contact.findOne({ where: { publicKey: sender_pub_key } }); |
|||
const chat = yield models_1.models.Chat.findOne({ where: { uuid: chat_uuid } }); |
|||
// new confirmation logic
|
|||
if (msg_id) { |
|||
lock_1.default.acquire('confirmation', function (done) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
console.log("update status map"); |
|||
const message = yield models_1.models.Message.findOne({ where: { id: msg_id } }); |
|||
if (message) { |
|||
let statusMap = {}; |
|||
try { |
|||
statusMap = JSON.parse(message.statusMap || '{}'); |
|||
} |
|||
catch (e) { } |
|||
statusMap[sender.id] = constants.statuses.received; |
|||
yield message.update({ |
|||
status: constants.statuses.received, |
|||
statusMap: JSON.stringify(statusMap) |
|||
}); |
|||
socket.sendJson({ |
|||
type: 'confirmation', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
} |
|||
done(); |
|||
}); |
|||
}); |
|||
} |
|||
else { // old logic
|
|||
const messages = yield models_1.models.Message.findAll({ |
|||
limit: 1, |
|||
where: { |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: [ |
|||
constants.message_types.message, |
|||
constants.message_types.invoice, |
|||
constants.message_types.attachment, |
|||
], |
|||
status: constants.statuses.pending, |
|||
}, |
|||
order: [['createdAt', 'desc']] |
|||
}); |
|||
const message = messages[0]; |
|||
message.update({ status: constants.statuses.received }); |
|||
socket.sendJson({ |
|||
type: 'confirmation', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
} |
|||
}); |
|||
exports.receiveConfirmation = receiveConfirmation; |
|||
const readMessages = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const chat_id = req.params.chat_id; |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
models_1.models.Message.update({ seen: true }, { |
|||
where: { |
|||
sender: { |
|||
[sequelize_1.Op.ne]: owner.id |
|||
}, |
|||
chatId: chat_id |
|||
} |
|||
}); |
|||
res_1.success(res, {}); |
|||
}); |
|||
exports.readMessages = readMessages; |
|||
const clearMessages = (req, res) => { |
|||
models_1.models.Message.destroy({ where: {}, truncate: true }); |
|||
res_1.success(res, {}); |
|||
}; |
|||
exports.clearMessages = clearMessages; |
|||
//# sourceMappingURL=messages.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,178 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const hub_1 = require("../hub"); |
|||
const socket = require("../utils/socket"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const helpers = require("../helpers"); |
|||
const res_1 = require("../utils/res"); |
|||
const lightning = require("../utils/lightning"); |
|||
const ldat_1 = require("../utils/ldat"); |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
const sendPayment = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { amount, chat_id, contact_id, destination_key, media_type, muid, text, remote_text, dimensions, } = req.body; |
|||
console.log('[send payment]', req.body); |
|||
if (destination_key && !contact_id && !chat_id) { |
|||
return helpers.performKeysendMessage({ |
|||
destination_key, |
|||
amount, |
|||
msg: '{}', |
|||
success: () => { |
|||
console.log('payment sent!'); |
|||
res_1.success(res, { destination_key, amount }); |
|||
}, |
|||
failure: (error) => { |
|||
res.status(200); |
|||
res.json({ success: false, error }); |
|||
res.end(); |
|||
} |
|||
}); |
|||
} |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const chat = yield helpers.findOrCreateChat({ |
|||
chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: contact_id |
|||
}); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const msg = { |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.direct_payment, |
|||
amount: amount, |
|||
amountMsat: parseFloat(amount) * 1000, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}; |
|||
if (text) |
|||
msg.messageContent = text; |
|||
if (remote_text) |
|||
msg.remoteMessageContent = remote_text; |
|||
if (muid) { |
|||
const myMediaToken = yield ldat_1.tokenFromTerms({ |
|||
meta: { dim: dimensions }, host: '', |
|||
muid, ttl: null, |
|||
pubkey: owner.publicKey |
|||
}); |
|||
msg.mediaToken = myMediaToken; |
|||
msg.mediaType = media_type || ''; |
|||
} |
|||
const message = yield models_1.models.Message.create(msg); |
|||
const msgToSend = { |
|||
id: message.id, |
|||
amount, |
|||
}; |
|||
if (muid) { |
|||
msgToSend.mediaType = media_type || 'image/jpeg'; |
|||
msgToSend.mediaTerms = { muid, meta: { dim: dimensions } }; |
|||
} |
|||
if (remote_text) |
|||
msgToSend.content = remote_text; |
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.direct_payment, |
|||
message: msgToSend, |
|||
amount: amount, |
|||
success: (data) => __awaiter(void 0, void 0, void 0, function* () { |
|||
// console.log('payment sent', { data })
|
|||
res_1.success(res, jsonUtils.messageToJson(message, chat)); |
|||
}), |
|||
failure: (error) => { |
|||
res.status(200); |
|||
res.json({ success: false, error }); |
|||
res.end(); |
|||
} |
|||
}); |
|||
}); |
|||
exports.sendPayment = sendPayment; |
|||
const receivePayment = (payload) => __awaiter(void 0, void 0, void 0, function* () { |
|||
console.log('received payment', { payload }); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const { owner, sender, chat, amount, content, mediaType, mediaToken } = yield helpers.parseReceiveParams(payload); |
|||
if (!owner || !sender || !chat) { |
|||
return console.log('=> no group chat!'); |
|||
} |
|||
const msg = { |
|||
chatId: chat.id, |
|||
type: constants.message_types.direct_payment, |
|||
sender: sender.id, |
|||
amount: amount, |
|||
amountMsat: parseFloat(amount) * 1000, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date |
|||
}; |
|||
if (content) |
|||
msg.messageContent = content; |
|||
if (mediaType) |
|||
msg.mediaType = mediaType; |
|||
if (mediaToken) |
|||
msg.mediaToken = mediaToken; |
|||
const message = yield models_1.models.Message.create(msg); |
|||
console.log('saved message', message.dataValues); |
|||
socket.sendJson({ |
|||
type: 'direct_payment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
hub_1.sendNotification(chat, sender.alias, 'message'); |
|||
}); |
|||
exports.receivePayment = receivePayment; |
|||
const listPayments = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const limit = (req.query.limit && parseInt(req.query.limit)) || 100; |
|||
const offset = (req.query.offset && parseInt(req.query.offset)) || 0; |
|||
const payments = []; |
|||
const response = yield lightning.listInvoices(); |
|||
const invs = response && response.invoices; |
|||
if (invs && invs.length) { |
|||
invs.forEach(inv => { |
|||
const val = inv.value && parseInt(inv.value); |
|||
if (val && val > 1) { |
|||
let payment_hash = ''; |
|||
if (inv.r_hash) { |
|||
payment_hash = Buffer.from(inv.r_hash).toString('hex'); |
|||
} |
|||
payments.push({ |
|||
type: 'invoice', |
|||
amount: parseInt(inv.value), |
|||
date: parseInt(inv.creation_date), |
|||
payment_request: inv.payment_request, |
|||
payment_hash |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
const res2 = yield lightning.listPayments(); |
|||
const pays = res2 && res2.payments; |
|||
if (pays && pays.length) { |
|||
pays.forEach(pay => { |
|||
const val = pay.value && parseInt(pay.value); |
|||
if (val && val > 1) { |
|||
payments.push({ |
|||
type: 'payment', |
|||
amount: parseInt(pay.value), |
|||
date: parseInt(pay.creation_date), |
|||
pubkey: pay.path[pay.path.length - 1], |
|||
payment_hash: pay.payment_hash, |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
// latest one first
|
|||
payments.sort((a, b) => b.date - a.date); |
|||
res_1.success(res, payments.splice(offset, limit)); |
|||
}); |
|||
exports.listPayments = listPayments; |
|||
//# sourceMappingURL=payment.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,25 @@ |
|||
"use strict"; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const yup = require("yup"); |
|||
/* |
|||
These schemas validate payloads coming from app, |
|||
do not necessarily match up with Models |
|||
*/ |
|||
const attachment = yup.object().shape({ |
|||
muid: yup.string().required(), |
|||
media_type: yup.string().required(), |
|||
media_key_map: yup.object().required(), |
|||
}); |
|||
exports.attachment = attachment; |
|||
const message = yup.object().shape({ |
|||
contact_id: yup.number().required(), |
|||
}); |
|||
exports.message = message; |
|||
const purchase = yup.object().shape({ |
|||
chat_id: yup.number().required(), |
|||
contact_id: yup.number().required(), |
|||
mediaToken: yup.string().required(), |
|||
amount: yup.number().required() |
|||
}); |
|||
exports.purchase = purchase; |
|||
//# sourceMappingURL=schemas.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../../api/controllers/schemas.ts"],"names":[],"mappings":";;AAAA,2BAA0B;AAE1B;;;EAGE;AAEF,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC;IAClC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACzC,CAAC,CAAA;AAcE,gCAAU;AAZd,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC;IAC/B,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACtC,CAAC,CAAA;AAYE,0BAAO;AAVX,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC;IAChC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAA;AAIE,4BAAQ"} |
@ -0,0 +1,402 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const res_1 = require("../utils/res"); |
|||
const cron_1 = require("cron"); |
|||
const case_1 = require("../utils/case"); |
|||
const cronUtils = require("../utils/cron"); |
|||
const socket = require("../utils/socket"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const helpers = require("../helpers"); |
|||
const rsa = require("../crypto/rsa"); |
|||
const moment = require("moment"); |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
// store all current running jobs in memory
|
|||
let jobs = {}; |
|||
// init jobs from DB
|
|||
const initializeCronJobs = () => __awaiter(void 0, void 0, void 0, function* () { |
|||
yield helpers.sleep(1000); |
|||
const subs = yield getRawSubs({ where: { ended: false } }); |
|||
subs.length && subs.forEach(sub => { |
|||
console.log("=> starting subscription cron job", sub.id + ":", sub.cron); |
|||
startCronJob(sub); |
|||
}); |
|||
}); |
|||
exports.initializeCronJobs = initializeCronJobs; |
|||
function startCronJob(sub) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
jobs[sub.id] = new cron_1.CronJob(sub.cron, function () { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const subscription = yield models_1.models.Subscription.findOne({ where: { id: sub.id } }); |
|||
if (!subscription) { |
|||
delete jobs[sub.id]; |
|||
return this.stop(); |
|||
} |
|||
console.log('EXEC CRON =>', subscription.id); |
|||
if (subscription.paused) { // skip, still in jobs{} tho
|
|||
return this.stop(); |
|||
} |
|||
let STOP = checkSubscriptionShouldAlreadyHaveEnded(subscription); |
|||
if (STOP) { // end the job and return
|
|||
console.log("stop"); |
|||
subscription.update({ ended: true }); |
|||
delete jobs[subscription.id]; |
|||
return this.stop(); |
|||
} |
|||
// SEND PAYMENT!!!
|
|||
sendSubscriptionPayment(subscription, false); |
|||
}); |
|||
}, null, true); |
|||
}); |
|||
} |
|||
function checkSubscriptionShouldAlreadyHaveEnded(sub) { |
|||
if (sub.endDate) { |
|||
const now = new Date(); |
|||
if (now.getTime() > sub.endDate.getTime()) { |
|||
return true; |
|||
} |
|||
} |
|||
if (sub.endNumber) { |
|||
if (sub.count >= sub.endNumber) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
function checkSubscriptionShouldEndAfterThisPayment(sub) { |
|||
if (sub.endDate) { |
|||
const { ms } = cronUtils.parse(sub.cron); |
|||
const now = new Date(); |
|||
if ((now.getTime() + ms) > sub.endDate.getTime()) { |
|||
return true; |
|||
} |
|||
} |
|||
if (sub.endNumber) { |
|||
if (sub.count + 1 >= sub.endNumber) { |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
function msgForSubPayment(owner, sub, isFirstMessage, forMe) { |
|||
let text = ''; |
|||
if (isFirstMessage) { |
|||
const alias = forMe ? 'You' : owner.alias; |
|||
text = `${alias} subscribed\n`; |
|||
} |
|||
else { |
|||
text = 'Subscription\n'; |
|||
} |
|||
text += `Amount: ${sub.amount} sats\n`; |
|||
text += `Interval: ${cronUtils.parse(sub.cron).interval}\n`; |
|||
if (sub.endDate) { |
|||
text += `End: ${moment(sub.endDate).format('MM/DD/YY')}\n`; |
|||
text += `Status: ${sub.count + 1} sent`; |
|||
} |
|||
else if (sub.endNumber) { |
|||
text += `Status: ${sub.count + 1} of ${sub.endNumber} sent`; |
|||
} |
|||
return text; |
|||
} |
|||
function sendSubscriptionPayment(sub, isFirstMessage) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const subscription = yield models_1.models.Subscription.findOne({ where: { id: sub.id } }); |
|||
if (!subscription) { |
|||
return; |
|||
} |
|||
const chat = yield models_1.models.Chat.findOne({ where: { id: subscription.chatId } }); |
|||
if (!subscription) { |
|||
console.log("=> no sub for this payment!!!"); |
|||
return; |
|||
} |
|||
const forMe = false; |
|||
const text = msgForSubPayment(owner, sub, isFirstMessage, forMe); |
|||
const contact = yield models_1.models.Contact.findByPk(sub.contactId); |
|||
const enc = rsa.encrypt(contact.contactKey, text); |
|||
helpers.sendMessage({ |
|||
chat: chat, |
|||
sender: owner, |
|||
type: constants.message_types.direct_payment, |
|||
message: { amount: sub.amount, content: enc }, |
|||
amount: sub.amount, |
|||
success: (data) => __awaiter(this, void 0, void 0, function* () { |
|||
const shouldEnd = checkSubscriptionShouldEndAfterThisPayment(subscription); |
|||
const obj = { |
|||
totalPaid: parseFloat(subscription.totalPaid || 0) + parseFloat(subscription.amount), |
|||
count: parseInt(subscription.count || 0) + 1, |
|||
ended: false, |
|||
}; |
|||
if (shouldEnd) { |
|||
obj.ended = true; |
|||
if (jobs[sub.id]) |
|||
jobs[subscription.id].stop(); |
|||
delete jobs[subscription.id]; |
|||
} |
|||
yield subscription.update(obj); |
|||
const forMe = true; |
|||
const text2 = msgForSubPayment(owner, sub, isFirstMessage, forMe); |
|||
const encText = rsa.encrypt(owner.contactKey, text2); |
|||
const message = yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.direct_payment, |
|||
status: constants.statuses.confirmed, |
|||
messageContent: encText, |
|||
amount: subscription.amount, |
|||
amountMsat: parseFloat(subscription.amount) * 1000, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
subscriptionId: subscription.id, |
|||
}); |
|||
socket.sendJson({ |
|||
type: 'direct_payment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
}), |
|||
failure: (err) => __awaiter(this, void 0, void 0, function* () { |
|||
console.log("SEND PAY ERROR"); |
|||
let errMessage = constants.payment_errors[err] || 'Unknown'; |
|||
errMessage = 'Payment Failed: ' + errMessage; |
|||
const message = yield models_1.models.Message.create({ |
|||
chatId: chat.id, |
|||
sender: owner.id, |
|||
type: constants.message_types.direct_payment, |
|||
status: constants.statuses.failed, |
|||
messageContent: errMessage, |
|||
amount: sub.amount, |
|||
amountMsat: parseFloat(sub.amount) * 1000, |
|||
date: date, |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
subscriptionId: sub.id, |
|||
}); |
|||
socket.sendJson({ |
|||
type: 'direct_payment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
}) |
|||
}); |
|||
}); |
|||
} |
|||
// pause sub
|
|||
function pauseSubscription(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const id = parseInt(req.params.id); |
|||
try { |
|||
const sub = yield models_1.models.Subscription.findOne({ where: { id } }); |
|||
if (sub) { |
|||
sub.update({ paused: true }); |
|||
if (jobs[id]) |
|||
jobs[id].stop(); |
|||
res_1.success(res, jsonUtils.subscriptionToJson(sub, null)); |
|||
} |
|||
else { |
|||
res_1.failure(res, 'not found'); |
|||
} |
|||
} |
|||
catch (e) { |
|||
console.log('ERROR pauseSubscription', e); |
|||
res_1.failure(res, e); |
|||
} |
|||
}); |
|||
} |
|||
exports.pauseSubscription = pauseSubscription; |
|||
; |
|||
// restart sub
|
|||
function restartSubscription(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const id = parseInt(req.params.id); |
|||
try { |
|||
const sub = yield models_1.models.Subscription.findOne({ where: { id } }); |
|||
if (sub) { |
|||
sub.update({ paused: false }); |
|||
if (jobs[id]) |
|||
jobs[id].start(); |
|||
res_1.success(res, jsonUtils.subscriptionToJson(sub, null)); |
|||
} |
|||
else { |
|||
res_1.failure(res, 'not found'); |
|||
} |
|||
} |
|||
catch (e) { |
|||
console.log('ERROR restartSubscription', e); |
|||
res_1.failure(res, e); |
|||
} |
|||
}); |
|||
} |
|||
exports.restartSubscription = restartSubscription; |
|||
; |
|||
function getRawSubs(opts = {}) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const options = Object.assign({ order: [['id', 'asc']] }, opts); |
|||
try { |
|||
const subs = yield models_1.models.Subscription.findAll(options); |
|||
return subs; |
|||
} |
|||
catch (e) { |
|||
throw e; |
|||
} |
|||
}); |
|||
} |
|||
// all subs
|
|||
const getAllSubscriptions = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
try { |
|||
const subs = yield getRawSubs(); |
|||
res_1.success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub, null))); |
|||
} |
|||
catch (e) { |
|||
console.log('ERROR getAllSubscriptions', e); |
|||
res_1.failure(res, e); |
|||
} |
|||
}); |
|||
exports.getAllSubscriptions = getAllSubscriptions; |
|||
// one sub by id
|
|||
function getSubscription(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
try { |
|||
const sub = yield models_1.models.Subscription.findOne({ where: { id: req.params.id } }); |
|||
res_1.success(res, jsonUtils.subscriptionToJson(sub, null)); |
|||
} |
|||
catch (e) { |
|||
console.log('ERROR getSubscription', e); |
|||
res_1.failure(res, e); |
|||
} |
|||
}); |
|||
} |
|||
exports.getSubscription = getSubscription; |
|||
; |
|||
// delete sub by id
|
|||
function deleteSubscription(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const id = req.params.id; |
|||
if (!id) |
|||
return; |
|||
try { |
|||
if (jobs[id]) { |
|||
jobs[id].stop(); |
|||
delete jobs[id]; |
|||
} |
|||
models_1.models.Subscription.destroy({ where: { id } }); |
|||
res_1.success(res, true); |
|||
} |
|||
catch (e) { |
|||
console.log('ERROR deleteSubscription', e); |
|||
res_1.failure(res, e); |
|||
} |
|||
}); |
|||
} |
|||
exports.deleteSubscription = deleteSubscription; |
|||
; |
|||
// all subs for contact id
|
|||
const getSubscriptionsForContact = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
try { |
|||
const subs = yield getRawSubs({ where: { contactId: req.params.contactId } }); |
|||
res_1.success(res, subs.map(sub => jsonUtils.subscriptionToJson(sub, null))); |
|||
} |
|||
catch (e) { |
|||
console.log('ERROR getSubscriptionsForContact', e); |
|||
res_1.failure(res, e); |
|||
} |
|||
}); |
|||
exports.getSubscriptionsForContact = getSubscriptionsForContact; |
|||
// create new sub
|
|||
function createSubscription(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const s = jsonToSubscription(Object.assign(Object.assign({}, req.body), { count: 0, total_paid: 0, createdAt: date, ended: false, paused: false })); |
|||
if (!s.cron) { |
|||
return res_1.failure(res, 'Invalid interval'); |
|||
} |
|||
try { |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
const chat = yield helpers.findOrCreateChat({ |
|||
chat_id: req.body.chat_id, |
|||
owner_id: owner.id, |
|||
recipient_id: req.body.contact_id, |
|||
}); |
|||
s.chatId = chat.id; // add chat id if newly created
|
|||
if (!owner || !chat) { |
|||
return res_1.failure(res, 'Invalid chat or contact'); |
|||
} |
|||
const sub = yield models_1.models.Subscription.create(s); |
|||
startCronJob(sub); |
|||
const isFirstMessage = true; |
|||
sendSubscriptionPayment(sub, isFirstMessage); |
|||
res_1.success(res, jsonUtils.subscriptionToJson(sub, chat)); |
|||
} |
|||
catch (e) { |
|||
console.log('ERROR createSubscription', e); |
|||
res_1.failure(res, e); |
|||
} |
|||
}); |
|||
} |
|||
exports.createSubscription = createSubscription; |
|||
; |
|||
function editSubscription(req, res) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
console.log('======> editSubscription'); |
|||
const date = new Date(); |
|||
date.setMilliseconds(0); |
|||
const id = parseInt(req.params.id); |
|||
const s = jsonToSubscription(Object.assign(Object.assign({}, req.body), { count: 0, createdAt: date, ended: false, paused: false })); |
|||
try { |
|||
if (!id || !s.chatId || !s.cron) { |
|||
return res_1.failure(res, 'Invalid data'); |
|||
} |
|||
const subRecord = yield models_1.models.Subscription.findOne({ where: { id } }); |
|||
if (!subRecord) { |
|||
return res_1.failure(res, 'No subscription found'); |
|||
} |
|||
// stop so it can be restarted
|
|||
if (jobs[id]) |
|||
jobs[id].stop(); |
|||
const obj = { |
|||
cron: s.cron, |
|||
updatedAt: date, |
|||
}; |
|||
if (s.amount) |
|||
obj.amount = s.amount; |
|||
if (s.endDate) |
|||
obj.endDate = s.endDate; |
|||
if (s.endNumber) |
|||
obj.endNumber = s.endNumber; |
|||
const sub = yield subRecord.update(obj); |
|||
const end = checkSubscriptionShouldAlreadyHaveEnded(sub); |
|||
if (end) { |
|||
yield subRecord.update({ ended: true }); |
|||
delete jobs[id]; |
|||
} |
|||
else { |
|||
startCronJob(sub); // restart
|
|||
} |
|||
const chat = yield models_1.models.Chat.findOne({ where: { id: s.chatId } }); |
|||
res_1.success(res, jsonUtils.subscriptionToJson(sub, chat)); |
|||
} |
|||
catch (e) { |
|||
console.log('ERROR createSubscription', e); |
|||
res_1.failure(res, e); |
|||
} |
|||
}); |
|||
} |
|||
exports.editSubscription = editSubscription; |
|||
; |
|||
function jsonToSubscription(j) { |
|||
console.log("=>", j); |
|||
const cron = cronUtils.make(j.interval); |
|||
return case_1.toCamel(Object.assign(Object.assign({}, j), { cron })); |
|||
} |
|||
//# sourceMappingURL=subscriptions.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,64 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/app.json')[env]; |
|||
// setup disk storage
|
|||
var multer = require('multer'); |
|||
var avatarStorage = multer.diskStorage({ |
|||
destination: (req, file, cb) => { |
|||
let dir = __dirname.includes('/dist/') ? __dirname + '/..' : __dirname; |
|||
cb(null, dir + '/../../public/uploads'); |
|||
}, |
|||
filename: (req, file, cb) => { |
|||
const mime = file.mimetype; |
|||
const extA = mime.split("/"); |
|||
const ext = extA[extA.length - 1]; |
|||
if (req.body.chat_id) { |
|||
cb(null, `chat_${req.body.chat_id}_picture.${ext}`); |
|||
} |
|||
else { |
|||
cb(null, `${req.body.contact_id}_profile_picture.${ext}`); |
|||
} |
|||
} |
|||
}); |
|||
var avatarUpload = multer({ storage: avatarStorage }); |
|||
exports.avatarUpload = avatarUpload; |
|||
const uploadFile = (req, res) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { contact_id, chat_id } = req.body; |
|||
const { file } = req; |
|||
const photo_url = config.node_http_protocol + |
|||
'://' + |
|||
process.env.NODE_IP + |
|||
'/static/uploads/' + |
|||
file.filename; |
|||
if (contact_id) { |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id: contact_id } }); |
|||
if (contact) |
|||
contact.update({ photoUrl: photo_url }); |
|||
} |
|||
if (chat_id) { |
|||
const chat = yield models_1.models.Chat.findOne({ where: { id: chat_id } }); |
|||
if (chat) |
|||
chat.update({ photoUrl: photo_url }); |
|||
} |
|||
res.status(200); |
|||
res.json({ |
|||
success: true, |
|||
contact_id: parseInt(contact_id || 0), |
|||
chat_id: parseInt(chat_id || 0), |
|||
photo_url |
|||
}); |
|||
res.end(); |
|||
}); |
|||
exports.uploadFile = uploadFile; |
|||
//# sourceMappingURL=uploads.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"uploads.js","sourceRoot":"","sources":["../../../api/controllers/uploads.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,sCAAgC;AAEhC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa,CAAC;AAClD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,wBAAwB,CAAC,CAAC,GAAG,CAAC,CAAC;AAElE,qBAAqB;AACrB,IAAI,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAC9B,IAAI,aAAa,GAAG,MAAM,CAAC,WAAW,CAAC;IACrC,WAAW,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE;QAC7B,IAAI,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,GAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;QACpE,EAAE,CAAC,IAAI,EAAE,GAAG,GAAG,uBAAuB,CAAC,CAAA;IACzC,CAAC;IACD,QAAQ,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAA;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAC,CAAC,CAAC,CAAA;QAC/B,IAAG,GAAG,CAAC,IAAI,CAAC,OAAO,EAAC;YAClB,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,CAAC,IAAI,CAAC,OAAO,YAAY,GAAG,EAAE,CAAC,CAAA;SACpD;aAAM;YACL,EAAE,CAAC,IAAI,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,oBAAoB,GAAG,EAAE,CAAC,CAAA;SAC1D;IACH,CAAC;CACF,CAAC,CAAA;AACF,IAAI,YAAY,GAAG,MAAM,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAA;AAkCnD,oCAAY;AAhCd,MAAM,UAAU,GAAG,CAAO,GAAG,EAAE,GAAG,EAAE,EAAE;IACpC,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAAI,CAAA;IACxC,MAAM,EAAE,IAAI,EAAE,GAAG,GAAG,CAAA;IAEpB,MAAM,SAAS,GACb,MAAM,CAAC,kBAAkB;QACzB,KAAK;QACL,OAAO,CAAC,GAAG,CAAC,OAAO;QACnB,kBAAkB;QAClB,IAAI,CAAC,QAAQ,CAAA;IAEf,IAAG,UAAU,EAAC;QACZ,MAAM,OAAO,GAAG,MAAM,eAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,EAAE,CAAC,CAAA;QAC3E,IAAG,OAAO;YAAE,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAA;KACpD;IAED,IAAG,OAAO,EAAC;QACT,MAAM,IAAI,GAAG,MAAM,eAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,CAAC,CAAA;QAClE,IAAG,IAAI;YAAE,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAA;KAC9C;IAED,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACf,GAAG,CAAC,IAAI,CAAC;QACP,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,QAAQ,CAAC,UAAU,IAAE,CAAC,CAAC;QACnC,OAAO,EAAE,QAAQ,CAAC,OAAO,IAAE,CAAC,CAAC;QAC7B,SAAS;KACV,CAAC,CAAC;IACH,GAAG,CAAC,GAAG,EAAE,CAAC;AACZ,CAAC,CAAA,CAAA;AAIA,gCAAU"} |
@ -0,0 +1,65 @@ |
|||
"use strict"; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const crypto = require("crypto"); |
|||
function encrypt(key, txt) { |
|||
try { |
|||
const pubc = cert.pub(key); |
|||
const buf = crypto.publicEncrypt({ |
|||
key: pubc, |
|||
padding: crypto.constants.RSA_PKCS1_PADDING, |
|||
}, Buffer.from(txt, 'utf-8')); |
|||
return buf.toString('base64'); |
|||
} |
|||
catch (e) { |
|||
return ''; |
|||
} |
|||
} |
|||
exports.encrypt = encrypt; |
|||
function decrypt(privateKey, enc) { |
|||
try { |
|||
const privc = cert.priv(privateKey); |
|||
const buf = crypto.privateDecrypt({ |
|||
key: privc, |
|||
padding: crypto.constants.RSA_PKCS1_PADDING, |
|||
}, Buffer.from(enc, 'base64')); |
|||
return buf.toString('utf-8'); |
|||
} |
|||
catch (e) { |
|||
return ''; |
|||
} |
|||
} |
|||
exports.decrypt = decrypt; |
|||
function testRSA() { |
|||
crypto.generateKeyPair('rsa', { |
|||
modulusLength: 2048 |
|||
}, (err, publicKey, priv) => { |
|||
const pubPEM = publicKey.export({ |
|||
type: 'pkcs1', format: 'pem' |
|||
}); |
|||
const pub = cert.unpub(pubPEM); |
|||
const msg = 'hi'; |
|||
const enc = encrypt(pub, msg); |
|||
const dec = decrypt(priv, enc); |
|||
console.log("FINAL:", dec); |
|||
}); |
|||
} |
|||
exports.testRSA = testRSA; |
|||
const cert = { |
|||
unpub: function (key) { |
|||
let s = key; |
|||
s = s.replace('-----BEGIN RSA PUBLIC KEY-----', ''); |
|||
s = s.replace('-----END RSA PUBLIC KEY-----', ''); |
|||
return s.replace(/[\r\n]+/gm, ''); |
|||
}, |
|||
pub: function (key) { |
|||
return '-----BEGIN RSA PUBLIC KEY-----\n' + |
|||
key + '\n' + |
|||
'-----END RSA PUBLIC KEY-----'; |
|||
}, |
|||
priv: function (key) { |
|||
return '-----BEGIN RSA PRIVATE KEY-----\n' + |
|||
key + '\n' + |
|||
'-----END RSA PRIVATE KEY-----'; |
|||
} |
|||
}; |
|||
//# sourceMappingURL=rsa.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"rsa.js","sourceRoot":"","sources":["../../../api/crypto/rsa.ts"],"names":[],"mappings":";;AAAA,iCAAiC;AAEjC,SAAgB,OAAO,CAAC,GAAG,EAAE,GAAG;IAC9B,IAAG;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC1B,MAAM,GAAG,GAAG,MAAM,CAAC,aAAa,CAAC;YAC/B,GAAG,EAAC,IAAI;YACR,OAAO,EAAC,MAAM,CAAC,SAAS,CAAC,iBAAiB;SAC3C,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAC,OAAO,CAAC,CAAC,CAAA;QAC5B,OAAO,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;KAC9B;IAAC,OAAM,CAAC,EAAE;QACT,OAAO,EAAE,CAAA;KACV;AACH,CAAC;AAXD,0BAWC;AAED,SAAgB,OAAO,CAAC,UAAU,EAAE,GAAG;IACrC,IAAG;QACD,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACnC,MAAM,GAAG,GAAG,MAAM,CAAC,cAAc,CAAC;YAChC,GAAG,EAAC,KAAK;YACT,OAAO,EAAC,MAAM,CAAC,SAAS,CAAC,iBAAiB;SAC3C,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAC,QAAQ,CAAC,CAAC,CAAA;QAC7B,OAAO,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;KAC7B;IAAC,OAAM,CAAC,EAAE;QACT,OAAO,EAAE,CAAA;KACV;AACH,CAAC;AAXD,0BAWC;AAED,SAAgB,OAAO;IACrB,MAAM,CAAC,eAAe,CAAC,KAAK,EAAE;QAC5B,aAAa,EAAE,IAAI;KACpB,EAAE,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAC,EAAE;QACzB,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;YAC9B,IAAI,EAAC,OAAO,EAAC,MAAM,EAAC,KAAK;SAC1B,CAAC,CAAA;QACF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QAE9B,MAAM,GAAG,GAAG,IAAI,CAAA;QAChB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QAE7B,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;QAC9B,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAC,GAAG,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;AACJ,CAAC;AAfD,0BAeC;AAED,MAAM,IAAI,GAAG;IACX,KAAK,EAAE,UAAS,GAAG;QACjB,IAAI,CAAC,GAAG,GAAG,CAAA;QACX,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,gCAAgC,EAAC,EAAE,CAAC,CAAA;QAClD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,8BAA8B,EAAC,EAAE,CAAC,CAAA;QAChD,OAAO,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAA;IACnC,CAAC;IACD,GAAG,EAAC,UAAS,GAAG;QACd,OAAO,kCAAkC;YACvC,GAAG,GAAG,IAAI;YACV,8BAA8B,CAAA;IAClC,CAAC;IACD,IAAI,EAAC,UAAS,GAAG;QACf,OAAO,mCAAmC;YACxC,GAAG,GAAG,IAAI;YACV,+BAA+B,CAAA;IACnC,CAAC;CACF,CAAA"} |
@ -0,0 +1,150 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("../models"); |
|||
const socket = require("../utils/socket"); |
|||
const hub_1 = require("../hub"); |
|||
const jsonUtils = require("../utils/json"); |
|||
const decodeUtils = require("../utils/decode"); |
|||
const lightning_1 = require("../utils/lightning"); |
|||
const constants = require(__dirname + '/../../config/constants.json'); |
|||
function parseKeysendInvoice(i, actions) { |
|||
const recs = i.htlcs && i.htlcs[0] && i.htlcs[0].custom_records; |
|||
const buf = recs && recs[lightning_1.SPHINX_CUSTOM_RECORD_KEY]; |
|||
const data = buf && buf.toString(); |
|||
const value = i && i.value && parseInt(i.value); |
|||
if (!data) |
|||
return; |
|||
let payload; |
|||
if (data[0] === '{') { |
|||
try { |
|||
payload = JSON.parse(data); |
|||
} |
|||
catch (e) { } |
|||
} |
|||
else { |
|||
const threads = weave(data); |
|||
if (threads) |
|||
payload = JSON.parse(threads); |
|||
} |
|||
if (payload) { |
|||
const dat = payload.content || payload; |
|||
if (value && dat && dat.message) { |
|||
dat.message.amount = value; |
|||
} |
|||
if (actions[payload.type]) { |
|||
actions[payload.type](payload); |
|||
} |
|||
else { |
|||
console.log('Incorrect payload type:', payload.type); |
|||
} |
|||
} |
|||
} |
|||
const chunks = {}; |
|||
function weave(p) { |
|||
const pa = p.split('_'); |
|||
if (pa.length < 4) |
|||
return; |
|||
const ts = pa[0]; |
|||
const i = pa[1]; |
|||
const n = pa[2]; |
|||
const m = pa.filter((u, i) => i > 2).join('_'); |
|||
chunks[ts] = chunks[ts] ? [...chunks[ts], { i, n, m }] : [{ i, n, m }]; |
|||
if (chunks[ts].length === parseInt(n)) { |
|||
// got em all!
|
|||
const all = chunks[ts]; |
|||
let payload = ''; |
|||
all.slice().sort((a, b) => a.i - b.i).forEach(obj => { |
|||
payload += obj.m; |
|||
}); |
|||
delete chunks[ts]; |
|||
return payload; |
|||
} |
|||
} |
|||
function subscribeInvoices(actions) { |
|||
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { |
|||
const lightning = yield lightning_1.loadLightning(); |
|||
var call = lightning.subscribeInvoices(); |
|||
call.on('data', function (response) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
// console.log('subscribed invoices', { response })
|
|||
if (response['state'] !== 'SETTLED') { |
|||
return; |
|||
} |
|||
// console.log("IS KEYSEND", response.is_keysend)
|
|||
if (response.is_keysend) { |
|||
parseKeysendInvoice(response, actions); |
|||
} |
|||
else { |
|||
const invoice = yield models_1.models.Message.findOne({ where: { type: constants.message_types.invoice, payment_request: response['payment_request'] } }); |
|||
if (invoice == null) { |
|||
// console.log("ERROR: Invoice " + response['payment_request'] + " not found");
|
|||
socket.sendJson({ |
|||
type: 'invoice_payment', |
|||
response: { invoice: response['payment_request'] } |
|||
}); |
|||
return; |
|||
} |
|||
models_1.models.Message.update({ status: constants.statuses.confirmed }, { where: { id: invoice.id } }); |
|||
let decodedPaymentRequest = decodeUtils.decode(response['payment_request']); |
|||
var paymentHash = ""; |
|||
for (var i = 0; i < decodedPaymentRequest["data"]["tags"].length; i++) { |
|||
let tag = decodedPaymentRequest["data"]["tags"][i]; |
|||
if (tag['description'] == 'payment_hash') { |
|||
paymentHash = tag['value']; |
|||
break; |
|||
} |
|||
} |
|||
let settleDate = parseInt(response['settle_date'] + '000'); |
|||
const chat = yield models_1.models.Chat.findOne({ where: { id: invoice.chatId } }); |
|||
const contactIds = JSON.parse(chat.contactIds); |
|||
const senderId = contactIds.find(id => id != invoice.sender); |
|||
const message = yield models_1.models.Message.create({ |
|||
chatId: invoice.chatId, |
|||
type: constants.message_types.payment, |
|||
sender: senderId, |
|||
amount: response['amt_paid_sat'], |
|||
amountMsat: response['amt_paid_msat'], |
|||
paymentHash: paymentHash, |
|||
date: new Date(settleDate), |
|||
messageContent: response['memo'], |
|||
status: constants.statuses.confirmed, |
|||
createdAt: new Date(settleDate), |
|||
updatedAt: new Date(settleDate) |
|||
}); |
|||
socket.sendJson({ |
|||
type: 'payment', |
|||
response: jsonUtils.messageToJson(message, chat) |
|||
}); |
|||
const sender = yield models_1.models.Contact.findOne({ where: { id: senderId } }); |
|||
hub_1.sendNotification(chat, sender.alias, 'message'); |
|||
} |
|||
}); |
|||
}); |
|||
call.on('status', function (status) { |
|||
console.log("Status", status); |
|||
resolve(status); |
|||
}); |
|||
call.on('error', function (err) { |
|||
// console.log(err)
|
|||
reject(err); |
|||
}); |
|||
call.on('end', function () { |
|||
console.log("Closed stream"); |
|||
// The server has closed the stream.
|
|||
}); |
|||
setTimeout(() => { |
|||
resolve(null); |
|||
}, 100); |
|||
})); |
|||
} |
|||
exports.subscribeInvoices = subscribeInvoices; |
|||
//# sourceMappingURL=index.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,240 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("./models"); |
|||
const md5 = require("md5"); |
|||
const lightning_1 = require("./utils/lightning"); |
|||
const msg_1 = require("./utils/msg"); |
|||
const constants = require('../config/constants.json'); |
|||
const findOrCreateChat = (params) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { chat_id, owner_id, recipient_id } = params; |
|||
let chat; |
|||
let date = new Date(); |
|||
date.setMilliseconds(0); |
|||
if (chat_id) { |
|||
chat = yield models_1.models.Chat.findOne({ where: { id: chat_id } }); |
|||
// console.log('findOrCreateChat: chat_id exists')
|
|||
} |
|||
else { |
|||
console.log("chat does not exists, create new"); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { id: owner_id } }); |
|||
const recipient = yield models_1.models.Contact.findOne({ where: { id: recipient_id } }); |
|||
const uuid = md5([owner.publicKey, recipient.publicKey].sort().join("-")); |
|||
// find by uuid
|
|||
chat = yield models_1.models.Chat.findOne({ where: { uuid } }); |
|||
if (!chat) { // no chat! create new
|
|||
chat = yield models_1.models.Chat.create({ |
|||
uuid: uuid, |
|||
contactIds: JSON.stringify([parseInt(owner_id), parseInt(recipient_id)]), |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
type: constants.chat_types.conversation |
|||
}); |
|||
} |
|||
} |
|||
return chat; |
|||
}); |
|||
exports.findOrCreateChat = findOrCreateChat; |
|||
const sendContactKeys = (args) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { type, contactIds, contactPubKey, sender, success, failure } = args; |
|||
const msg = newkeyexchangemsg(type, sender); |
|||
let yes = null; |
|||
let no = null; |
|||
let cids = contactIds; |
|||
if (!contactIds) |
|||
cids = [null]; // nully
|
|||
yield asyncForEach(cids, (contactId) => __awaiter(void 0, void 0, void 0, function* () { |
|||
let destination_key; |
|||
if (!contactId) { // nully
|
|||
destination_key = contactPubKey; |
|||
} |
|||
else { |
|||
if (contactId == sender.id) { |
|||
return; |
|||
} |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id: contactId } }); |
|||
destination_key = contact.publicKey; |
|||
} |
|||
performKeysendMessage({ |
|||
destination_key, |
|||
amount: 1, |
|||
msg: JSON.stringify(msg), |
|||
success: (data) => { |
|||
yes = data; |
|||
}, |
|||
failure: (error) => { |
|||
no = error; |
|||
} |
|||
}); |
|||
})); |
|||
if (no && failure) { |
|||
failure(no); |
|||
} |
|||
if (!no && yes && success) { |
|||
success(yes); |
|||
} |
|||
}); |
|||
exports.sendContactKeys = sendContactKeys; |
|||
const sendMessage = (params) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const { type, chat, message, sender, amount, success, failure } = params; |
|||
const m = newmsg(type, chat, sender, message); |
|||
const contactIds = typeof chat.contactIds === 'string' ? JSON.parse(chat.contactIds) : chat.contactIds; |
|||
let yes = null; |
|||
let no = null; |
|||
console.log('all contactIds', contactIds); |
|||
yield asyncForEach(contactIds, (contactId) => __awaiter(void 0, void 0, void 0, function* () { |
|||
if (contactId == sender.id) { |
|||
return; |
|||
} |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id: contactId } }); |
|||
const destkey = contact.publicKey; |
|||
const finalMsg = yield msg_1.personalizeMessage(m, contactId, destkey); |
|||
const opts = { |
|||
dest: destkey, |
|||
data: JSON.stringify(finalMsg), |
|||
amt: amount || 1, |
|||
}; |
|||
try { |
|||
const r = yield lightning_1.keysendMessage(opts); |
|||
yes = r; |
|||
} |
|||
catch (e) { |
|||
console.log("KEYSEND ERROR", e); |
|||
no = e; |
|||
} |
|||
})); |
|||
if (yes) { |
|||
if (success) |
|||
success(yes); |
|||
} |
|||
else { |
|||
if (failure) |
|||
failure(no); |
|||
} |
|||
}); |
|||
exports.sendMessage = sendMessage; |
|||
const performKeysendMessage = ({ destination_key, amount, msg, success, failure }) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const opts = { |
|||
dest: destination_key, |
|||
data: msg || JSON.stringify({}), |
|||
amt: amount || 1 |
|||
}; |
|||
try { |
|||
const r = yield lightning_1.keysendMessage(opts); |
|||
console.log("MESSAGE SENT outside SW!", r); |
|||
if (success) |
|||
success(r); |
|||
} |
|||
catch (e) { |
|||
console.log("MESSAGE ERROR", e); |
|||
if (failure) |
|||
failure(e); |
|||
} |
|||
}); |
|||
exports.performKeysendMessage = performKeysendMessage; |
|||
function findOrCreateContactByPubkey(senderPubKey) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
let sender = yield models_1.models.Contact.findOne({ where: { publicKey: senderPubKey } }); |
|||
if (!sender) { |
|||
sender = yield models_1.models.Contact.create({ |
|||
publicKey: senderPubKey, |
|||
alias: "Unknown", |
|||
status: 1 |
|||
}); |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
sendContactKeys({ |
|||
contactIds: [sender.id], |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}); |
|||
} |
|||
return sender; |
|||
}); |
|||
} |
|||
exports.findOrCreateContactByPubkey = findOrCreateContactByPubkey; |
|||
function findOrCreateChatByUUID(chat_uuid, contactIds) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
let chat = yield models_1.models.Chat.findOne({ where: { uuid: chat_uuid } }); |
|||
if (!chat) { |
|||
var date = new Date(); |
|||
date.setMilliseconds(0); |
|||
chat = yield models_1.models.Chat.create({ |
|||
uuid: chat_uuid, |
|||
contactIds: JSON.stringify(contactIds || []), |
|||
createdAt: date, |
|||
updatedAt: date, |
|||
type: 0 // conversation
|
|||
}); |
|||
} |
|||
return chat; |
|||
}); |
|||
} |
|||
exports.findOrCreateChatByUUID = findOrCreateChatByUUID; |
|||
function sleep(ms) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
return new Promise(resolve => setTimeout(resolve, ms)); |
|||
}); |
|||
} |
|||
exports.sleep = sleep; |
|||
function parseReceiveParams(payload) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
const dat = payload.content || payload; |
|||
const sender_pub_key = dat.sender.pub_key; |
|||
const chat_uuid = dat.chat.uuid; |
|||
const chat_type = dat.chat.type; |
|||
const chat_members = dat.chat.members || {}; |
|||
const chat_name = dat.chat.name; |
|||
const amount = dat.message.amount; |
|||
const content = dat.message.content; |
|||
const mediaToken = dat.message.mediaToken; |
|||
const msg_id = dat.message.id || 0; |
|||
const mediaKey = dat.message.mediaKey; |
|||
const mediaType = dat.message.mediaType; |
|||
const isGroup = chat_type && chat_type == constants.chat_types.group; |
|||
let sender; |
|||
let chat; |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
if (isGroup) { |
|||
sender = yield models_1.models.Contact.findOne({ where: { publicKey: sender_pub_key } }); |
|||
chat = yield models_1.models.Chat.findOne({ where: { uuid: chat_uuid } }); |
|||
} |
|||
else { |
|||
sender = yield findOrCreateContactByPubkey(sender_pub_key); |
|||
chat = yield findOrCreateChatByUUID(chat_uuid, [parseInt(owner.id), parseInt(sender.id)]); |
|||
} |
|||
return { owner, sender, chat, sender_pub_key, chat_uuid, amount, content, mediaToken, mediaKey, mediaType, chat_type, msg_id, chat_members, chat_name }; |
|||
}); |
|||
} |
|||
exports.parseReceiveParams = parseReceiveParams; |
|||
function asyncForEach(array, callback) { |
|||
return __awaiter(this, void 0, void 0, function* () { |
|||
for (let index = 0; index < array.length; index++) { |
|||
yield callback(array[index], index, array); |
|||
} |
|||
}); |
|||
} |
|||
function newmsg(type, chat, sender, message) { |
|||
return { |
|||
type: type, |
|||
chat: Object.assign(Object.assign(Object.assign({ uuid: chat.uuid }, chat.name && { name: chat.name }), chat.type && { type: chat.type }), chat.members && { members: chat.members }), |
|||
message: message, |
|||
sender: { |
|||
pub_key: sender.publicKey, |
|||
} |
|||
}; |
|||
} |
|||
function newkeyexchangemsg(type, sender) { |
|||
return { |
|||
type: type, |
|||
sender: Object.assign({ pub_key: sender.publicKey, contact_key: sender.contactKey }, sender.alias && { alias: sender.alias }) |
|||
}; |
|||
} |
|||
//# sourceMappingURL=helpers.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,201 @@ |
|||
"use strict"; |
|||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { |
|||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
|||
return new (P || (P = Promise))(function (resolve, reject) { |
|||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
|||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
|||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
|||
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
|||
}); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const models_1 = require("./models"); |
|||
const fetch = require("node-fetch"); |
|||
const sequelize_1 = require("sequelize"); |
|||
const socket = require("./utils/socket"); |
|||
const jsonUtils = require("./utils/json"); |
|||
const helpers = require("./helpers"); |
|||
const nodeinfo_1 = require("./utils/nodeinfo"); |
|||
const constants = require(__dirname + '/../config/constants.json'); |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require('../config/app.json')[env]; |
|||
const checkInviteHub = (params = {}) => __awaiter(void 0, void 0, void 0, function* () { |
|||
if (env != "production") { |
|||
return; |
|||
} |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
//console.log('[hub] checking invites ping')
|
|||
const inviteStrings = yield models_1.models.Invite.findAll({ where: { status: { [sequelize_1.Op.notIn]: [constants.invite_statuses.complete, constants.invite_statuses.expired] } } }).map(invite => invite.inviteString); |
|||
fetch(config.hub_api_url + '/invites/check', { |
|||
method: 'POST', |
|||
body: JSON.stringify({ invite_strings: inviteStrings }), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
if (json.object) { |
|||
json.object.invites.map((object) => __awaiter(void 0, void 0, void 0, function* () { |
|||
const invite = object.invite; |
|||
const pubkey = object.pubkey; |
|||
const price = object.price; |
|||
const dbInvite = yield models_1.models.Invite.findOne({ where: { inviteString: invite.pin } }); |
|||
const contact = yield models_1.models.Contact.findOne({ where: { id: dbInvite.contactId } }); |
|||
if (dbInvite.status != invite.invite_status) { |
|||
dbInvite.update({ status: invite.invite_status, price: price }); |
|||
socket.sendJson({ |
|||
type: 'invite', |
|||
response: jsonUtils.inviteToJson(dbInvite) |
|||
}); |
|||
if (dbInvite.status == constants.invite_statuses.ready && contact) { |
|||
sendNotification(-1, contact.alias, 'invite'); |
|||
} |
|||
} |
|||
if (pubkey && dbInvite.status == constants.invite_statuses.complete && contact) { |
|||
contact.update({ publicKey: pubkey, status: constants.contact_statuses.confirmed }); |
|||
var contactJson = jsonUtils.contactToJson(contact); |
|||
contactJson.invite = jsonUtils.inviteToJson(dbInvite); |
|||
socket.sendJson({ |
|||
type: 'contact', |
|||
response: contactJson |
|||
}); |
|||
helpers.sendContactKeys({ |
|||
contactIds: [contact.id], |
|||
sender: owner, |
|||
type: constants.message_types.contact_key, |
|||
}); |
|||
} |
|||
})); |
|||
} |
|||
}) |
|||
.catch(error => { |
|||
console.log('[hub error]', error); |
|||
}); |
|||
}); |
|||
const pingHub = (params = {}) => __awaiter(void 0, void 0, void 0, function* () { |
|||
if (env != "production") { |
|||
return; |
|||
} |
|||
const node = yield nodeinfo_1.nodeinfo(); |
|||
sendHubCall(Object.assign(Object.assign({}, params), { node })); |
|||
}); |
|||
const sendHubCall = (params) => { |
|||
// console.log('[hub] sending ping')
|
|||
fetch(config.hub_api_url + '/ping', { |
|||
method: 'POST', |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
// ?
|
|||
}) |
|||
.catch(error => { |
|||
console.log('[hub error]', error); |
|||
}); |
|||
}; |
|||
exports.sendHubCall = sendHubCall; |
|||
const pingHubInterval = (ms) => { |
|||
setInterval(pingHub, ms); |
|||
}; |
|||
exports.pingHubInterval = pingHubInterval; |
|||
const checkInvitesHubInterval = (ms) => { |
|||
setInterval(checkInviteHub, ms); |
|||
}; |
|||
exports.checkInvitesHubInterval = checkInvitesHubInterval; |
|||
const finishInviteInHub = (params, onSuccess, onFailure) => { |
|||
fetch(config.hub_api_url + '/invites/finish', { |
|||
method: 'POST', |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
if (json.object) { |
|||
console.log('[hub] finished invite to hub'); |
|||
onSuccess(json); |
|||
} |
|||
else { |
|||
console.log('[hub] fail to finish invite in hub'); |
|||
onFailure(json); |
|||
} |
|||
}); |
|||
}; |
|||
exports.finishInviteInHub = finishInviteInHub; |
|||
const payInviteInHub = (invite_string, params, onSuccess, onFailure) => { |
|||
fetch(config.hub_api_url + '/invites/' + invite_string + '/pay', { |
|||
method: 'POST', |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
if (json.object) { |
|||
console.log('[hub] finished pay to hub'); |
|||
onSuccess(json); |
|||
} |
|||
else { |
|||
console.log('[hub] fail to pay invite in hub'); |
|||
onFailure(json); |
|||
} |
|||
}); |
|||
}; |
|||
exports.payInviteInHub = payInviteInHub; |
|||
const createInviteInHub = (params, onSuccess, onFailure) => { |
|||
fetch(config.hub_api_url + '/invites', { |
|||
method: 'POST', |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => { |
|||
if (json.object) { |
|||
console.log('[hub] sent invite to be created to hub'); |
|||
onSuccess(json); |
|||
} |
|||
else { |
|||
console.log('[hub] fail to create invite in hub'); |
|||
onFailure(json); |
|||
} |
|||
}); |
|||
}; |
|||
exports.createInviteInHub = createInviteInHub; |
|||
const sendNotification = (chat, name, type) => __awaiter(void 0, void 0, void 0, function* () { |
|||
let message = `You have a new message from ${name}`; |
|||
if (type === 'invite') { |
|||
message = `Your invite to ${name} is ready`; |
|||
} |
|||
if (type === 'group') { |
|||
message = `You have been added to group ${name}`; |
|||
} |
|||
if (type === 'message' && chat.type == constants.chat_types.group && chat.name && chat.name.length) { |
|||
message += ` on ${chat.name}`; |
|||
} |
|||
console.log('[send notification]', { chat_id: chat.id, message }); |
|||
if (chat.isMuted) { |
|||
console.log('[send notification] skipping. chat is muted.'); |
|||
return; |
|||
} |
|||
const owner = yield models_1.models.Contact.findOne({ where: { isOwner: true } }); |
|||
if (!owner.deviceId) { |
|||
console.log('[send notification] skipping. owner.deviceId not set.'); |
|||
return; |
|||
} |
|||
const unseenMessages = yield models_1.models.Message.findAll({ where: { sender: { [sequelize_1.Op.ne]: owner.id }, seen: false } }); |
|||
const params = { |
|||
device_id: owner.deviceId, |
|||
notification: { |
|||
chat_id: chat.id, |
|||
message, |
|||
badge: unseenMessages.length |
|||
} |
|||
}; |
|||
fetch("http://hub.sphinx.chat/api/v1/nodes/notify", { |
|||
method: 'POST', |
|||
body: JSON.stringify(params), |
|||
headers: { 'Content-Type': 'application/json' } |
|||
}) |
|||
.then(res => res.json()) |
|||
.then(json => console.log('[hub notification]', json)); |
|||
}); |
|||
exports.sendNotification = sendNotification; |
|||
//# sourceMappingURL=hub.js.map
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,10 @@ |
|||
"use strict"; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const sequelize_typescript_1 = require("sequelize-typescript"); |
|||
const env = process.env.NODE_ENV || 'development'; |
|||
const config = require(__dirname + '/../../config/config.json')[env]; |
|||
const sequelize = new sequelize_typescript_1.Sequelize(Object.assign(Object.assign({}, config), { logging: process.env.SQL_LOG === 'true' ? console.log : false, models: [__dirname + '/ts'] })); |
|||
exports.sequelize = sequelize; |
|||
const models = sequelize.models; |
|||
exports.models = models; |
|||
//# sourceMappingURL=index.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../api/models/index.ts"],"names":[],"mappings":";;AAAA,+DAA+C;AAE/C,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,aAAa,CAAC;AAClD,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,2BAA2B,CAAC,CAAC,GAAG,CAAC,CAAC;AAErE,MAAM,SAAS,GAAG,IAAI,gCAAS,iCAC1B,MAAM,KACT,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,KAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAC3D,MAAM,EAAE,CAAC,SAAS,GAAG,KAAK,CAAC,IAC3B,CAAA;AAIA,8BAAS;AAHX,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAA;AAI7B,wBAAM"} |
@ -0,0 +1,72 @@ |
|||
"use strict"; |
|||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { |
|||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; |
|||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); |
|||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; |
|||
return c > 3 && r && Object.defineProperty(target, key, r), r; |
|||
}; |
|||
var __metadata = (this && this.__metadata) || function (k, v) { |
|||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const sequelize_typescript_1 = require("sequelize-typescript"); |
|||
let Chat = class Chat extends sequelize_typescript_1.Model { |
|||
}; |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}), |
|||
__metadata("design:type", Number) |
|||
], Chat.prototype, "id", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Chat.prototype, "uuid", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Chat.prototype, "name", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Chat.prototype, "photoUrl", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Chat.prototype, "type", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Chat.prototype, "status", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Chat.prototype, "contactIds", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Boolean) |
|||
], Chat.prototype, "isMuted", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Chat.prototype, "createdAt", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Chat.prototype, "updatedAt", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BOOLEAN, |
|||
defaultValue: false, |
|||
allowNull: false |
|||
}), |
|||
__metadata("design:type", Boolean) |
|||
], Chat.prototype, "deleted", void 0); |
|||
Chat = __decorate([ |
|||
sequelize_typescript_1.Table({ tableName: 'sphinx_chats', underscored: true }) |
|||
], Chat); |
|||
exports.default = Chat; |
|||
//# sourceMappingURL=chat.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../../../api/models/ts/chat.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,+DAAsE;AAGtE,IAAqB,IAAI,GAAzB,MAAqB,IAAK,SAAQ,4BAAW;CA4C5C,CAAA;AApCC;IANC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,MAAM;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,aAAa,EAAE,IAAI;KACpB,CAAC;;gCACQ;AAGV;IADC,6BAAM;;kCACK;AAGZ;IADC,6BAAM;;kCACK;AAGZ;IADC,6BAAM;;sCACS;AAGhB;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;kCACZ;AAGZ;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;oCACV;AAGd;IADC,6BAAM;;wCACW;AAGlB;IADC,6BAAM;;qCACS;AAGhB;IADC,6BAAM;8BACI,IAAI;uCAAA;AAGf;IADC,6BAAM;8BACI,IAAI;uCAAA;AAOf;IALC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,OAAO;QACtB,YAAY,EAAE,KAAK;QACnB,SAAS,EAAE,KAAK;KACjB,CAAC;;qCACc;AA1CG,IAAI;IADxB,4BAAK,CAAC,EAAC,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,IAAI,EAAC,CAAC;GACjC,IAAI,CA4CxB;kBA5CoB,IAAI"} |
@ -0,0 +1,84 @@ |
|||
"use strict"; |
|||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { |
|||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; |
|||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); |
|||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; |
|||
return c > 3 && r && Object.defineProperty(target, key, r), r; |
|||
}; |
|||
var __metadata = (this && this.__metadata) || function (k, v) { |
|||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const sequelize_typescript_1 = require("sequelize-typescript"); |
|||
let Contact = class Contact extends sequelize_typescript_1.Model { |
|||
}; |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}), |
|||
__metadata("design:type", Number) |
|||
], Contact.prototype, "id", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Contact.prototype, "publicKey", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Contact.prototype, "nodeAlias", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Contact.prototype, "alias", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Contact.prototype, "photoUrl", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Boolean) |
|||
], Contact.prototype, "isOwner", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BOOLEAN, |
|||
defaultValue: false, |
|||
allowNull: false |
|||
}), |
|||
__metadata("design:type", Boolean) |
|||
], Contact.prototype, "deleted", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Contact.prototype, "authToken", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Contact.prototype, "remoteId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Contact.prototype, "status", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.TEXT), |
|||
__metadata("design:type", String) |
|||
], Contact.prototype, "contactKey", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Contact.prototype, "deviceId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Contact.prototype, "createdAt", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Contact.prototype, "updatedAt", void 0); |
|||
Contact = __decorate([ |
|||
sequelize_typescript_1.Table({ tableName: 'sphinx_contacts', underscored: true }) |
|||
], Contact); |
|||
exports.default = Contact; |
|||
//# sourceMappingURL=contact.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"contact.js","sourceRoot":"","sources":["../../../../api/models/ts/contact.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,+DAAsE;AAGtE,IAAqB,OAAO,GAA5B,MAAqB,OAAQ,SAAQ,4BAAc;CAqDlD,CAAA;AA7CC;IANC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,MAAM;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,aAAa,EAAE,IAAI;KACpB,CAAC;;mCACQ;AAGV;IADC,6BAAM;;0CACU;AAGjB;IADC,6BAAM;;0CACU;AAGjB;IADC,6BAAM;;sCACM;AAGb;IADC,6BAAM;;yCACS;AAGhB;IADC,6BAAM;;wCACS;AAOhB;IALC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,OAAO;QACtB,YAAY,EAAE,KAAK;QACnB,SAAS,EAAE,KAAK;KACjB,CAAC;;wCACc;AAGhB;IADC,6BAAM;;0CACU;AAGjB;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;yCACR;AAGhB;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;uCACV;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,IAAI,CAAC;;2CACJ;AAGlB;IADC,6BAAM;;yCACS;AAGhB;IADC,6BAAM;8BACI,IAAI;0CAAA;AAGf;IADC,6BAAM;8BACI,IAAI;0CAAA;AAnDI,OAAO;IAD3B,4BAAK,CAAC,EAAC,SAAS,EAAE,iBAAiB,EAAE,WAAW,EAAE,IAAI,EAAC,CAAC;GACpC,OAAO,CAqD3B;kBArDoB,OAAO"} |
@ -0,0 +1,56 @@ |
|||
"use strict"; |
|||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { |
|||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; |
|||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); |
|||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; |
|||
return c > 3 && r && Object.defineProperty(target, key, r), r; |
|||
}; |
|||
var __metadata = (this && this.__metadata) || function (k, v) { |
|||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const sequelize_typescript_1 = require("sequelize-typescript"); |
|||
let Invite = class Invite extends sequelize_typescript_1.Model { |
|||
}; |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}), |
|||
__metadata("design:type", Number) |
|||
], Invite.prototype, "id", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Invite.prototype, "inviteString", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Invite.prototype, "welcomeMessage", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Invite.prototype, "contactId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Invite.prototype, "status", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.DECIMAL(10, 2)), |
|||
__metadata("design:type", Number) |
|||
], Invite.prototype, "price", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Invite.prototype, "createdAt", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Invite.prototype, "updatedAt", void 0); |
|||
Invite = __decorate([ |
|||
sequelize_typescript_1.Table({ tableName: 'sphinx_invites', underscored: true }) |
|||
], Invite); |
|||
exports.default = Invite; |
|||
//# sourceMappingURL=invite.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"invite.js","sourceRoot":"","sources":["../../../../api/models/ts/invite.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,+DAAsE;AAGtE,IAAqB,MAAM,GAA3B,MAAqB,MAAO,SAAQ,4BAAa;CA+BhD,CAAA;AAvBC;IANC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,MAAM;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,aAAa,EAAE,IAAI;KACpB,CAAC;;kCACQ;AAGV;IADC,6BAAM;;4CACa;AAGpB;IADC,6BAAM;;8CACe;AAGtB;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;yCACP;AAGjB;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;sCACV;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;;qCACnB;AAGb;IADC,6BAAM;8BACI,IAAI;yCAAA;AAGf;IADC,6BAAM;8BACI,IAAI;yCAAA;AA7BI,MAAM;IAD1B,4BAAK,CAAC,EAAC,SAAS,EAAE,gBAAgB,EAAE,WAAW,EAAE,IAAI,EAAC,CAAC;GACnC,MAAM,CA+B1B;kBA/BoB,MAAM"} |
@ -0,0 +1,59 @@ |
|||
"use strict"; |
|||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { |
|||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; |
|||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); |
|||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; |
|||
return c > 3 && r && Object.defineProperty(target, key, r), r; |
|||
}; |
|||
var __metadata = (this && this.__metadata) || function (k, v) { |
|||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const sequelize_typescript_1 = require("sequelize-typescript"); |
|||
/* |
|||
Used for media uploads. When you upload a file, |
|||
also upload the symetric key encrypted for each chat member. |
|||
When they buy the file, they can retrieve the key from here. |
|||
|
|||
"received" media keys are not stored here, only in Message |
|||
*/ |
|||
let MediaKey = class MediaKey extends sequelize_typescript_1.Model { |
|||
}; |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}), |
|||
__metadata("design:type", Number) |
|||
], MediaKey.prototype, "id", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], MediaKey.prototype, "muid", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], MediaKey.prototype, "chatId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], MediaKey.prototype, "receiver", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], MediaKey.prototype, "key", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], MediaKey.prototype, "messageId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], MediaKey.prototype, "createdAt", void 0); |
|||
MediaKey = __decorate([ |
|||
sequelize_typescript_1.Table({ tableName: 'sphinx_media_keys', underscored: true }) |
|||
], MediaKey); |
|||
exports.default = MediaKey; |
|||
//# sourceMappingURL=mediaKey.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"mediaKey.js","sourceRoot":"","sources":["../../../../api/models/ts/mediaKey.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,+DAAsE;AAEtE;;;;;;EAME;AAGF,IAAqB,QAAQ,GAA7B,MAAqB,QAAS,SAAQ,4BAAe;CA2BpD,CAAA;AAnBC;IANC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,MAAM;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,aAAa,EAAE,IAAI;KACpB,CAAC;;oCACQ;AAGV;IADC,6BAAM;;sCACK;AAGZ;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;wCACV;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;0CACR;AAGhB;IADC,6BAAM;;qCACI;AAGX;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;2CACP;AAGjB;IADC,6BAAM;8BACI,IAAI;2CAAA;AA1BI,QAAQ;IAD5B,4BAAK,CAAC,EAAC,SAAS,EAAE,mBAAmB,EAAE,WAAW,EAAE,IAAI,EAAC,CAAC;GACtC,QAAQ,CA2B5B;kBA3BoB,QAAQ"} |
@ -0,0 +1,128 @@ |
|||
"use strict"; |
|||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { |
|||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; |
|||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); |
|||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; |
|||
return c > 3 && r && Object.defineProperty(target, key, r), r; |
|||
}; |
|||
var __metadata = (this && this.__metadata) || function (k, v) { |
|||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const sequelize_typescript_1 = require("sequelize-typescript"); |
|||
let Message = class Message extends sequelize_typescript_1.Model { |
|||
}; |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "id", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "chatId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "type", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "sender", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "receiver", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.DECIMAL), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "amount", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.DECIMAL), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "amountMsat", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "paymentHash", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.TEXT), |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "paymentRequest", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Message.prototype, "date", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Message.prototype, "expirationDate", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.TEXT), |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "messageContent", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.TEXT), |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "remoteMessageContent", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "status", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.TEXT), |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "statusMap", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "parentId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Message.prototype, "subscriptionId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "mediaTerms", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "receipt", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "mediaKey", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "mediaType", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", String) |
|||
], Message.prototype, "mediaToken", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BOOLEAN, |
|||
defaultValue: false, |
|||
allowNull: false |
|||
}), |
|||
__metadata("design:type", Boolean) |
|||
], Message.prototype, "seen", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Message.prototype, "createdAt", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Message.prototype, "updatedAt", void 0); |
|||
Message = __decorate([ |
|||
sequelize_typescript_1.Table({ tableName: 'sphinx_messages', underscored: true }) |
|||
], Message); |
|||
exports.default = Message; |
|||
//# sourceMappingURL=message.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"message.js","sourceRoot":"","sources":["../../../../api/models/ts/message.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,+DAAsE;AAGtE,IAAqB,OAAO,GAA5B,MAAqB,OAAQ,SAAQ,4BAAc;CAqFlD,CAAA;AA7EC;IANC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,MAAM;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,aAAa,EAAE,IAAI;KACpB,CAAC;;mCACQ;AAGV;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;uCACV;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;qCACZ;AAGZ;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;uCACV;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;yCACR;AAGhB;IADC,6BAAM,CAAC,+BAAQ,CAAC,OAAO,CAAC;;uCACX;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,OAAO,CAAC;;2CACP;AAGlB;IADC,6BAAM;;4CACY;AAGnB;IADC,6BAAM,CAAC,+BAAQ,CAAC,IAAI,CAAC;;+CACA;AAGtB;IADC,6BAAM;8BACD,IAAI;qCAAA;AAGV;IADC,6BAAM;8BACS,IAAI;+CAAA;AAGpB;IADC,6BAAM,CAAC,+BAAQ,CAAC,IAAI,CAAC;;+CACA;AAGtB;IADC,6BAAM,CAAC,+BAAQ,CAAC,IAAI,CAAC;;qDACM;AAG5B;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;uCACV;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,IAAI,CAAC;;0CACL;AAGjB;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;yCACR;AAGhB;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;+CACF;AAGtB;IADC,6BAAM;;2CACW;AAGlB;IADC,6BAAM;;wCACQ;AAGf;IADC,6BAAM;;yCACS;AAGhB;IADC,6BAAM;;0CACU;AAGjB;IADC,6BAAM;;2CACW;AAOlB;IALC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,OAAO;QACtB,YAAY,EAAE,KAAK;QACnB,SAAS,EAAE,KAAK;KACjB,CAAC;;qCACW;AAGb;IADC,6BAAM;8BACI,IAAI;0CAAA;AAGf;IADC,6BAAM;8BACI,IAAI;0CAAA;AApFI,OAAO;IAD3B,4BAAK,CAAC,EAAC,SAAS,EAAE,iBAAiB,EAAE,WAAW,EAAE,IAAI,EAAC,CAAC;GACpC,OAAO,CAqF3B;kBArFoB,OAAO"} |
@ -0,0 +1,76 @@ |
|||
"use strict"; |
|||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { |
|||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; |
|||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); |
|||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; |
|||
return c > 3 && r && Object.defineProperty(target, key, r), r; |
|||
}; |
|||
var __metadata = (this && this.__metadata) || function (k, v) { |
|||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); |
|||
}; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const sequelize_typescript_1 = require("sequelize-typescript"); |
|||
let Subscription = class Subscription extends sequelize_typescript_1.Model { |
|||
}; |
|||
__decorate([ |
|||
sequelize_typescript_1.Column({ |
|||
type: sequelize_typescript_1.DataType.BIGINT, |
|||
primaryKey: true, |
|||
unique: true, |
|||
autoIncrement: true |
|||
}), |
|||
__metadata("design:type", Number) |
|||
], Subscription.prototype, "id", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Subscription.prototype, "chatId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Subscription.prototype, "contactId", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.TEXT), |
|||
__metadata("design:type", String) |
|||
], Subscription.prototype, "cron", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.DECIMAL), |
|||
__metadata("design:type", Number) |
|||
], Subscription.prototype, "amount", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.DECIMAL), |
|||
__metadata("design:type", Number) |
|||
], Subscription.prototype, "totalPaid", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Subscription.prototype, "endNumber", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Subscription.prototype, "endDate", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column(sequelize_typescript_1.DataType.BIGINT), |
|||
__metadata("design:type", Number) |
|||
], Subscription.prototype, "count", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Boolean) |
|||
], Subscription.prototype, "ended", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Boolean) |
|||
], Subscription.prototype, "paused", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Subscription.prototype, "createdAt", void 0); |
|||
__decorate([ |
|||
sequelize_typescript_1.Column, |
|||
__metadata("design:type", Date) |
|||
], Subscription.prototype, "updatedAt", void 0); |
|||
Subscription = __decorate([ |
|||
sequelize_typescript_1.Table({ tableName: 'sphinx_subscriptions', underscored: true }) |
|||
], Subscription); |
|||
exports.default = Subscription; |
|||
//# sourceMappingURL=subscription.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../../../api/models/ts/subscription.ts"],"names":[],"mappings":";;;;;;;;;;;AAAA,+DAAsE;AAGtE,IAAqB,YAAY,GAAjC,MAAqB,YAAa,SAAQ,4BAAmB;CA6C5D,CAAA;AArCC;IANC,6BAAM,CAAC;QACN,IAAI,EAAE,+BAAQ,CAAC,MAAM;QACrB,UAAU,EAAE,IAAI;QAChB,MAAM,EAAE,IAAI;QACZ,aAAa,EAAE,IAAI;KACpB,CAAC;;wCACQ;AAGV;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;4CACV;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;+CACP;AAGjB;IADC,6BAAM,CAAC,+BAAQ,CAAC,IAAI,CAAC;;0CACV;AAGZ;IADC,6BAAM,CAAC,+BAAQ,CAAC,OAAO,CAAC;;4CACX;AAGd;IADC,6BAAM,CAAC,+BAAQ,CAAC,OAAO,CAAC;;+CACR;AAGjB;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;+CACP;AAGjB;IADC,6BAAM;8BACE,IAAI;6CAAA;AAGb;IADC,6BAAM,CAAC,+BAAQ,CAAC,MAAM,CAAC;;2CACX;AAGb;IADC,6BAAM;;2CACO;AAGd;IADC,6BAAM;;4CACQ;AAGf;IADC,6BAAM;8BACI,IAAI;+CAAA;AAGf;IADC,6BAAM;8BACI,IAAI;+CAAA;AA5CI,YAAY;IADhC,4BAAK,CAAC,EAAC,SAAS,EAAE,sBAAsB,EAAE,WAAW,EAAE,IAAI,EAAC,CAAC;GACzC,YAAY,CA6ChC;kBA7CoB,YAAY"} |
@ -0,0 +1,28 @@ |
|||
"use strict"; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const changeCase = require("change-case"); |
|||
const dateKeys = ['date', 'createdAt', 'updatedAt', 'created_at', 'updated_at']; |
|||
function toSnake(obj) { |
|||
const ret = {}; |
|||
for (let [key, value] of Object.entries(obj)) { |
|||
if (dateKeys.includes(key) && value) { |
|||
const v = value; |
|||
const d = new Date(v); |
|||
ret[changeCase.snakeCase(key)] = d.toISOString(); |
|||
} |
|||
else { |
|||
ret[changeCase.snakeCase(key)] = value; |
|||
} |
|||
} |
|||
return ret; |
|||
} |
|||
exports.toSnake = toSnake; |
|||
function toCamel(obj) { |
|||
const ret = {}; |
|||
for (let [key, value] of Object.entries(obj)) { |
|||
ret[changeCase.camelCase(key)] = value; |
|||
} |
|||
return ret; |
|||
} |
|||
exports.toCamel = toCamel; |
|||
//# sourceMappingURL=case.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"case.js","sourceRoot":"","sources":["../../../api/utils/case.ts"],"names":[],"mappings":";;AAAA,0CAA0C;AAE1C,MAAM,QAAQ,GAAG,CAAC,MAAM,EAAC,WAAW,EAAC,WAAW,EAAC,YAAY,EAAC,YAAY,CAAC,CAAA;AAE3E,SAAS,OAAO,CAAC,GAAG;IAChB,MAAM,GAAG,GAAuB,EAAE,CAAA;IAClC,KAAK,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QAC1C,IAAG,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,EAAC;YAC/B,MAAM,CAAC,GAAQ,KAAK,CAAA;YACpB,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAA;YACrB,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,CAAA;SACnD;aAAM;YACH,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAA;SACzC;KACJ;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAUO,0BAAO;AARf,SAAS,OAAO,CAAC,GAAG;IAChB,MAAM,GAAG,GAAuB,EAAE,CAAA;IAClC,KAAK,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QAC1C,GAAG,CAAC,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAA;KACzC;IACD,OAAO,GAAG,CAAA;AACd,CAAC;AAEgB,0BAAO"} |
@ -0,0 +1,45 @@ |
|||
"use strict"; |
|||
Object.defineProperty(exports, "__esModule", { value: true }); |
|||
const parser = require("cron-parser"); |
|||
function daily() { |
|||
const now = new Date(); |
|||
const minute = now.getMinutes(); |
|||
const hour = now.getHours(); |
|||
return `${minute} ${hour} * * *`; |
|||
} |
|||
function weekly() { |
|||
const now = new Date(); |
|||
const minute = now.getMinutes(); |
|||
const hour = now.getHours(); |
|||
const dayOfWeek = now.getDay(); |
|||
return `${minute} ${hour} * * ${dayOfWeek}`; |
|||
} |
|||
function monthly() { |
|||
const now = new Date(); |
|||
const minute = now.getMinutes(); |
|||
const hour = now.getHours(); |
|||
const dayOfMonth = now.getDate(); |
|||
return `${minute} ${hour} ${dayOfMonth} * *`; |
|||
} |
|||
function parse(s) { |
|||
var interval = parser.parseExpression(s); |
|||
const next = interval.next().toString(); |
|||
if (s.endsWith(' * * *')) { |
|||
return { interval: 'daily', next, ms: 86400000 }; |
|||
} |
|||
if (s.endsWith(' * *')) { |
|||
return { interval: 'monthly', next, ms: 86400000 * 30 }; |
|||
} |
|||
return { interval: 'weekly', next, ms: 86400000 * 7 }; |
|||
} |
|||
exports.parse = parse; |
|||
function make(interval) { |
|||
if (interval === 'daily') |
|||
return daily(); |
|||
if (interval === 'weekly') |
|||
return weekly(); |
|||
if (interval === 'monthly') |
|||
return monthly(); |
|||
} |
|||
exports.make = make; |
|||
//# sourceMappingURL=cron.js.map
|
@ -0,0 +1 @@ |
|||
{"version":3,"file":"cron.js","sourceRoot":"","sources":["../../../api/utils/cron.ts"],"names":[],"mappings":";;AAAA,sCAAqC;AAErC,SAAS,KAAK;IACV,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;IACtB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,EAAE,CAAA;IAC/B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAA;IAC3B,OAAO,GAAG,MAAM,IAAI,IAAI,QAAQ,CAAA;AACpC,CAAC;AAED,SAAS,MAAM;IACX,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;IACtB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,EAAE,CAAA;IAC/B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAA;IAC3B,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,EAAE,CAAA;IAC9B,OAAO,GAAG,MAAM,IAAI,IAAI,QAAQ,SAAS,EAAE,CAAA;AAC/C,CAAC;AAED,SAAS,OAAO;IACZ,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAA;IACtB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,EAAE,CAAA;IAC/B,MAAM,IAAI,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAA;IAC3B,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,EAAE,CAAA;IAChC,OAAO,GAAG,MAAM,IAAI,IAAI,IAAI,UAAU,MAAM,CAAA;AAChD,CAAC;AAED,SAAS,KAAK,CAAC,CAAC;IACZ,IAAI,QAAQ,GAAG,MAAM,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACzC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAA;IAEvC,IAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE;QACrB,OAAO,EAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAC,QAAQ,EAAC,CAAA;KAChD;IACD,IAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE;QACnB,OAAO,EAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,EAAC,QAAQ,GAAC,EAAE,EAAC,CAAA;KACrD;IACD,OAAO,EAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAC,QAAQ,GAAC,CAAC,EAAC,CAAA;AACpD,CAAC;AASG,sBAAK;AAPT,SAAS,IAAI,CAAC,QAAQ;IAClB,IAAG,QAAQ,KAAG,OAAO;QAAE,OAAO,KAAK,EAAE,CAAA;IACrC,IAAG,QAAQ,KAAG,QAAQ;QAAE,OAAO,MAAM,EAAE,CAAA;IACvC,IAAG,QAAQ,KAAG,SAAS;QAAE,OAAO,OAAO,EAAE,CAAA;AAC7C,CAAC;AAIG,oBAAI"} |
@ -0,0 +1,294 @@ |
|||
const bech32CharValues = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; |
|||
module.exports = { |
|||
decode: function (paymentRequest) { |
|||
let input = paymentRequest.toLowerCase(); |
|||
let splitPosition = input.lastIndexOf('1'); |
|||
let humanReadablePart = input.substring(0, splitPosition); |
|||
let data = input.substring(splitPosition + 1, input.length - 6); |
|||
let checksum = input.substring(input.length - 6, input.length); |
|||
if (!this.verify_checksum(humanReadablePart, this.bech32ToFiveBitArray(data + checksum))) { |
|||
return 'error'; |
|||
} |
|||
return { |
|||
'human_readable_part': this.decodeHumanReadablePart(humanReadablePart), |
|||
'data': this.decodeData(data, humanReadablePart), |
|||
'checksum': checksum |
|||
}; |
|||
}, |
|||
decodeHumanReadablePart: function (humanReadablePart) { |
|||
let prefixes = ['lnbc', 'lntb', 'lnbcrt']; |
|||
let prefix; |
|||
prefixes.forEach(value => { |
|||
if (humanReadablePart.substring(0, value.length) === value) { |
|||
prefix = value; |
|||
} |
|||
}); |
|||
if (prefix == null) |
|||
return 'error'; // A reader MUST fail if it does not understand the prefix.
|
|||
let amount = this.decodeAmount(humanReadablePart.substring(prefix.length, humanReadablePart.length)); |
|||
return { |
|||
'prefix': prefix, |
|||
'amount': amount |
|||
}; |
|||
}, |
|||
decodeData: function (data, humanReadablePart) { |
|||
let date32 = data.substring(0, 7); |
|||
let dateEpoch = this.bech32ToInt(date32); |
|||
let signature = data.substring(data.length - 104, data.length); |
|||
let tagData = data.substring(7, data.length - 104); |
|||
let decodedTags = this.decodeTags(tagData); |
|||
let value = this.bech32ToFiveBitArray(date32 + tagData); |
|||
value = this.fiveBitArrayTo8BitArray(value, true); |
|||
value = this.textToHexString(humanReadablePart).concat(this.byteArrayToHexString(value)); |
|||
return { |
|||
'time_stamp': dateEpoch, |
|||
'tags': decodedTags, |
|||
'signature': this.decodeSignature(signature), |
|||
'signing_data': value |
|||
}; |
|||
}, |
|||
decodeSignature: function (signature) { |
|||
let data = this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(signature)); |
|||
let recoveryFlag = data[data.length - 1]; |
|||
let r = this.byteArrayToHexString(data.slice(0, 32)); |
|||
let s = this.byteArrayToHexString(data.slice(32, data.length - 1)); |
|||
return { |
|||
'r': r, |
|||
's': s, |
|||
'recovery_flag': recoveryFlag |
|||
}; |
|||
}, |
|||
decodeAmount: function (str) { |
|||
let multiplier = str.charAt(str.length - 1); |
|||
let amount = str.substring(0, str.length - 1); |
|||
if (amount.substring(0, 1) === '0') { |
|||
return 'error'; |
|||
} |
|||
amount = Number(amount); |
|||
if (amount < 0 || !Number.isInteger(amount)) { |
|||
return 'error'; |
|||
} |
|||
switch (multiplier) { |
|||
case '': |
|||
return 'Any amount'; // A reader SHOULD indicate if amount is unspecified
|
|||
case 'p': |
|||
return amount / 10; |
|||
case 'n': |
|||
return amount * 100; |
|||
case 'u': |
|||
return amount * 100000; |
|||
case 'm': |
|||
return amount * 100000000; |
|||
default: |
|||
// A reader SHOULD fail if amount is followed by anything except a defined multiplier.
|
|||
return 'error'; |
|||
} |
|||
}, |
|||
decodeTags: function (tagData) { |
|||
let tags = this.extractTags(tagData); |
|||
let decodedTags = []; |
|||
tags.forEach(value => decodedTags.push(this.decodeTag(value.type, value.length, value.data))); |
|||
return decodedTags; |
|||
}, |
|||
extractTags: function (str) { |
|||
let tags = []; |
|||
while (str.length > 0) { |
|||
let type = str.charAt(0); |
|||
let dataLength = this.bech32ToInt(str.substring(1, 3)); |
|||
let data = str.substring(3, dataLength + 3); |
|||
tags.push({ |
|||
'type': type, |
|||
'length': dataLength, |
|||
'data': data |
|||
}); |
|||
str = str.substring(3 + dataLength, str.length); |
|||
} |
|||
return tags; |
|||
}, |
|||
decodeTag: function (type, length, data) { |
|||
switch (type) { |
|||
case 'p': |
|||
if (length !== 52) |
|||
break; // A reader MUST skip over a 'p' field that does not have data_length 52
|
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'payment_hash', |
|||
'value': this.byteArrayToHexString(this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data))) |
|||
}; |
|||
case 'd': |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'description', |
|||
'value': this.bech32ToUTF8String(data) |
|||
}; |
|||
case 'n': |
|||
if (length !== 53) |
|||
break; // A reader MUST skip over a 'n' field that does not have data_length 53
|
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'payee_public_key', |
|||
'value': this.byteArrayToHexString(this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data))) |
|||
}; |
|||
case 'h': |
|||
if (length !== 52) |
|||
break; // A reader MUST skip over a 'h' field that does not have data_length 52
|
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'description_hash', |
|||
'value': data |
|||
}; |
|||
case 'x': |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'expiry', |
|||
'value': this.bech32ToInt(data) |
|||
}; |
|||
case 'c': |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'min_final_cltv_expiry', |
|||
'value': this.bech32ToInt(data) |
|||
}; |
|||
case 'f': |
|||
let version = this.bech32ToFiveBitArray(data.charAt(0))[0]; |
|||
if (version < 0 || version > 18) |
|||
break; // a reader MUST skip over an f field with unknown version.
|
|||
data = data.substring(1, data.length); |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'fallback_address', |
|||
'value': { |
|||
'version': version, |
|||
'fallback_address': data |
|||
} |
|||
}; |
|||
case 'r': |
|||
data = this.fiveBitArrayTo8BitArray(this.bech32ToFiveBitArray(data)); |
|||
let pubkey = data.slice(0, 33); |
|||
let shortChannelId = data.slice(33, 41); |
|||
let feeBaseMsat = data.slice(41, 45); |
|||
let feeProportionalMillionths = data.slice(45, 49); |
|||
let cltvExpiryDelta = data.slice(49, 51); |
|||
return { |
|||
'type': type, |
|||
'length': length, |
|||
'description': 'routing_information', |
|||
'value': { |
|||
'public_key': this.byteArrayToHexString(pubkey), |
|||
'short_channel_id': this.byteArrayToHexString(shortChannelId), |
|||
'fee_base_msat': this.byteArrayToInt(feeBaseMsat), |
|||
'fee_proportional_millionths': this.byteArrayToInt(feeProportionalMillionths), |
|||
'cltv_expiry_delta': this.byteArrayToInt(cltvExpiryDelta) |
|||
} |
|||
}; |
|||
default: |
|||
// reader MUST skip over unknown fields
|
|||
} |
|||
}, |
|||
polymod: function (values) { |
|||
let GEN = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]; |
|||
let chk = 1; |
|||
values.forEach((value) => { |
|||
let b = (chk >> 25); |
|||
chk = (chk & 0x1ffffff) << 5 ^ value; |
|||
for (let i = 0; i < 5; i++) { |
|||
if (((b >> i) & 1) === 1) { |
|||
chk ^= GEN[i]; |
|||
} |
|||
else { |
|||
chk ^= 0; |
|||
} |
|||
} |
|||
}); |
|||
return chk; |
|||
}, |
|||
expand: function (str) { |
|||
let array = []; |
|||
for (let i = 0; i < str.length; i++) { |
|||
array.push(str.charCodeAt(i) >> 5); |
|||
} |
|||
array.push(0); |
|||
for (let i = 0; i < str.length; i++) { |
|||
array.push(str.charCodeAt(i) & 31); |
|||
} |
|||
return array; |
|||
}, |
|||
verify_checksum: function (hrp, data) { |
|||
hrp = this.expand(hrp); |
|||
let all = hrp.concat(data); |
|||
let bool = this.polymod(all); |
|||
return bool === 1; |
|||
}, |
|||
byteArrayToInt: function (byteArray) { |
|||
let value = 0; |
|||
for (let i = 0; i < byteArray.length; ++i) { |
|||
value = (value << 8) + byteArray[i]; |
|||
} |
|||
return value; |
|||
}, |
|||
bech32ToInt: function (str) { |
|||
let sum = 0; |
|||
for (let i = 0; i < str.length; i++) { |
|||
sum = sum * 32; |
|||
sum = sum + bech32CharValues.indexOf(str.charAt(i)); |
|||
} |
|||
return sum; |
|||
}, |
|||
bech32ToFiveBitArray: function (str) { |
|||
let array = []; |
|||
for (let i = 0; i < str.length; i++) { |
|||
array.push(bech32CharValues.indexOf(str.charAt(i))); |
|||
} |
|||
return array; |
|||
}, |
|||
fiveBitArrayTo8BitArray: function (int5Array, includeOverflow) { |
|||
let count = 0; |
|||
let buffer = 0; |
|||
let byteArray = []; |
|||
int5Array.forEach((value) => { |
|||
buffer = (buffer << 5) + value; |
|||
count += 5; |
|||
if (count >= 8) { |
|||
byteArray.push(buffer >> (count - 8) & 255); |
|||
count -= 8; |
|||
} |
|||
}); |
|||
if (includeOverflow && count > 0) { |
|||
byteArray.push(buffer << (8 - count) & 255); |
|||
} |
|||
return byteArray; |
|||
}, |
|||
bech32ToUTF8String: function (str) { |
|||
let int5Array = this.bech32ToFiveBitArray(str); |
|||
let byteArray = this.fiveBitArrayTo8BitArray(int5Array); |
|||
let utf8String = ''; |
|||
for (let i = 0; i < byteArray.length; i++) { |
|||
utf8String += '%' + ('0' + byteArray[i].toString(16)).slice(-2); |
|||
} |
|||
return decodeURIComponent(utf8String); |
|||
}, |
|||
byteArrayToHexString: function (byteArray) { |
|||
return Array.prototype.map.call(byteArray, function (byte) { |
|||
return ('0' + (byte & 0xFF).toString(16)).slice(-2); |
|||
}).join(''); |
|||
}, |
|||
textToHexString: function (text) { |
|||
let hexString = ''; |
|||
for (let i = 0; i < text.length; i++) { |
|||
hexString += text.charCodeAt(i).toString(16); |
|||
} |
|||
return hexString; |
|||
}, |
|||
epochToDate: function (int) { |
|||
let date = new Date(int * 1000); |
|||
return date.toUTCString(); |
|||
} |
|||
}; |
|||
//# sourceMappingURL=index.js.map
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue