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