Merge branch 'release-10.0' into feat/lam-1111/customer-last-used-machine
* release-10.0: chore: use map instead of foreach chore: v10.0.1 (#1696) Add InforU SMS plugin (#1695) fix: add machine name and timestamp to pending notifs messages refactor: drop unused debug logs refactor: replace `fs`+`util.promisify` with `fs/promises` refactor: drop unnecessary use of `_.curry` refactor: destruct in parameters list refactor: drop unnecessary curly braces chore: drop unused import refactor: destruct in parameters list to build object refactor: destruct in parameters list instead refactor: use `set` from `lodash/fp` instead of syntax feat: notifications and flow fixes fix: generic external auth on ui fix: admin ui refactor: yagni and flow of external compliance fix: change sumsub usage away from self-hosted solutions feat: implement sumsub API module chore: remove unused files
This commit is contained in:
commit
4b4af106a8
338 changed files with 1005 additions and 81792 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -33,8 +33,8 @@ seeds/
|
||||||
mnemonics/
|
mnemonics/
|
||||||
certs/
|
certs/
|
||||||
blockchains/
|
blockchains/
|
||||||
test/stress/machines
|
tests/stress/machines
|
||||||
test/stress/config.json
|
tests/stress/config.json
|
||||||
lamassu.json
|
lamassu.json
|
||||||
|
|
||||||
terraform.*
|
terraform.*
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
FROM node:14 as build-admin
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# COPY new-lamassu-admin/src packages/lamassu-admin/src
|
|
||||||
# COPY new-lamassu-admin/patches packages/lamassu-admin/patches
|
|
||||||
# COPY new-lamassu-admin/public packages/lamassu-admin/public
|
|
||||||
# COPY new-lamassu-admin/nginx packages/lamassu-admin/nginx
|
|
||||||
# COPY new-lamassu-admin/.env packages/lamassu-admin/.env
|
|
||||||
# COPY new-lamassu-admin/.eslintrc.js packages/lamassu-admin/.eslintrc.js
|
|
||||||
# COPY new-lamassu-admin/jsconfig.json packages/lamassu-admin/jsconfig.json
|
|
||||||
# COPY new-lamassu-admin/package.json packages/lamassu-admin/package.json
|
|
||||||
|
|
||||||
COPY new-lamassu-admin packages/lamassu-admin
|
|
||||||
|
|
||||||
WORKDIR /app/packages/lamassu-admin
|
|
||||||
|
|
||||||
# RUN npm install
|
|
||||||
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
FROM nginx:1.21.4-alpine as production-admin
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
|
|
||||||
COPY --from=build-admin /app/packages/lamassu-admin/build /usr/share/nginx/html/
|
|
||||||
RUN rm /etc/nginx/conf.d/default.conf
|
|
||||||
COPY --from=build-admin /app/packages/lamassu-admin/nginx/nginx.conf /etc/nginx/conf.d
|
|
||||||
|
|
||||||
EXPOSE 80
|
|
||||||
|
|
||||||
CMD [ "nginx", "-g", "daemon off;" ]
|
|
||||||
36
Dockerfiles/common.Dockerfile
Normal file
36
Dockerfiles/common.Dockerfile
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
FROM ubuntu:20.04 as base
|
||||||
|
|
||||||
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
ENV TZ=Europe/Lisbon
|
||||||
|
|
||||||
|
RUN apt-get update
|
||||||
|
|
||||||
|
RUN apt-get install -y -q curl \
|
||||||
|
sudo \
|
||||||
|
git \
|
||||||
|
python2-minimal \
|
||||||
|
build-essential \
|
||||||
|
libpq-dev \
|
||||||
|
net-tools
|
||||||
|
|
||||||
|
RUN curl -sL https://deb.nodesource.com/setup_14.x | sudo -E bash -
|
||||||
|
RUN apt-get install nodejs -y -q
|
||||||
|
|
||||||
|
FROM base as build-l-s
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY bin/ packages/lamassu-server/bin
|
||||||
|
COPY lib/ packages/lamassu-server/lib
|
||||||
|
COPY data/ packages/lamassu-server/data
|
||||||
|
COPY tools/ packages/lamassu-server/tools
|
||||||
|
COPY migrations/ packages/lamassu-server/migrations
|
||||||
|
COPY package.json packages/lamassu-server/package.json
|
||||||
|
COPY Lamassu_CA.pem packages/lamassu-server/Lamassu_CA.pem
|
||||||
|
|
||||||
|
WORKDIR /app/packages/lamassu-server
|
||||||
|
|
||||||
|
RUN chmod +x tools/build-docker-certs.sh
|
||||||
|
RUN chmod +x bin/lamassu-server-entrypoint.sh
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
|
|
||||||
var pgp = require('pg-promise')()
|
|
||||||
|
|
||||||
require('../lib/environment-helper')
|
|
||||||
const { PSQL_URL } = require('../lib/constants')
|
|
||||||
|
|
||||||
var db = pgp(PSQL_URL)
|
|
||||||
|
|
||||||
db.manyOrNone(`select * from transactions where incoming=false
|
|
||||||
and stage='final_request' and authority='machine'`)
|
|
||||||
.then(rs =>
|
|
||||||
db.tx(t =>
|
|
||||||
t.batch(rs.map(r => db.none(`insert into cash_in_txs (session_id,
|
|
||||||
device_fingerprint, to_address, crypto_atoms, crypto_code, fiat,
|
|
||||||
currency_code, fee, tx_hash, error, created) values ($1, $2, $3, $4, $5,
|
|
||||||
$6, $7, $8, $9, $10, $11)`, [r.session_id, r.device_fingerprint,
|
|
||||||
r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code, r.fee,
|
|
||||||
r.tx_hash, r.error, r.created]))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(() => db.manyOrNone(`select * from transactions where incoming=true
|
|
||||||
and stage='initial_request' and authority='pending'`))
|
|
||||||
.then(rs =>
|
|
||||||
db.tx(t =>
|
|
||||||
t.batch(rs.map(r => db.none(`insert into cash_out_txs (session_id,
|
|
||||||
device_fingerprint, to_address, crypto_atoms, crypto_code, fiat,
|
|
||||||
currency_code, tx_hash, phone, error, created) values ($1, $2, $3, $4, $5,
|
|
||||||
$6, $7, $8, $9, $10, $11)`, [r.session_id, r.device_fingerprint,
|
|
||||||
r.to_address, r.satoshis, r.crypto_code, r.fiat, r.currency_code,
|
|
||||||
r.tx_hash, r.phone, r.error, r.created]))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(() => db.manyOrNone(`select * from transactions where incoming=true
|
|
||||||
and stage='dispense' and authority='authorized'`))
|
|
||||||
.then(rs =>
|
|
||||||
db.tx(t =>
|
|
||||||
t.batch(rs.map(r =>
|
|
||||||
db.none(`update cash_out_txs set dispensed=true where session_id=$1`, [r.session_id])
|
|
||||||
.then(() => db.none(`insert into cash_out_actions (session_id, action,
|
|
||||||
created) values ($1, $2, $3)`, [r.session_id, 'dispensed', r.created]))
|
|
||||||
))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.then(() => pgp.end())
|
|
||||||
.then(() => console.log('Success.'))
|
|
||||||
.catch(e => {
|
|
||||||
console.log(e)
|
|
||||||
pgp.end()
|
|
||||||
})
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
node bin/new-lamassu-admin-server --dev & node bin/new-graphql-dev-insecure
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
require('../lib/environment-helper')
|
|
||||||
|
|
||||||
const settingsLoader = require('../lib/new-settings-loader')
|
|
||||||
const pp = require('../lib/pp')
|
|
||||||
|
|
||||||
settingsLoader.loadLatest()
|
|
||||||
.then(r => {
|
|
||||||
pp('config')(r)
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
console.log(e.stack)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
require('../lib/environment-helper')
|
|
||||||
|
|
||||||
const hdkey = require('ethereumjs-wallet/hdkey')
|
|
||||||
const _ = require('lodash/fp')
|
|
||||||
const hkdf = require('futoin-hkdf')
|
|
||||||
const pify = require('pify')
|
|
||||||
const fs = pify(require('fs'))
|
|
||||||
const Web3 = require('web3')
|
|
||||||
const web3 = new Web3()
|
|
||||||
|
|
||||||
const db = require('../lib/db')
|
|
||||||
const configManager = require('../lib/new-config-manager')
|
|
||||||
const { loadLatest } = require('../lib/new-settings-loader')
|
|
||||||
const mnemonicHelpers = require('../lib/mnemonic-helpers')
|
|
||||||
const { sweep } = require('../lib/wallet')
|
|
||||||
const ph = require('../lib/plugin-helper')
|
|
||||||
|
|
||||||
const MNEMONIC_PATH = process.env.MNEMONIC_PATH
|
|
||||||
|
|
||||||
function fetchWallet (settings, cryptoCode) {
|
|
||||||
return fs.readFile(MNEMONIC_PATH, 'utf8')
|
|
||||||
.then(mnemonic => {
|
|
||||||
const masterSeed = mnemonicHelpers.toEntropyBuffer(mnemonic)
|
|
||||||
const plugin = configManager.getWalletSettings(cryptoCode, settings.config).wallet
|
|
||||||
const wallet = ph.load(ph.WALLET, plugin)
|
|
||||||
const rawAccount = settings.accounts[plugin]
|
|
||||||
const account = _.set('seed', computeSeed(masterSeed), rawAccount)
|
|
||||||
if (_.isFunction(wallet.run)) wallet.run(account)
|
|
||||||
return { wallet, account }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeSeed (masterSeed) {
|
|
||||||
return hkdf(masterSeed, 32, { salt: 'lamassu-server-salt', info: 'wallet-seed' })
|
|
||||||
}
|
|
||||||
|
|
||||||
function paymentHdNode (account) {
|
|
||||||
const masterSeed = account.seed
|
|
||||||
if (!masterSeed) throw new Error('No master seed!')
|
|
||||||
const key = hdkey.fromMasterSeed(masterSeed)
|
|
||||||
return key.derivePath("m/44'/60'/0'/0'")
|
|
||||||
}
|
|
||||||
|
|
||||||
const getHdIndices = db => {
|
|
||||||
const sql = `SELECT id, crypto_code, hd_index FROM cash_out_txs WHERE hd_index IS NOT NULL AND status IN ('confirmed', 'instant') AND crypto_code = 'ETH'`
|
|
||||||
return db.any(sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getCashoutAddresses = (settings, indices) => {
|
|
||||||
return Promise.all(_.map(it => {
|
|
||||||
return fetchWallet(settings, it.crypto_code)
|
|
||||||
.then(({ wallet, account }) => Promise.all([wallet, paymentHdNode(account).deriveChild(it.hd_index).getWallet().getChecksumAddressString()]))
|
|
||||||
.then(([wallet, address]) => Promise.all([address, wallet._balance(false, address, 'ETH')]))
|
|
||||||
.then(([address, balance]) => ({ address, balance: balance.toNumber(), cryptoCode: it.crypto_code, index: it.hd_index, txId: it.id }))
|
|
||||||
}, indices))
|
|
||||||
}
|
|
||||||
|
|
||||||
Promise.all([getHdIndices(db), loadLatest()])
|
|
||||||
.then(([indices, settings]) => Promise.all([settings, getCashoutAddresses(settings, indices)]))
|
|
||||||
.then(([settings, addresses]) => {
|
|
||||||
console.log('Found these cash-out addresses for ETH:')
|
|
||||||
console.log(addresses)
|
|
||||||
|
|
||||||
return Promise.all(_.map(it => {
|
|
||||||
// If the address only has dust in it, don't bother sweeping
|
|
||||||
if (web3.utils.fromWei(it.balance.toString()) > 0.00001) {
|
|
||||||
console.log(`Address ${it.address} found to have ${web3.utils.fromWei(it.balance.toString())} ETH in it. Sweeping...`)
|
|
||||||
return sweep(settings, it.txId, it.cryptoCode, it.index)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Address ${it.address} contains no significant balance (${web3.utils.fromWei(it.balance.toString())}). Skipping the sweep process...`)
|
|
||||||
return Promise.resolve()
|
|
||||||
}, addresses))
|
|
||||||
})
|
|
||||||
.then(() => console.log('Process finished!'))
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const pgp = require('pg-promise')()
|
|
||||||
|
|
||||||
require('../lib/environment-helper')
|
|
||||||
const { PSQL_URL } = require('../lib/constants')
|
|
||||||
|
|
||||||
const db = pgp(PSQL_URL)
|
|
||||||
|
|
||||||
db.many('select data from user_config', 'exchanges')
|
|
||||||
.then(rows => {
|
|
||||||
const config = rows.filter(r => r.type === 'exchanges')[0].data
|
|
||||||
const brain = rows.filter(r => r.type === 'unit')[0].data
|
|
||||||
const settings = config.exchanges.settings
|
|
||||||
const compliance = settings.compliance
|
|
||||||
const newConfig = {
|
|
||||||
global: {
|
|
||||||
cashInTransactionLimit: compliance.maximum.limit,
|
|
||||||
cashOutTransactionLimit: settings.fiatTxLimit,
|
|
||||||
cashInCommission: settings.commission,
|
|
||||||
cashOutCommission: settings.fiatCommission || settings.commission,
|
|
||||||
idVerificationEnabled: compliance.idVerificationEnabled,
|
|
||||||
idVerificationLimit: compliance.idVerificationLimit,
|
|
||||||
lowBalanceMargin: settings.lowBalanceMargin,
|
|
||||||
zeroConfLimit: settings.zeroConfLimit,
|
|
||||||
fiatCurrency: settings.currency,
|
|
||||||
topCashOutDenomination: settings.cartridges[0],
|
|
||||||
bottomCashOutDenomination: settings.cartridges[1],
|
|
||||||
virtualCashOutDenomination: settings.virtualCartridges[0],
|
|
||||||
machineLanguages: brain.locale.localeInfo.primaryLocales,
|
|
||||||
coins: settings.coins
|
|
||||||
},
|
|
||||||
accounts: settings.plugins.settings
|
|
||||||
}
|
|
||||||
|
|
||||||
db.none('insert into user_config (type, data) values ($1, $2)', ['global', newConfig])
|
|
||||||
.then(() => {
|
|
||||||
console.log('Success.')
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.error('Error: %s', err)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
const car = require('../lib/coinatmradar/coinatmradar')
|
|
||||||
const plugins = require('../lib/plugins')
|
|
||||||
|
|
||||||
require('../lib/new-settings-loader').loadLatest()
|
|
||||||
.then(settings => {
|
|
||||||
const pi = plugins(settings)
|
|
||||||
|
|
||||||
return pi.getRawRates()
|
|
||||||
.then(rates => {
|
|
||||||
return car.update(rates, settings)
|
|
||||||
.then(require('../lib/pp')('DEBUG100'))
|
|
||||||
.catch(console.log)
|
|
||||||
.then(() => process.exit())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
const express = require('express')
|
|
||||||
const app = express()
|
|
||||||
|
|
||||||
app.use(express.raw({ type: '*/*' }))
|
|
||||||
|
|
||||||
app.post('/api/lamassu', (req, res) => {
|
|
||||||
console.log(req.headers)
|
|
||||||
console.log(req.body.toString())
|
|
||||||
res.send('Hello World!')
|
|
||||||
})
|
|
||||||
|
|
||||||
app.listen(3200, () => console.log('Example app listening on port 3200!'))
|
|
||||||
|
|
||||||
// "url": "https://coinatmradar.info/api/lamassu/"
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
const settingsLoader = require('../lib/new-settings-loader')
|
|
||||||
const configManager = require('../lib/new-config-manager')
|
|
||||||
|
|
||||||
settingsLoader.loadLatest()
|
|
||||||
.then(settings => {
|
|
||||||
const config = settings.config
|
|
||||||
require('../lib/pp')('config')(configManager.getAllCryptoCurrencies(config))
|
|
||||||
})
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
const got = require('got')
|
|
||||||
|
|
||||||
const tx = {
|
|
||||||
sessionId: 'a9fdfedc-1d45-11e6-be13-2f68ff6306b9',
|
|
||||||
toAddress: '1DrK44np3gMKuvcGeFVv9Jk67zodP52eMu',
|
|
||||||
fiat: 10
|
|
||||||
}
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
'content-type': 'application/json',
|
|
||||||
'session-id': '36f17fbe-1d44-11e6-a1a9-bbe8a5a41617'
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = JSON.stringify({tx: tx})
|
|
||||||
got('http://localhost:3000/dispense', {body: body, json: true, headers: headers})
|
|
||||||
.then(res => {
|
|
||||||
console.log(res.body)
|
|
||||||
})
|
|
||||||
.catch(console.log)
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
var db = require('../lib/postgresql_interface')
|
|
||||||
var connectionString = 'postgres://lamassu:lamassu@localhost/lamassu'
|
|
||||||
db.init(connectionString)
|
|
||||||
|
|
||||||
var session = {
|
|
||||||
id: '6ede611c-cd03-11e5-88ee-2b5fcfdb0bc2',
|
|
||||||
fingerprint: 'xx:xx'
|
|
||||||
}
|
|
||||||
var tx = {
|
|
||||||
fiat: 40,
|
|
||||||
satoshis: 6980000,
|
|
||||||
toAddress: '1xxx',
|
|
||||||
currencyCode: 'CAD',
|
|
||||||
incoming: false
|
|
||||||
}
|
|
||||||
|
|
||||||
var tx2 = {
|
|
||||||
fiat: 0,
|
|
||||||
satoshis: 6980000,
|
|
||||||
toAddress: '1xxx',
|
|
||||||
currencyCode: 'CAD',
|
|
||||||
incoming: false
|
|
||||||
}
|
|
||||||
|
|
||||||
db.addOutgoingTx(session, tx, function (err, res) {
|
|
||||||
console.log('DEBUG1')
|
|
||||||
console.log(err)
|
|
||||||
console.log(res)
|
|
||||||
})
|
|
||||||
|
|
||||||
db.addOutgoingTx(session, tx2, function (err, res) {
|
|
||||||
console.log('DEBUG2')
|
|
||||||
console.log(err)
|
|
||||||
console.log(res)
|
|
||||||
})
|
|
||||||
|
|
||||||
/*
|
|
||||||
setTimeout(function () {
|
|
||||||
db.addOutgoingTx(session, tx2, function (err, res) {
|
|
||||||
console.log('DEBUG2')
|
|
||||||
console.log(err)
|
|
||||||
console.log(res)
|
|
||||||
})
|
|
||||||
}, 0)
|
|
||||||
*/
|
|
||||||
|
|
||||||
var bills = {
|
|
||||||
uuid: 'c630338c-cd03-11e5-a9df-dbc9be2e9fbb',
|
|
||||||
currency: 'CAD',
|
|
||||||
toAddress: '1xxx',
|
|
||||||
deviceTime: Date.now(),
|
|
||||||
satoshis: 6980000,
|
|
||||||
fiat: 40
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
db.recordBill(session, bills, function (err) {
|
|
||||||
console.log(err)
|
|
||||||
})
|
|
||||||
*/
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
const complianceTriggers = require('../lib/compliance-triggers')
|
|
||||||
const settingsLoader = require('../lib/new-settings-loader')
|
|
||||||
const configManager = require('../lib/new-config-manager')
|
|
||||||
|
|
||||||
settingsLoader.loadLatest().then(settings => {
|
|
||||||
const triggers = configManager.getTriggers(settings.config)
|
|
||||||
const response = complianceTriggers.getBackwardsCompatibleTriggers(triggers)
|
|
||||||
console.log(response)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
const rpc = require('../lib/plugins/common/json-rpc')
|
|
||||||
|
|
||||||
const method = ''
|
|
||||||
|
|
||||||
// const url = null
|
|
||||||
// const url = 'https://httpstat.us/500'
|
|
||||||
// const url = 'https://httpstat.us/400'
|
|
||||||
const url = 'https://httpstat.us/200'
|
|
||||||
|
|
||||||
const account = {
|
|
||||||
username: 'test',
|
|
||||||
password: 'test',
|
|
||||||
port: 8080,
|
|
||||||
url
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc.fetch(account, method)
|
|
||||||
.then(res => console.log('got result', res))
|
|
||||||
.catch(err => console.error('gor error', err))
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
require('es6-promise').polyfill()
|
|
||||||
|
|
||||||
var notifier = require('../lib/notifier')
|
|
||||||
var db = require('../lib/postgresql_interface')
|
|
||||||
const { PSQL_URL } = require('../lib/constants')
|
|
||||||
|
|
||||||
function getBalances () {
|
|
||||||
return [
|
|
||||||
{fiatBalance: 23.2345, fiatCode: 'USD', cryptoCode: 'BTC'},
|
|
||||||
{fiatBalance: 23, fiatCode: 'USD', cryptoCode: 'ETH'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
db.init(PSQL_URL)
|
|
||||||
notifier.init(db, getBalances, {lowBalanceThreshold: 10})
|
|
||||||
console.log('DEBUG0')
|
|
||||||
notifier.checkStatus()
|
|
||||||
.then(function (alertRec) {
|
|
||||||
console.log('DEBUG1')
|
|
||||||
console.log('%j', alertRec)
|
|
||||||
var subject = notifier.alertSubject(alertRec)
|
|
||||||
console.log(subject)
|
|
||||||
var body = notifier.printEmailAlerts(alertRec)
|
|
||||||
console.log(body)
|
|
||||||
console.log(notifier.alertFingerprint(alertRec))
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
console.log(err.stack)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
const compliance = require('../lib/compliance')
|
|
||||||
const ofac = require('../lib/ofac/index')
|
|
||||||
|
|
||||||
const [customerId, firstName, lastName, dateOfBirth] = process.argv.slice(2)
|
|
||||||
|
|
||||||
const customer = {
|
|
||||||
id: customerId,
|
|
||||||
idCardData: {firstName, lastName, dateOfBirth}
|
|
||||||
}
|
|
||||||
|
|
||||||
const deviceId = 'test-device'
|
|
||||||
|
|
||||||
ofac.load()
|
|
||||||
.then(() => compliance.validationPatch(deviceId, true, customer))
|
|
||||||
.then(console.log)
|
|
||||||
.catch(err => console.log(err))
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
const plugins = require('../lib/plugins')
|
|
||||||
const settingsLoader = require('../lib/new-settings-loader')
|
|
||||||
const pp = require('../lib/pp')
|
|
||||||
|
|
||||||
settingsLoader.loadLatest()
|
|
||||||
.then(settings => {
|
|
||||||
console.log('DEBUG300')
|
|
||||||
const pi = plugins(settings)
|
|
||||||
pi.getRates().then(r => console.log(JSON.stringify(r)))
|
|
||||||
})
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const os = require('os')
|
|
||||||
const bip39 = require('bip39')
|
|
||||||
|
|
||||||
require('../lib/environment-helper')
|
|
||||||
const setEnvVariable = require('../tools/set-env-var')
|
|
||||||
|
|
||||||
if (process.env.MNEMONIC_PATH && !process.env.SEED_PATH) {
|
|
||||||
const mnemonic = fs.readFileSync(process.env.MNEMONIC_PATH, 'utf8')
|
|
||||||
const seed = bip39.mnemonicToEntropy(mnemonic.split('\n').join(' ').trim()).toString('hex')
|
|
||||||
|
|
||||||
setEnvVariable('SEED_PATH', path.resolve(os.homedir(), '.lamassu', 'seeds', 'seed.txt'))
|
|
||||||
|
|
||||||
if (!fs.existsSync(path.dirname(process.env.SEED_PATH))) {
|
|
||||||
fs.mkdirSync(path.dirname(process.env.SEED_PATH))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(process.env.SEED_PATH)) {
|
|
||||||
fs.writeFileSync(process.env.SEED_PATH, seed, 'utf8')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
require('es6-promise').polyfill()
|
|
||||||
|
|
||||||
var config = require('../lib/new-settings-loader')
|
|
||||||
var sms = require('../lib/sms')
|
|
||||||
|
|
||||||
var rand = Math.floor(Math.random() * 1e6)
|
|
||||||
var db = config.connection
|
|
||||||
var rec = {
|
|
||||||
email: {
|
|
||||||
subject: 'Test email ' + rand,
|
|
||||||
body: 'This is a test email from lamassu-server'
|
|
||||||
},
|
|
||||||
sms: {
|
|
||||||
toNumber: '666',
|
|
||||||
body: '[Lamassu] This is a test sms ' + rand
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
config.loadLatest(db)
|
|
||||||
.then(function (config) {
|
|
||||||
sms.sendMessage(config, rec)
|
|
||||||
.then(function () {
|
|
||||||
console.log('Success.')
|
|
||||||
process.exit(0)
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
console.log(err.stack)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
const strike = require('../lib/plugins/wallet/strike/strike')
|
|
||||||
const BN = require('../lib/bn')
|
|
||||||
|
|
||||||
const account = {token: 'xxx'}
|
|
||||||
|
|
||||||
strike.newAddress(account, { cryptoCode: 'BTC', cryptoAtoms: new BN(10000) })
|
|
||||||
.then(r => {
|
|
||||||
console.log(r)
|
|
||||||
|
|
||||||
const toAddress = r
|
|
||||||
const requested = null
|
|
||||||
const cryptoCode = 'BTC'
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
strike.getStatus(account, toAddress, requested, cryptoCode)
|
|
||||||
.then(console.log)
|
|
||||||
.catch(r => console.log(r.message))
|
|
||||||
}, 2000)
|
|
||||||
})
|
|
||||||
.catch(console.log)
|
|
||||||
|
|
@ -125,19 +125,4 @@ services:
|
||||||
- LOG_LEVEL=info
|
- LOG_LEVEL=info
|
||||||
depends_on:
|
depends_on:
|
||||||
lamassu-server:
|
lamassu-server:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
|
||||||
# admin:
|
|
||||||
# container_name: lamassu-admin
|
|
||||||
# build:
|
|
||||||
# context: .
|
|
||||||
# dockerfile: Dockerfiles/admin.Dockerfile
|
|
||||||
# target: production-admin
|
|
||||||
# restart: always
|
|
||||||
# ports:
|
|
||||||
# - 80:80
|
|
||||||
# networks:
|
|
||||||
# - lamassu-network
|
|
||||||
# depends_on:
|
|
||||||
# lamassu-admin-server:
|
|
||||||
# condition: service_started
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
lamassu-remote-install
|
|
||||||
===============
|
|
||||||
|
|
||||||
This will install your Lamassu Bitcoin Machine remote server.
|
|
||||||
|
|
||||||
Instructions
|
|
||||||
------------
|
|
||||||
|
|
||||||
1. Start a new Digital Ocean droplet
|
|
||||||
|
|
||||||
2. ssh into the droplet
|
|
||||||
|
|
||||||
```
|
|
||||||
ssh root@<your-new-ip-address>
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Run the following command once you're logged in (default branch name is master):
|
|
||||||
|
|
||||||
```
|
|
||||||
curl -sS https://raw.githubusercontent.com/lamassu/lamassu-server/master/lamassu-remote-install/install | bash -s -- <branch-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
4. You should be set. Just follow the instructions on the screen to open your dashboard.
|
|
||||||
72
lamassu-remote-install/Vagrantfile
vendored
72
lamassu-remote-install/Vagrantfile
vendored
|
|
@ -1,72 +0,0 @@
|
||||||
# -*- mode: ruby -*-
|
|
||||||
# vi: set ft=ruby :
|
|
||||||
|
|
||||||
# All Vagrant configuration is done below. The "2" in Vagrant.configure
|
|
||||||
# configures the configuration version (we support older styles for
|
|
||||||
# backwards compatibility). Please don't change it unless you know what
|
|
||||||
# you're doing.
|
|
||||||
Vagrant.configure(2) do |config|
|
|
||||||
# The most common configuration options are documented and commented below.
|
|
||||||
# For a complete reference, please see the online documentation at
|
|
||||||
# https://docs.vagrantup.com.
|
|
||||||
|
|
||||||
# Every Vagrant development environment requires a box. You can search for
|
|
||||||
# boxes at https://atlas.hashicorp.com/search.
|
|
||||||
config.vm.box = "ubuntu/xenial64"
|
|
||||||
|
|
||||||
# Disable automatic box update checking. If you disable this, then
|
|
||||||
# boxes will only be checked for updates when the user runs
|
|
||||||
# `vagrant box outdated`. This is not recommended.
|
|
||||||
# config.vm.box_check_update = false
|
|
||||||
|
|
||||||
# Create a forwarded port mapping which allows access to a specific port
|
|
||||||
# within the machine from a port on the host machine. In the example below,
|
|
||||||
# accessing "localhost:8080" will access port 80 on the guest machine.
|
|
||||||
config.vm.network "forwarded_port", guest: 8081, host: 8091
|
|
||||||
|
|
||||||
# Create a private network, which allows host-only access to the machine
|
|
||||||
# using a specific IP.
|
|
||||||
# config.vm.network "private_network", ip: "192.168.33.10"
|
|
||||||
|
|
||||||
# Create a public network, which generally matched to bridged network.
|
|
||||||
# Bridged networks make the machine appear as another physical device on
|
|
||||||
# your network.
|
|
||||||
# config.vm.network "public_network"
|
|
||||||
|
|
||||||
# Share an additional folder to the guest VM. The first argument is
|
|
||||||
# the path on the host to the actual folder. The second argument is
|
|
||||||
# the path on the guest to mount the folder. And the optional third
|
|
||||||
# argument is a set of non-required options.
|
|
||||||
config.vm.synced_folder ".", "/vagrant"
|
|
||||||
config.vm.synced_folder "../lamassu-scripts", "/lamassu-scripts"
|
|
||||||
|
|
||||||
# Provider-specific configuration so you can fine-tune various
|
|
||||||
# backing providers for Vagrant. These expose provider-specific options.
|
|
||||||
# Example for VirtualBox:
|
|
||||||
#
|
|
||||||
config.vm.provider "virtualbox" do |vb|
|
|
||||||
# # Display the VirtualBox GUI when booting the machine
|
|
||||||
# vb.gui = true
|
|
||||||
#
|
|
||||||
# # Customize the amount of memory on the VM:
|
|
||||||
vb.memory = "2048"
|
|
||||||
end
|
|
||||||
#
|
|
||||||
# View the documentation for the provider you are using for more
|
|
||||||
# information on available options.
|
|
||||||
|
|
||||||
# Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
|
|
||||||
# such as FTP and Heroku are also available. See the documentation at
|
|
||||||
# https://docs.vagrantup.com/v2/push/atlas.html for more information.
|
|
||||||
# config.push.define "atlas" do |push|
|
|
||||||
# push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Enable provisioning with a shell script. Additional provisioners such as
|
|
||||||
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
|
|
||||||
# documentation for more information about their specific syntax and use.
|
|
||||||
# config.vm.provision "shell", inline: <<-SHELL
|
|
||||||
# sudo apt-get update
|
|
||||||
# sudo apt-get install -y apache2
|
|
||||||
# SHELL
|
|
||||||
end
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
COPY user_config (id, type, data) FROM stdin;
|
|
||||||
1 exchanges {"exchanges" : {\
|
|
||||||
"settings": {\
|
|
||||||
"commission": 1.0,\
|
|
||||||
"compliance": {\
|
|
||||||
"maximum": {\
|
|
||||||
"limit": null\
|
|
||||||
}\
|
|
||||||
}\
|
|
||||||
},\
|
|
||||||
"plugins" : {\
|
|
||||||
"current": {\
|
|
||||||
"ticker": "bitpay",\
|
|
||||||
"transfer": "bitgo"\
|
|
||||||
},\
|
|
||||||
"settings": {\
|
|
||||||
"bitpay": {},\
|
|
||||||
"bitgo" : {}\
|
|
||||||
}\
|
|
||||||
}\
|
|
||||||
}\
|
|
||||||
}
|
|
||||||
\.
|
|
||||||
|
|
||||||
COPY user_config (id, type, data) FROM stdin;
|
|
||||||
2 unit { "brain": {\
|
|
||||||
"unit": {\
|
|
||||||
"ssn": "xx-1234-45",\
|
|
||||||
"owner": "Unlisted"\
|
|
||||||
},\
|
|
||||||
"locale": {\
|
|
||||||
"currency": "USD",\
|
|
||||||
"localeInfo": {\
|
|
||||||
"primaryLocale": "en-US",\
|
|
||||||
"primaryLocales": ["en-US"]\
|
|
||||||
}\
|
|
||||||
}\
|
|
||||||
}\
|
|
||||||
}
|
|
||||||
\.
|
|
||||||
|
|
@ -1,280 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
export LOG_FILE=/tmp/install.log
|
|
||||||
|
|
||||||
CERT_DIR=/etc/ssl/certs
|
|
||||||
KEY_DIR=/etc/ssl/private
|
|
||||||
CONFIG_DIR=/etc/lamassu
|
|
||||||
MIGRATE_STATE_PATH=$CONFIG_DIR/.migrate
|
|
||||||
LAMASSU_CA_PATH=$CERT_DIR/Lamassu_CA.pem
|
|
||||||
CA_KEY_PATH=$KEY_DIR/Lamassu_OP_Root_CA.key
|
|
||||||
CA_PATH=$CERT_DIR/Lamassu_OP_Root_CA.pem
|
|
||||||
SERVER_KEY_PATH=$KEY_DIR/Lamassu_OP.key
|
|
||||||
SERVER_CERT_PATH=$CERT_DIR/Lamassu_OP.pem
|
|
||||||
MNEMONIC_DIR=$CONFIG_DIR/mnemonics
|
|
||||||
MNEMONIC_FILE=$MNEMONIC_DIR/mnemonic.txt
|
|
||||||
BACKUP_DIR=/var/backups/postgresql
|
|
||||||
BLOCKCHAIN_DIR=/mnt/blockchains
|
|
||||||
OFAC_DATA_DIR=/var/lamassu/ofac
|
|
||||||
ID_PHOTO_CARD_DIR=/opt/lamassu-server/idphotocard
|
|
||||||
FRONTCAMERA_DIR=/opt/lamassu-server/frontcamera
|
|
||||||
OPERATOR_DIR=/opt/lamassu-server/operatordata
|
|
||||||
|
|
||||||
# Look into http://unix.stackexchange.com/questions/140734/configure-localtime-dpkg-reconfigure-tzdata
|
|
||||||
|
|
||||||
decho () {
|
|
||||||
echo `date +"%H:%M:%S"` $1
|
|
||||||
echo `date +"%H:%M:%S"` $1 >> $LOG_FILE
|
|
||||||
}
|
|
||||||
|
|
||||||
retry() {
|
|
||||||
local -r -i max_attempts="$1"; shift
|
|
||||||
local -r cmd="$@"
|
|
||||||
local -i attempt_num=1
|
|
||||||
|
|
||||||
until $cmd
|
|
||||||
do
|
|
||||||
if (( attempt_num == max_attempts ))
|
|
||||||
then
|
|
||||||
echo
|
|
||||||
echo "****************************************************************"
|
|
||||||
echo "Attempt $attempt_num failed and there are no more attempts left! ($cmd)"
|
|
||||||
return 1
|
|
||||||
else
|
|
||||||
echo
|
|
||||||
echo "****************************************************************"
|
|
||||||
echo "Attempt $attempt_num failed! Trying again in $attempt_num seconds..."
|
|
||||||
sleep $(( attempt_num++ ))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
rm -f $LOG_FILE
|
|
||||||
|
|
||||||
cat <<'FIG'
|
|
||||||
_
|
|
||||||
| | __ _ _ __ ___ __ _ ___ ___ _ _ ___ ___ _ ____ _____ _ __
|
|
||||||
| |/ _` | '_ ` _ \ / _` / __/ __| | | |_____/ __|/ _ \ '__\ \ / / _ \ '__|
|
|
||||||
| | (_| | | | | | | (_| \__ \__ \ |_| |_____\__ \ __/ | \ V / __/ |
|
|
||||||
|_|\__,_|_| |_| |_|\__,_|___/___/\__,_| |___/\___|_| \_/ \___|_|
|
|
||||||
FIG
|
|
||||||
|
|
||||||
echo -e "\nStarting \033[1mlamassu-server\033[0m install. This will take a few minutes...\n"
|
|
||||||
|
|
||||||
if [ "$(whoami)" != "root" ]; then
|
|
||||||
echo -e "This script has to be run as \033[1mroot\033[0m user"
|
|
||||||
exit 3
|
|
||||||
fi
|
|
||||||
|
|
||||||
release=$(lsb_release -rs)
|
|
||||||
processor=$(uname -i)
|
|
||||||
if [ "$release" != "16.04" ] || [ "$processor" != "x86_64" ]; then
|
|
||||||
echo "You're attempting to install on an unsupported Linux distribution or release ("$release $processor")."
|
|
||||||
echo
|
|
||||||
uname -a
|
|
||||||
echo
|
|
||||||
echo "Please return to DigitalOcean and create a droplet running Ubuntu 16.04 x64 instead."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# So we don't run out of memory
|
|
||||||
decho "Enabling swap file for install only..."
|
|
||||||
fallocate -l 1G /swapfile >> $LOG_FILE 2>&1
|
|
||||||
chmod 600 /swapfile >> $LOG_FILE 2>&1
|
|
||||||
mkswap /swapfile >> $LOG_FILE 2>&1
|
|
||||||
swapon /swapfile >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
IP=$(ifconfig eth0 | grep "inet" | grep -v "inet6" | awk -F: '{print $2}' | awk '{print $1}')
|
|
||||||
|
|
||||||
decho "Updating system..."
|
|
||||||
sleep 10
|
|
||||||
curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - >> $LOG_FILE 2>&1
|
|
||||||
apt update >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
decho "Installing necessary packages..."
|
|
||||||
apt install nodejs python-minimal build-essential supervisor postgresql libpq-dev -y -q >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
decho "Installing latest npm package manager for node..."
|
|
||||||
retry 3 npm -g --unsafe-perm install npm@5 >> $LOG_FILE 2>&1
|
|
||||||
NODE_MODULES=$(npm -g root)
|
|
||||||
NPM_BIN=$(npm -g bin)
|
|
||||||
|
|
||||||
decho "Installing lamassu-server..."
|
|
||||||
retry 3 npm -g --unsafe-perm install lamassu/lamassu-server#${1-master} >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
decho "Generating mnemonic..."
|
|
||||||
mkdir -p $MNEMONIC_DIR >> $LOG_FILE 2>&1
|
|
||||||
SEED=$(openssl rand -hex 32)
|
|
||||||
MNEMONIC=$(bip39 $SEED)
|
|
||||||
echo "$MNEMONIC" > $MNEMONIC_FILE
|
|
||||||
|
|
||||||
decho "Creating postgres user..."
|
|
||||||
POSTGRES_PW=$(hkdf postgres-pw $SEED)
|
|
||||||
su -l postgres >> $LOG_FILE 2>&1 <<EOF
|
|
||||||
psql -c "CREATE ROLE lamassu_pg WITH LOGIN SUPERUSER PASSWORD '$POSTGRES_PW';"
|
|
||||||
createdb lamassu
|
|
||||||
EOF
|
|
||||||
|
|
||||||
mkdir -p $CERT_DIR >> $LOG_FILE 2>&1
|
|
||||||
mkdir -p $CONFIG_DIR >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
decho "Generating SSL certificates..."
|
|
||||||
|
|
||||||
openssl genrsa \
|
|
||||||
-out $CA_KEY_PATH \
|
|
||||||
4096 >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
openssl req \
|
|
||||||
-x509 \
|
|
||||||
-sha256 \
|
|
||||||
-new \
|
|
||||||
-nodes \
|
|
||||||
-key $CA_KEY_PATH \
|
|
||||||
-days 3650 \
|
|
||||||
-out $CA_PATH \
|
|
||||||
-subj "/C=IS/ST=/L=Reykjavik/O=Lamassu Operator CA/CN=lamassu-operator.is" \
|
|
||||||
>> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
openssl genrsa \
|
|
||||||
-out $SERVER_KEY_PATH \
|
|
||||||
4096 >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
openssl req -new \
|
|
||||||
-key $SERVER_KEY_PATH \
|
|
||||||
-out /tmp/Lamassu_OP.csr.pem \
|
|
||||||
-subj "/C=IS/ST=/L=Reykjavik/O=Lamassu Operator/CN=$IP" \
|
|
||||||
-reqexts SAN \
|
|
||||||
-sha256 \
|
|
||||||
-config <(cat /etc/ssl/openssl.cnf \
|
|
||||||
<(printf "[SAN]\nsubjectAltName=IP.1:$IP")) \
|
|
||||||
>> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
openssl x509 \
|
|
||||||
-req -in /tmp/Lamassu_OP.csr.pem \
|
|
||||||
-CA $CA_PATH \
|
|
||||||
-CAkey $CA_KEY_PATH \
|
|
||||||
-CAcreateserial \
|
|
||||||
-out $SERVER_CERT_PATH \
|
|
||||||
-extfile <(cat /etc/ssl/openssl.cnf \
|
|
||||||
<(printf "[SAN]\nsubjectAltName=IP.1:$IP")) \
|
|
||||||
-extensions SAN \
|
|
||||||
-days 3650 >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
rm /tmp/Lamassu_OP.csr.pem
|
|
||||||
|
|
||||||
decho "Copying Lamassu certificate authority..."
|
|
||||||
LAMASSU_CA_FILE=$NODE_MODULES/lamassu-server/Lamassu_CA.pem
|
|
||||||
cp $LAMASSU_CA_FILE $LAMASSU_CA_PATH
|
|
||||||
|
|
||||||
mkdir -p $OFAC_DATA_DIR
|
|
||||||
|
|
||||||
cat <<EOF > $CONFIG_DIR/lamassu.json
|
|
||||||
{
|
|
||||||
"postgresql": "postgres://lamassu_pg:$POSTGRES_PW@localhost/lamassu",
|
|
||||||
"mnemonicPath": "$MNEMONIC_FILE",
|
|
||||||
"lamassuCaPath": "$LAMASSU_CA_PATH",
|
|
||||||
"caPath": "$CA_PATH",
|
|
||||||
"certPath": "$SERVER_CERT_PATH",
|
|
||||||
"keyPath": "$SERVER_KEY_PATH",
|
|
||||||
"hostname": "$IP",
|
|
||||||
"logLevel": "info",
|
|
||||||
"migrateStatePath": "$MIGRATE_STATE_PATH",
|
|
||||||
"blockchainDir": "$BLOCKCHAIN_DIR",
|
|
||||||
"ofacDataDir": "$OFAC_DATA_DIR",
|
|
||||||
"idPhotoCardDir": "$ID_PHOTO_CARD_DIR",
|
|
||||||
"frontCameraDir": "$FRONTCAMERA_DIR",
|
|
||||||
"operatorDataDir": "$OPERATOR_DIR"
|
|
||||||
"strike": {
|
|
||||||
"baseUrl": "https://api.strike.acinq.co/api/"
|
|
||||||
},
|
|
||||||
"coinAtmRadar": {
|
|
||||||
"url": "https://coinatmradar.info/api/lamassu/"
|
|
||||||
},
|
|
||||||
"ofacSources": [
|
|
||||||
{
|
|
||||||
"name": "sdn_advanced",
|
|
||||||
"url": "https://www.treasury.gov/ofac/downloads/sanctions/1.0/sdn_advanced.xml"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "cons_advanced",
|
|
||||||
"url": "https://www.treasury.gov/ofac/downloads/sanctions/1.0/cons_advanced.xml"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
decho "Setting up database tables..."
|
|
||||||
lamassu-migrate >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
decho "Setting up lamassu-admin..."
|
|
||||||
ADMIN_REGISTRATION_URL=`lamassu-register admin 2>> $LOG_FILE`
|
|
||||||
|
|
||||||
decho "Setting up backups..."
|
|
||||||
BIN=$(npm -g bin)
|
|
||||||
BACKUP_CMD=$BIN/lamassu-backup-pg
|
|
||||||
mkdir -p $BACKUP_DIR
|
|
||||||
BACKUP_CRON="@daily $BACKUP_CMD > /dev/null"
|
|
||||||
(crontab -l 2>/dev/null || echo -n ""; echo "$BACKUP_CRON") | crontab - >> $LOG_FILE 2>&1
|
|
||||||
$BACKUP_CMD >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
decho "Setting up firewall..."
|
|
||||||
ufw allow ssh >> $LOG_FILE 2>&1
|
|
||||||
ufw allow 443/tcp >> $LOG_FILE 2>&1 # Admin
|
|
||||||
ufw allow 3000/tcp >> $LOG_FILE 2>&1 # Server
|
|
||||||
ufw allow 8071/tcp >> $LOG_FILE 2>&1 # Lamassu support
|
|
||||||
ufw -f enable >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
decho "Setting up supervisor..."
|
|
||||||
cat <<EOF > /etc/supervisor/conf.d/lamassu-server.conf
|
|
||||||
[program:lamassu-server]
|
|
||||||
command=${NPM_BIN}/lamassu-server
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/var/log/supervisor/lamassu-server.err.log
|
|
||||||
stdout_logfile=/var/log/supervisor/lamassu-server.out.log
|
|
||||||
environment=HOME="/root"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat <<EOF > /etc/supervisor/conf.d/lamassu-admin-server.conf
|
|
||||||
[program:lamassu-admin-server]
|
|
||||||
command=${NPM_BIN}/lamassu-admin-server
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
stderr_logfile=/var/log/supervisor/lamassu-admin-server.err.log
|
|
||||||
stdout_logfile=/var/log/supervisor/lamassu-admin-server.out.log
|
|
||||||
environment=HOME="/root"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
service supervisor restart >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
decho "Disabling swap file..."
|
|
||||||
swapoff /swapfile >> $LOG_FILE 2>&1
|
|
||||||
|
|
||||||
# disable exitting on error in case DO changes motd scripts
|
|
||||||
set +e
|
|
||||||
chmod -x /etc/update-motd.d/*-release-upgrade
|
|
||||||
chmod -x /etc/update-motd.d/*-updates-available
|
|
||||||
chmod -x /etc/update-motd.d/*-reboot-required
|
|
||||||
chmod -x /etc/update-motd.d/*-help-text
|
|
||||||
chmod -x /etc/update-motd.d/*-cloudguest
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# reset terminal to link new executables
|
|
||||||
hash -r
|
|
||||||
|
|
||||||
echo
|
|
||||||
decho "Done! Now it's time to configure Lamassu stack."
|
|
||||||
echo
|
|
||||||
echo -e "\n*** IMPORTANT ***"
|
|
||||||
echo "In a private space, run lamassu-mnemonic, write down the words"
|
|
||||||
echo "and keep them in a safe place."
|
|
||||||
echo
|
|
||||||
echo "This secret will allow you to retrieve system passwords, such "
|
|
||||||
echo "as the keys to your Ethereum account. However, you must still "
|
|
||||||
echo "backup your wallets separately. Visit support.lamassu.is for "
|
|
||||||
echo "details on regularly backing up your wallets and coins."
|
|
||||||
echo
|
|
||||||
echo
|
|
||||||
echo "Activation URL for lamassu-admin:"
|
|
||||||
echo $ADMIN_REGISTRATION_URL
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
const path = require('path')
|
|
||||||
const fs = require('fs')
|
|
||||||
|
|
||||||
const _ = require('lodash/fp')
|
|
||||||
|
|
||||||
const db = require('../db')
|
|
||||||
const configValidate = require('./config-validate')
|
|
||||||
const config = require('./config')
|
|
||||||
|
|
||||||
function loadSchemas () {
|
|
||||||
const schemasRoot = path.resolve(__dirname, 'schemas')
|
|
||||||
const schemaFiles = fs.readdirSync(schemasRoot)
|
|
||||||
const stripJson = fileName => fileName.slice(0, -5)
|
|
||||||
const readSchema = fileName => JSON.parse(fs.readFileSync(path.resolve(schemasRoot, fileName)))
|
|
||||||
return _.zipObject(_.map(stripJson, schemaFiles), _.map(readSchema, schemaFiles))
|
|
||||||
}
|
|
||||||
|
|
||||||
const schemas = loadSchemas()
|
|
||||||
|
|
||||||
function fetchAccounts () {
|
|
||||||
return db.oneOrNone('select data from user_config where type=$1 and schema_version=$2', ['accounts', configValidate.SETTINGS_LOADER_SCHEMA_VERSION])
|
|
||||||
.then(row => {
|
|
||||||
// Hard code this for now
|
|
||||||
const accounts = [{
|
|
||||||
code: 'blockcypher',
|
|
||||||
display: 'Blockcypher',
|
|
||||||
fields: [
|
|
||||||
{ code: 'confidenceFactor', display: 'Confidence Factor', fieldType: 'integer', required: true, value: 40 }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
|
|
||||||
return row
|
|
||||||
? Promise.resolve(row.data.accounts)
|
|
||||||
: db.none('insert into user_config (type, data, valid) values ($1, $2, $3)', ['accounts', {accounts}, true])
|
|
||||||
.then(fetchAccounts)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectedAccounts () {
|
|
||||||
const mapAccount = v => v.fieldLocator.fieldType === 'account' &&
|
|
||||||
v.fieldValue.value
|
|
||||||
|
|
||||||
const mapSchema = code => schemas[code]
|
|
||||||
return config.fetchConfig()
|
|
||||||
.then(conf => {
|
|
||||||
const accountCodes = _.uniq(conf.map(mapAccount)
|
|
||||||
.filter(_.identity))
|
|
||||||
|
|
||||||
return _.sortBy(_.get('display'), accountCodes.map(mapSchema)
|
|
||||||
.filter(_.identity))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchAccountSchema (account) {
|
|
||||||
return schemas[account]
|
|
||||||
}
|
|
||||||
|
|
||||||
function mergeAccount (oldAccount, newAccount) {
|
|
||||||
if (!newAccount) return oldAccount
|
|
||||||
|
|
||||||
const newFields = newAccount.fields
|
|
||||||
|
|
||||||
const updateWithData = oldField => {
|
|
||||||
const newField = _.find(r => r.code === oldField.code, newFields)
|
|
||||||
const newValue = _.isUndefined(newField) ? oldField.value : newField.value
|
|
||||||
return _.set('value', newValue, oldField)
|
|
||||||
}
|
|
||||||
|
|
||||||
const updatedFields = oldAccount.fields.map(updateWithData)
|
|
||||||
|
|
||||||
return _.set('fields', updatedFields, oldAccount)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAccounts (accountCode) {
|
|
||||||
const schema = fetchAccountSchema(accountCode)
|
|
||||||
if (!schema) return Promise.reject(new Error('No schema for: ' + accountCode))
|
|
||||||
|
|
||||||
return fetchAccounts()
|
|
||||||
.then(accounts => {
|
|
||||||
if (_.isEmpty(accounts)) return [schema]
|
|
||||||
const account = _.find(r => r.code === accountCode, accounts)
|
|
||||||
const mergedAccount = mergeAccount(schema, account)
|
|
||||||
|
|
||||||
return updateAccounts(mergedAccount, accounts)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function elideSecrets (account) {
|
|
||||||
const elideSecret = field => {
|
|
||||||
return field.fieldType === 'password'
|
|
||||||
? _.set('value', !_.isEmpty(field.value), field)
|
|
||||||
: field
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.set('fields', account.fields.map(elideSecret), account)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getAccount (accountCode) {
|
|
||||||
return getAccounts(accountCode)
|
|
||||||
.then(accounts => _.find(r => r.code === accountCode, accounts))
|
|
||||||
.then(elideSecrets)
|
|
||||||
}
|
|
||||||
|
|
||||||
function save (accounts) {
|
|
||||||
return db.none('update user_config set data=$1 where type=$2 and schema_version=$3', [{accounts: accounts}, 'accounts', configValidate.SETTINGS_LOADER_SCHEMA_VERSION])
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateAccounts (newAccount, accounts) {
|
|
||||||
const accountCode = newAccount.code
|
|
||||||
const isPresent = _.some(_.matchesProperty('code', accountCode), accounts)
|
|
||||||
const updateAccount = r => r.code === accountCode
|
|
||||||
? newAccount
|
|
||||||
: r
|
|
||||||
|
|
||||||
return isPresent
|
|
||||||
? _.map(updateAccount, accounts)
|
|
||||||
: _.concat(accounts, newAccount)
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateAccount (account) {
|
|
||||||
return getAccounts(account.code)
|
|
||||||
.then(accounts => {
|
|
||||||
const merged = mergeAccount(_.find(_.matchesProperty('code', account.code), accounts), account)
|
|
||||||
return save(updateAccounts(merged, accounts))
|
|
||||||
})
|
|
||||||
.then(() => getAccount(account.code))
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
selectedAccounts,
|
|
||||||
getAccount,
|
|
||||||
updateAccount
|
|
||||||
}
|
|
||||||
|
|
@ -1,342 +0,0 @@
|
||||||
const EventEmitter = require('events')
|
|
||||||
const qs = require('querystring')
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const express = require('express')
|
|
||||||
const app = express()
|
|
||||||
const https = require('https')
|
|
||||||
const serveStatic = require('serve-static')
|
|
||||||
const cookieParser = require('cookie-parser')
|
|
||||||
const argv = require('minimist')(process.argv.slice(2))
|
|
||||||
const got = require('got')
|
|
||||||
const morgan = require('morgan')
|
|
||||||
const helmet = require('helmet')
|
|
||||||
// const WebSocket = require('ws')
|
|
||||||
const http = require('http')
|
|
||||||
// const SocketIo = require('socket.io')
|
|
||||||
const makeDir = require('make-dir')
|
|
||||||
const _ = require('lodash/fp')
|
|
||||||
|
|
||||||
const machineLoader = require('../machine-loader')
|
|
||||||
const T = require('../time')
|
|
||||||
const logger = require('../logger')
|
|
||||||
|
|
||||||
const accounts = require('./accounts')
|
|
||||||
const config = require('./config')
|
|
||||||
const login = require('./login')
|
|
||||||
const pairing = require('./pairing')
|
|
||||||
const server = require('./server')
|
|
||||||
const transactions = require('./transactions')
|
|
||||||
const customers = require('../customers')
|
|
||||||
const logs = require('../logs')
|
|
||||||
const funding = require('./funding')
|
|
||||||
const supportServer = require('./admin-support')
|
|
||||||
|
|
||||||
const NEVER = new Date(Date.now() + 100 * T.years)
|
|
||||||
const REAUTHENTICATE_INTERVAL = T.minute
|
|
||||||
|
|
||||||
const HOSTNAME = process.env.HOSTNAME
|
|
||||||
const KEY_PATH = process.env.KEY_PATH
|
|
||||||
const CERT_PATH = process.env.CERT_PATH
|
|
||||||
const ID_PHOTO_CARD_DIR = process.env.ID_PHOTO_CARD_DIR
|
|
||||||
const FRONT_CAMERA_DIR = process.env.FRONT_CAMERA_DIR
|
|
||||||
const OPERATOR_DATA_DIR = process.env.OPERATOR_DATA_DIR
|
|
||||||
|
|
||||||
const devMode = argv.dev
|
|
||||||
|
|
||||||
const version = require('../../package.json').version
|
|
||||||
logger.info('Version: %s', version)
|
|
||||||
|
|
||||||
if (!HOSTNAME) {
|
|
||||||
logger.error('no hostname specified.')
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {run}
|
|
||||||
|
|
||||||
function dbNotify () {
|
|
||||||
return got.post('http://localhost:3030/dbChange')
|
|
||||||
.catch(e => logger.error('lamassu-server not responding'))
|
|
||||||
}
|
|
||||||
|
|
||||||
const skip = (req, res) => req.path === '/api/status/' && res.statusCode === 200
|
|
||||||
|
|
||||||
// Note: no rate limiting applied since that would allow an attacker to
|
|
||||||
// easily DDoS by just hitting the aggregate rate limit. We assume the
|
|
||||||
// attacker has unlimited unique IP addresses.
|
|
||||||
//
|
|
||||||
// The best we can do at the application level is to make the authentication
|
|
||||||
// lookup very fast. There will only be a few users at most, so it's not a problem
|
|
||||||
// to keep them in memory, but we need to update right after a new one is added.
|
|
||||||
// For now, we believe that probability of sustained DDoS by saturating our ability to
|
|
||||||
// fetch from the DB is pretty low.
|
|
||||||
|
|
||||||
app.use(morgan('dev', {skip}))
|
|
||||||
app.use(helmet({noCache: true}))
|
|
||||||
app.use(cookieParser())
|
|
||||||
app.use(register)
|
|
||||||
app.use(authenticate)
|
|
||||||
app.use(express.json())
|
|
||||||
|
|
||||||
app.get('/api/totem', (req, res) => {
|
|
||||||
const name = req.query.name
|
|
||||||
|
|
||||||
if (!name) return res.status(400).send('Name is required')
|
|
||||||
|
|
||||||
return pairing.totem(HOSTNAME, name)
|
|
||||||
.then(totem => res.send(totem))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/accounts', (req, res) => {
|
|
||||||
accounts.selectedAccounts()
|
|
||||||
.then(accounts => res.json({accounts: accounts}))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/account/:account', (req, res) => {
|
|
||||||
accounts.getAccount(req.params.account)
|
|
||||||
.then(account => res.json(account))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/api/account', (req, res) => {
|
|
||||||
return accounts.updateAccount(req.body)
|
|
||||||
.then(account => res.json(account))
|
|
||||||
.then(() => dbNotify())
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/config/:config', (req, res, next) =>
|
|
||||||
config.fetchConfigGroup(req.params.config)
|
|
||||||
.then(c => res.json(c))
|
|
||||||
.catch(next))
|
|
||||||
|
|
||||||
app.post('/api/config', (req, res, next) => {
|
|
||||||
config.saveConfigGroup(req.body)
|
|
||||||
.then(c => res.json(c))
|
|
||||||
.then(() => dbNotify())
|
|
||||||
.catch(next)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/accounts/account/:account', (req, res) => {
|
|
||||||
accounts.getAccount(req.params.account)
|
|
||||||
.then(r => res.send(r))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/machines', (req, res) => {
|
|
||||||
machineLoader.getMachineNames()
|
|
||||||
.then(r => res.send({machines: r}))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.post('/api/machines', (req, res) => {
|
|
||||||
machineLoader.setMachine(req.body)
|
|
||||||
.then(() => machineLoader.getMachineNames())
|
|
||||||
.then(r => res.send({machines: r}))
|
|
||||||
.then(() => dbNotify())
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/funding', (req, res) => {
|
|
||||||
return funding.getFunding()
|
|
||||||
.then(r => res.json(r))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/funding/:cryptoCode', (req, res) => {
|
|
||||||
const cryptoCode = req.params.cryptoCode
|
|
||||||
|
|
||||||
return funding.getFunding(cryptoCode)
|
|
||||||
.then(r => res.json(r))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/status', (req, res, next) => {
|
|
||||||
return Promise.all([server.status(), config.validateCurrentConfig()])
|
|
||||||
.then(([serverStatus, invalidConfigGroups]) => res.send({
|
|
||||||
server: serverStatus,
|
|
||||||
invalidConfigGroups
|
|
||||||
}))
|
|
||||||
.catch(next)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/transactions', (req, res, next) => {
|
|
||||||
return transactions.batch()
|
|
||||||
.then(r => res.send({transactions: r}))
|
|
||||||
.catch(next)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/transaction/:id', (req, res, next) => {
|
|
||||||
return transactions.single(req.params.id)
|
|
||||||
.then(r => {
|
|
||||||
if (!r) return res.status(404).send({Error: 'Not found'})
|
|
||||||
return res.send(r)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
app.patch('/api/transaction/:id', (req, res, next) => {
|
|
||||||
if (!req.query.cancel) return res.status(400).send({Error: 'Requires cancel'})
|
|
||||||
|
|
||||||
return transactions.cancel(req.params.id)
|
|
||||||
.then(r => {
|
|
||||||
return res.send(r)
|
|
||||||
})
|
|
||||||
.catch(() => res.status(404).send({Error: 'Not found'}))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/customers', (req, res, next) => {
|
|
||||||
return customers.batch()
|
|
||||||
.then(r => res.send({customers: r}))
|
|
||||||
.catch(next)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/customer/:id', (req, res, next) => {
|
|
||||||
return customers.getById(req.params.id)
|
|
||||||
.then(r => {
|
|
||||||
if (!r) return res.status(404).send({Error: 'Not found'})
|
|
||||||
return res.send(r)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/logs/:deviceId', (req, res, next) => {
|
|
||||||
return logs.getMachineLogs(req.params.deviceId)
|
|
||||||
.then(r => res.send(r))
|
|
||||||
.catch(next)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.get('/api/logs', (req, res, next) => {
|
|
||||||
return machineLoader.getMachines()
|
|
||||||
.then(machines => {
|
|
||||||
const firstMachine = _.first(machines)
|
|
||||||
if (!firstMachine) return res.status(404).send({Error: 'No machines'})
|
|
||||||
return logs.getMachineLogs(firstMachine.deviceId)
|
|
||||||
.then(r => res.send(r))
|
|
||||||
})
|
|
||||||
.catch(next)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.patch('/api/customer/:id', (req, res, next) => {
|
|
||||||
if (!req.params.id) return res.status(400).send({Error: 'Requires id'})
|
|
||||||
const token = req.token || req.cookies.token
|
|
||||||
return customers.update(req.params.id, req.query, token)
|
|
||||||
.then(r => res.send(r))
|
|
||||||
.catch(() => res.status(404).send({Error: 'Not found'}))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use((err, req, res, next) => {
|
|
||||||
logger.error(err)
|
|
||||||
|
|
||||||
return res.status(500).send(err.message)
|
|
||||||
})
|
|
||||||
|
|
||||||
const certOptions = {
|
|
||||||
key: fs.readFileSync(KEY_PATH),
|
|
||||||
cert: fs.readFileSync(CERT_PATH)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use(serveStatic(path.resolve(__dirname, 'public')))
|
|
||||||
|
|
||||||
if (!fs.existsSync(ID_PHOTO_CARD_DIR)) {
|
|
||||||
makeDir.sync(ID_PHOTO_CARD_DIR)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(FRONT_CAMERA_DIR)) {
|
|
||||||
makeDir.sync(FRONT_CAMERA_DIR)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!fs.existsSync(OPERATOR_DATA_DIR)) {
|
|
||||||
makeDir.sync(OPERATOR_DATA_DIR)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.use('/id-card-photo', serveStatic(ID_PHOTO_CARD_DIR, {index: false}))
|
|
||||||
app.use('/front-camera-photo', serveStatic(FRONT_CAMERA_DIR, {index: false}))
|
|
||||||
app.use('/operator-data', serveStatic(OPERATOR_DATA_DIR, {index: false}))
|
|
||||||
|
|
||||||
function register (req, res, next) {
|
|
||||||
const otp = req.query.otp
|
|
||||||
|
|
||||||
if (!otp) return next()
|
|
||||||
|
|
||||||
return login.register(otp)
|
|
||||||
.then(r => {
|
|
||||||
if (r.expired) return res.status(401).send('OTP expired, generate new registration link')
|
|
||||||
|
|
||||||
// Maybe user is using old registration key, attempt to authenticate
|
|
||||||
if (!r.success) return next()
|
|
||||||
|
|
||||||
const cookieOpts = {
|
|
||||||
httpOnly: true,
|
|
||||||
secure: true,
|
|
||||||
domain: HOSTNAME,
|
|
||||||
sameSite: true,
|
|
||||||
expires: NEVER
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = r.token
|
|
||||||
req.token = token
|
|
||||||
res.cookie('token', token, cookieOpts)
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function authenticate (req, res, next) {
|
|
||||||
const token = req.token || req.cookies.token
|
|
||||||
|
|
||||||
return login.authenticate(token)
|
|
||||||
.then(success => {
|
|
||||||
if (!success) return res.status(401).send('Authentication failed')
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
process.on('unhandledRejection', err => {
|
|
||||||
logger.error(err.stack)
|
|
||||||
process.exit(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
const socketServer = http.createServer()
|
|
||||||
const io = SocketIo(socketServer)
|
|
||||||
socketServer.listen(3060)
|
|
||||||
const socketEmitter = new EventEmitter()
|
|
||||||
|
|
||||||
io.on('connection', client => {
|
|
||||||
client.on('message', msg => socketEmitter.emit('message', msg))
|
|
||||||
})
|
|
||||||
|
|
||||||
const webServer = https.createServer(certOptions, app)
|
|
||||||
const wss = new WebSocket.Server({server: webServer})
|
|
||||||
|
|
||||||
function establishSocket (ws, token) {
|
|
||||||
return login.authenticate(token)
|
|
||||||
.then(success => {
|
|
||||||
if (!success) return ws.close(1008, 'Authentication error')
|
|
||||||
|
|
||||||
const listener = data => {
|
|
||||||
ws.send(JSON.stringify(data))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reauthenticate every once in a while, in case token expired
|
|
||||||
setInterval(() => {
|
|
||||||
return login.authenticate(token)
|
|
||||||
.then(success => {
|
|
||||||
if (!success) {
|
|
||||||
socketEmitter.removeListener('message', listener)
|
|
||||||
ws.close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, REAUTHENTICATE_INTERVAL)
|
|
||||||
|
|
||||||
socketEmitter.on('message', listener)
|
|
||||||
ws.send('Testing123')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
wss.on('connection', ws => {
|
|
||||||
const token = qs.parse(ws.upgradeReq.headers.cookie).token
|
|
||||||
|
|
||||||
return establishSocket(ws, token)
|
|
||||||
})
|
|
||||||
|
|
||||||
function run () {
|
|
||||||
const serverPort = devMode ? 8072 : 443
|
|
||||||
const supportPort = 8071
|
|
||||||
|
|
||||||
const serverLog = `lamassu-admin-server listening on port ${serverPort}`
|
|
||||||
const supportLog = `lamassu-support-server listening on port ${supportPort}`
|
|
||||||
|
|
||||||
webServer.listen(serverPort, () => logger.info(serverLog))
|
|
||||||
supportServer.run(supportPort).then(logger.info(supportLog))
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
const fs = require('fs')
|
|
||||||
const cookieParser = require('cookie-parser')
|
|
||||||
const helmet = require('helmet')
|
|
||||||
const morgan = require('morgan')
|
|
||||||
const express = require('express')
|
|
||||||
const app = express()
|
|
||||||
const https = require('https')
|
|
||||||
const _ = require('lodash/fp')
|
|
||||||
const serveStatic = require('serve-static')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const KEY_PATH = process.env.KEY_PATH
|
|
||||||
const CERT_PATH = process.env.CERT_PATH
|
|
||||||
const LAMASSU_CA_PATH = process.env.LAMASSU_CA_PATH
|
|
||||||
|
|
||||||
app.use(morgan('dev'))
|
|
||||||
app.use(helmet({noCache: true}))
|
|
||||||
app.use(cookieParser())
|
|
||||||
app.use(express.json())
|
|
||||||
app.use(serveStatic(path.resolve(__dirname, '..', '..', 'public'), {
|
|
||||||
'index': ['support-index.html']
|
|
||||||
}))
|
|
||||||
|
|
||||||
const certOptions = {
|
|
||||||
key: fs.readFileSync(KEY_PATH),
|
|
||||||
cert: fs.readFileSync(CERT_PATH),
|
|
||||||
ca: [fs.readFileSync(LAMASSU_CA_PATH)],
|
|
||||||
requestCert: true,
|
|
||||||
rejectUnauthorized: true
|
|
||||||
}
|
|
||||||
|
|
||||||
function run (port) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const webServer = https.createServer(certOptions, app)
|
|
||||||
webServer.listen(port, resolve)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { run }
|
|
||||||
|
|
@ -1,99 +0,0 @@
|
||||||
const _ = require('lodash/fp')
|
|
||||||
const BN = require('../bn')
|
|
||||||
const settingsLoader = require('./settings-loader')
|
|
||||||
const configManager = require('./config-manager')
|
|
||||||
const wallet = require('../wallet')
|
|
||||||
const ticker = require('../ticker')
|
|
||||||
const { utils: coinUtils } = require('@lamassu/coins')
|
|
||||||
const machineLoader = require('../machine-loader')
|
|
||||||
|
|
||||||
module.exports = {getFunding}
|
|
||||||
|
|
||||||
function allScopes (cryptoScopes, machineScopes) {
|
|
||||||
const scopes = []
|
|
||||||
cryptoScopes.forEach(c => {
|
|
||||||
machineScopes.forEach(m => scopes.push([c, m]))
|
|
||||||
})
|
|
||||||
|
|
||||||
return scopes
|
|
||||||
}
|
|
||||||
|
|
||||||
function allMachineScopes (machineList, machineScope) {
|
|
||||||
const machineScopes = []
|
|
||||||
|
|
||||||
if (machineScope === 'global' || machineScope === 'both') machineScopes.push('global')
|
|
||||||
if (machineScope === 'specific' || machineScope === 'both') machineList.forEach(r => machineScopes.push(r))
|
|
||||||
|
|
||||||
return machineScopes
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCryptos (config, machineList) {
|
|
||||||
const scopes = allScopes(['global'], allMachineScopes(machineList, 'both'))
|
|
||||||
const scoped = scope => configManager.scopedValue(scope[0], scope[1], 'cryptoCurrencies', config)
|
|
||||||
|
|
||||||
return _.uniq(_.flatten(_.map(scoped, scopes)))
|
|
||||||
}
|
|
||||||
|
|
||||||
function fetchMachines () {
|
|
||||||
return machineLoader.getMachines()
|
|
||||||
.then(machineList => machineList.map(r => r.deviceId))
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeCrypto (cryptoCode, _balance) {
|
|
||||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
|
||||||
const unitScale = cryptoRec.unitScale
|
|
||||||
|
|
||||||
return new BN(_balance).shiftedBy(-unitScale).decimalPlaces(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
function computeFiat (rate, cryptoCode, _balance) {
|
|
||||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
|
||||||
const unitScale = cryptoRec.unitScale
|
|
||||||
|
|
||||||
return new BN(_balance).shiftedBy(-unitScale).times(rate).decimalPlaces(5)
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFunding (_cryptoCode) {
|
|
||||||
return Promise.all([settingsLoader.loadLatest(), fetchMachines()])
|
|
||||||
.then(([settings, machineList]) => {
|
|
||||||
const config = configManager.unscoped(settings.config)
|
|
||||||
const cryptoCodes = getCryptos(settings.config, machineList)
|
|
||||||
const cryptoCode = _cryptoCode || cryptoCodes[0]
|
|
||||||
const fiatCode = config.fiatCurrency
|
|
||||||
const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes)
|
|
||||||
const cryptoCurrencies = coinUtils.cryptoCurrencies()
|
|
||||||
const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies)
|
|
||||||
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
|
|
||||||
|
|
||||||
if (!cryptoRec) throw new Error(`Unsupported coin: ${cryptoCode}`)
|
|
||||||
|
|
||||||
const promises = [
|
|
||||||
wallet.newFunding(settings, cryptoCode),
|
|
||||||
ticker.getRates(settings, fiatCode, cryptoCode)
|
|
||||||
]
|
|
||||||
|
|
||||||
return Promise.all(promises)
|
|
||||||
.then(([fundingRec, ratesRec]) => {
|
|
||||||
const rates = ratesRec.rates
|
|
||||||
const rate = (rates.ask.plus(rates.bid)).div(2)
|
|
||||||
const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance
|
|
||||||
const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance)
|
|
||||||
const pending = fundingRec.fundingPendingBalance
|
|
||||||
const fiatPending = computeFiat(rate, cryptoCode, pending)
|
|
||||||
const fundingAddress = fundingRec.fundingAddress
|
|
||||||
const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress)
|
|
||||||
|
|
||||||
return {
|
|
||||||
cryptoCode,
|
|
||||||
cryptoDisplays,
|
|
||||||
fundingAddress,
|
|
||||||
fundingAddressUrl,
|
|
||||||
confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5),
|
|
||||||
pending: computeCrypto(cryptoCode, pending).toFormat(5),
|
|
||||||
fiatConfirmedBalance: fiatConfirmedBalance.toFormat(2),
|
|
||||||
fiatPending: fiatPending.toFormat(2),
|
|
||||||
fiatCode
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
const crypto = require('crypto')
|
|
||||||
|
|
||||||
const db = require('../db')
|
|
||||||
|
|
||||||
function generateOTP (name) {
|
|
||||||
const otp = crypto.randomBytes(32).toString('hex')
|
|
||||||
|
|
||||||
const sql = 'insert into one_time_passes (token, name) values ($1, $2)'
|
|
||||||
|
|
||||||
return db.none(sql, [otp, name])
|
|
||||||
.then(() => otp)
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateOTP (otp) {
|
|
||||||
const sql = `delete from one_time_passes
|
|
||||||
where token=$1
|
|
||||||
returning name, created < now() - interval '1 hour' as expired`
|
|
||||||
|
|
||||||
return db.one(sql, [otp])
|
|
||||||
.then(r => ({success: !r.expired, expired: r.expired, name: r.name}))
|
|
||||||
.catch(() => ({success: false, expired: false}))
|
|
||||||
}
|
|
||||||
|
|
||||||
function register (otp) {
|
|
||||||
return validateOTP(otp)
|
|
||||||
.then(r => {
|
|
||||||
if (!r.success) return r
|
|
||||||
|
|
||||||
const token = crypto.randomBytes(32).toString('hex')
|
|
||||||
const sql = 'insert into user_tokens (token, name) values ($1, $2)'
|
|
||||||
|
|
||||||
return db.none(sql, [token, r.name])
|
|
||||||
.then(() => ({success: true, token: token}))
|
|
||||||
})
|
|
||||||
.catch(() => ({success: false, expired: false}))
|
|
||||||
}
|
|
||||||
|
|
||||||
function authenticate (token) {
|
|
||||||
const sql = 'select token from user_tokens where token=$1'
|
|
||||||
|
|
||||||
return db.one(sql, [token]).then(() => true).catch(() => false)
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
generateOTP,
|
|
||||||
register,
|
|
||||||
authenticate
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
const fs = require('fs')
|
|
||||||
const pify = require('pify')
|
|
||||||
const readFile = pify(fs.readFile)
|
|
||||||
const crypto = require('crypto')
|
|
||||||
const baseX = require('base-x')
|
|
||||||
|
|
||||||
const db = require('../db')
|
|
||||||
const pairing = require('../pairing')
|
|
||||||
|
|
||||||
const ALPHA_BASE = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'
|
|
||||||
const bsAlpha = baseX(ALPHA_BASE)
|
|
||||||
|
|
||||||
const CA_PATH = process.env.CA_PATH
|
|
||||||
|
|
||||||
const unpair = pairing.unpair
|
|
||||||
|
|
||||||
function totem (hostname, name) {
|
|
||||||
return readFile(CA_PATH)
|
|
||||||
.then(data => {
|
|
||||||
const caHash = crypto.createHash('sha256').update(data).digest()
|
|
||||||
const token = crypto.randomBytes(32)
|
|
||||||
const hexToken = token.toString('hex')
|
|
||||||
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
|
|
||||||
const buf = Buffer.concat([caHash, token, Buffer.from(hostname)])
|
|
||||||
const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)'
|
|
||||||
|
|
||||||
return db.none(sql, [hexToken, caHexToken, name])
|
|
||||||
.then(() => bsAlpha.encode(buf))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {totem, unpair}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "lamassu-admin-elm",
|
|
||||||
"homepage": "https://github.com/lamassu/lamassu-admin-elm",
|
|
||||||
"authors": [
|
|
||||||
"Josh Harvey <josh@lamassu.is>"
|
|
||||||
],
|
|
||||||
"description": "",
|
|
||||||
"main": "",
|
|
||||||
"license": "MIT",
|
|
||||||
"private": true,
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"bower_components",
|
|
||||||
"test",
|
|
||||||
"tests"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"name": "gridism",
|
|
||||||
"version": "0.2.2",
|
|
||||||
"author": "Coby Chapple",
|
|
||||||
"homepage": "http://cobyism.com/gridism",
|
|
||||||
"main": "./gridism.css",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git://github.com/cobyism/gridism.git"
|
|
||||||
},
|
|
||||||
"ignore": [
|
|
||||||
"shapeshifter/",
|
|
||||||
"**/*.yml",
|
|
||||||
"**/*.html"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"_release": "0.2.2",
|
|
||||||
"_resolution": {
|
|
||||||
"type": "version",
|
|
||||||
"tag": "0.2.2",
|
|
||||||
"commit": "490be0b6813d701dcc35a82b0bcc8f639e5ad63f"
|
|
||||||
},
|
|
||||||
"_source": "https://github.com/cobyism/gridism.git",
|
|
||||||
"_target": "^0.2.2",
|
|
||||||
"_originalSource": "gridism",
|
|
||||||
"_direct": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
Copyright (c) 2013 Coby Chapple.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
@ -1,80 +0,0 @@
|
||||||
# Gridism
|
|
||||||
|
|
||||||
A simple responsive CSS grid. [View the demo →](http://cobyism.com/gridism/)
|
|
||||||
|
|
||||||
## Why?
|
|
||||||
|
|
||||||
### My process
|
|
||||||
|
|
||||||
When I design web layouts, my thought process usually goes something like this:
|
|
||||||
|
|
||||||
> Alright, in this section, I want a bit that’s one third of the section’s width,
|
|
||||||
> and then next to that I want another bit that’s two thirds of the sections’s width.
|
|
||||||
> Now, in the next section…
|
|
||||||
|
|
||||||
I don’t think in 12 or 16 column grids. Instead, my mental model basically just consists of the page being divided up into multiple full-width vertical sections, and each vertical section being divided up into simple fractions of the section width.
|
|
||||||
|
|
||||||
### Existing grid frameworks
|
|
||||||
|
|
||||||
Most frameworks I’ve used don’t match that thought process *at all*. I usually have to:
|
|
||||||
|
|
||||||
1. Remember how many columns are in the grid for the particular framework I’m using.
|
|
||||||
1. Decide how I want to divide up this particular section’s content.
|
|
||||||
1. Mentally do the conversion from what I want to see (one quarter + three quarters, for example) into the number of columns I need for the grid I’m using.
|
|
||||||
1. Remember the class naming structure for the framework I’m using. Is it `.span3`, `.grid_3`, `.col-3`, or something else altogether?
|
|
||||||
1. Deal with other hassles like clearing floats, messing with column padding to have the gutters look right, indicating which elements are the first in a row, and so forth.
|
|
||||||
|
|
||||||
Only the second step should be necessary.
|
|
||||||
|
|
||||||
### Gridism’s Goals
|
|
||||||
|
|
||||||
I couldn’t find a framework that matched this mental model of how I work, so I started hacking on Gridism with the following goals:
|
|
||||||
|
|
||||||
- Class names should be memorable and self-evident.
|
|
||||||
- Gutters and basic content padding should be taken care of.
|
|
||||||
- Clearing floats should be done automatically.
|
|
||||||
- Wrapped grid sections should be independant of vertical page sections.
|
|
||||||
- Frequently required utility classes should be provided.
|
|
||||||
- Common patterns for Responsive Design™ should be built-in.
|
|
||||||
|
|
||||||
I hope you find that this project is living up to those goals. If not, please [create an issue](https://github.com/cobyism/gridism/issues/new) and let me know.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
### 1. Get the files
|
|
||||||
|
|
||||||
The easiest way to use Gridism in your project is via the [Bower](http://twitter.github.com/bower) package manager.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
bower install gridism
|
|
||||||
```
|
|
||||||
|
|
||||||
Elsewise, [download the zip folder](https://github.com/cobyism/gridism/archive/gh-pages.zip), extract it, and copy `gridism.css` into your project’s folder. Boom. Done.
|
|
||||||
|
|
||||||
### 2. Link the stylesheet
|
|
||||||
|
|
||||||
Add the following stylesheet to your HTML’s `<head>` section:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<link rel="stylesheet" href="bower_components/gridism/gridism.css">
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** If you didn’t install using Bower, you need to adjust the path of CSS file to match your file structure.
|
|
||||||
|
|
||||||
### 3. Viewport scale
|
|
||||||
|
|
||||||
Add the following meta tag to your HTML’s `<head>` section:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
||||||
```
|
|
||||||
|
|
||||||
Without this meta tag, mobiles and tablets might load your page as a scaled-down version of the desktop size, instead of resizing the content to match the device’s actual viewport width.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
I’d :heart: to receive contributions to this project. It doesn’t matter if it’s just a typo, or if you’re proposing an overhaul of the entire project—I’ll gladly take a look at your changes. Fork at will! :grinning:.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Go nuts. See [LICENSE](https://github.com/cobyism/gridism/blob/gh-pages/LICENSE) (MIT).
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"name": "gridism",
|
|
||||||
"version": "0.2.1",
|
|
||||||
"author": "Coby Chapple",
|
|
||||||
"homepage": "http://cobyism.com/gridism",
|
|
||||||
"main": "./gridism.css",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git://github.com/cobyism/gridism.git"
|
|
||||||
},
|
|
||||||
"ignore": [
|
|
||||||
"shapeshifter/",
|
|
||||||
"**/*.yml",
|
|
||||||
"**/*.html"
|
|
||||||
],
|
|
||||||
"license": "MIT"
|
|
||||||
}
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
/*
|
|
||||||
* Gridism
|
|
||||||
* A simple, responsive, and handy CSS grid by @cobyism
|
|
||||||
* https://github.com/cobyism/gridism
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Preserve some sanity */
|
|
||||||
.grid,
|
|
||||||
.unit {
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Set up some rules to govern the grid */
|
|
||||||
.grid {
|
|
||||||
display: block;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.grid .unit {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This ensures the outer gutters are equal to the (doubled) inner gutters. */
|
|
||||||
.grid .unit:first-child { padding-left: 20px; }
|
|
||||||
.grid .unit:last-child { padding-right: 20px; }
|
|
||||||
|
|
||||||
/* Nested grids already have padding though, so let's nuke it */
|
|
||||||
.unit .unit:first-child { padding-left: 0; }
|
|
||||||
.unit .unit:last-child { padding-right: 0; }
|
|
||||||
.unit .grid:first-child > .unit { padding-top: 0; }
|
|
||||||
.unit .grid:last-child > .unit { padding-bottom: 0; }
|
|
||||||
|
|
||||||
/* Let people nuke the gutters/padding completely in a couple of ways */
|
|
||||||
.no-gutters .unit,
|
|
||||||
.unit.no-gutters {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Wrapping at a maximum width is optional */
|
|
||||||
.wrap .grid,
|
|
||||||
.grid.wrap {
|
|
||||||
max-width: 978px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Width classes also have shorthand versions numbered as fractions
|
|
||||||
* For example: for a grid unit 1/3 (one third) of the parent width,
|
|
||||||
* simply apply class="w-1-3" to the element. */
|
|
||||||
.grid .whole, .grid .w-1-1 { width: 100%; }
|
|
||||||
.grid .half, .grid .w-1-2 { width: 50%; }
|
|
||||||
.grid .one-third, .grid .w-1-3 { width: 33.3332%; }
|
|
||||||
.grid .two-thirds, .grid .w-2-3 { width: 66.6665%; }
|
|
||||||
.grid .one-quarter,
|
|
||||||
.grid .one-fourth, .grid .w-1-4 { width: 25%; }
|
|
||||||
.grid .three-quarters,
|
|
||||||
.grid .three-fourths, .grid .w-3-4 { width: 75%; }
|
|
||||||
.grid .one-fifth, .grid .w-1-5 { width: 20%; }
|
|
||||||
.grid .two-fifths, .grid .w-2-5 { width: 40%; }
|
|
||||||
.grid .three-fifths, .grid .w-3-5 { width: 60%; }
|
|
||||||
.grid .four-fifths, .grid .w-4-5 { width: 80%; }
|
|
||||||
.grid .golden-small, .grid .w-g-s { width: 38.2716%; } /* Golden section: smaller piece */
|
|
||||||
.grid .golden-large, .grid .w-g-l { width: 61.7283%; } /* Golden section: larger piece */
|
|
||||||
|
|
||||||
/* Clearfix after every .grid */
|
|
||||||
.grid {
|
|
||||||
*zoom: 1;
|
|
||||||
}
|
|
||||||
.grid:before, .grid:after {
|
|
||||||
display: table;
|
|
||||||
content: "";
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
.grid:after {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Utility classes */
|
|
||||||
.align-center { text-align: center; }
|
|
||||||
.align-left { text-align: left; }
|
|
||||||
.align-right { text-align: right; }
|
|
||||||
.pull-left { float: left; }
|
|
||||||
.pull-right { float: right; }
|
|
||||||
|
|
||||||
/* A property for a better rendering of images in units: in
|
|
||||||
this way bigger pictures are just resized if the unit
|
|
||||||
becomes smaller */
|
|
||||||
.unit img {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide elements using this class by default */
|
|
||||||
.only-on-mobiles {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive Stuff */
|
|
||||||
@media screen and (max-width: 568px) {
|
|
||||||
/* Stack anything that isn't full-width on smaller screens
|
|
||||||
and doesn't provide the no-stacking-on-mobiles class */
|
|
||||||
.grid:not(.no-stacking-on-mobiles) > .unit {
|
|
||||||
width: 100% !important;
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
.unit .grid .unit {
|
|
||||||
padding-left: 0px;
|
|
||||||
padding-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sometimes, you just want to be different on small screens */
|
|
||||||
.center-on-mobiles {
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
.hide-on-mobiles {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
.only-on-mobiles {
|
|
||||||
display: block !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Expand the wrap a bit further on larger screens */
|
|
||||||
@media screen and (min-width: 1180px) {
|
|
||||||
.wider .grid,
|
|
||||||
.grid.wider {
|
|
||||||
max-width: 1180px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qr-code",
|
|
||||||
"version": "0.1.8",
|
|
||||||
"homepage": "https://github.com/educastellano/qr-code",
|
|
||||||
"authors": [
|
|
||||||
"Eduard Castellano <educastellano08@gmail.com>"
|
|
||||||
],
|
|
||||||
"description": "Web Component for generating QR codes",
|
|
||||||
"main": "src/qr-code.js",
|
|
||||||
"keywords": [
|
|
||||||
"qr",
|
|
||||||
"qrcode",
|
|
||||||
"qr-code",
|
|
||||||
"webcomponent",
|
|
||||||
"customelement",
|
|
||||||
"web-components"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"bower_components",
|
|
||||||
"test",
|
|
||||||
"tests",
|
|
||||||
"demo"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"qrjs": "~0.1.2"
|
|
||||||
},
|
|
||||||
"_release": "0.1.8",
|
|
||||||
"_resolution": {
|
|
||||||
"type": "version",
|
|
||||||
"tag": "v0.1.8",
|
|
||||||
"commit": "28a413834c62d8ec7f5b3f3005fe2ee78e47e647"
|
|
||||||
},
|
|
||||||
"_source": "https://github.com/educastellano/qr-code.git",
|
|
||||||
"_target": "^0.1.8",
|
|
||||||
"_originalSource": "webcomponent-qr-code",
|
|
||||||
"_direct": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
# <qr-code>
|
|
||||||
|
|
||||||
Web Component for generating QR Codes, using (a [fork](https://github.com/educastellano/qr.js) of) [qr.js](https://github.com/lifthrasiir/qr.js) lib.
|
|
||||||
|
|
||||||
> Maintained by [Eduard Castellano](https://github.com/educastellano).
|
|
||||||
|
|
||||||
## Demo
|
|
||||||
|
|
||||||
> [Check it live](http://educastellano.github.io/qr-code/demo).
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
* **NPM and Browserify** ([polyfill](https://github.com/WebComponents/webcomponentsjs) and the component):
|
|
||||||
|
|
||||||
Install:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install webcomponents.js
|
|
||||||
npm install webcomponent-qr-code
|
|
||||||
```
|
|
||||||
|
|
||||||
Import:
|
|
||||||
|
|
||||||
```js
|
|
||||||
require('webcomponents.js');
|
|
||||||
require('webcomponent-qr-code');
|
|
||||||
```
|
|
||||||
|
|
||||||
* **Bower** ([polyfill](https://github.com/WebComponents/webcomponentsjs), [qr.js](https://github.com/educastellano/qr.js) and the component):
|
|
||||||
|
|
||||||
Install:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
bower install webcomponentsjs
|
|
||||||
bower install webcomponent-qr-code
|
|
||||||
```
|
|
||||||
|
|
||||||
Import:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
|
|
||||||
<script src="bower_components/qrjs/qr.js"></script>
|
|
||||||
<script src="bower_components/webcomponent-qr-code/src/qr-code.js"></script>
|
|
||||||
```
|
|
||||||
|
|
||||||
> You can also import the component with [HTML Imports](http://w3c.github.io/webcomponents/spec/imports/), but you still need to import the polyfill and the qr.js lib separately:
|
|
||||||
>
|
|
||||||
> ```html
|
|
||||||
> <link rel="import" href="bower_components/webcomponent-qr-code/src/qr-code.html">
|
|
||||||
> ```
|
|
||||||
|
|
||||||
* **Start using it!**
|
|
||||||
|
|
||||||
```html
|
|
||||||
<qr-code data="hello world!"></qr-code>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Options
|
|
||||||
|
|
||||||
Attribute | Options | Default | Description
|
|
||||||
--- | --- | --- | ---
|
|
||||||
`data` | *string* | `null` | The information encoded by the QR code.
|
|
||||||
`format` | `png`, `html`, `svg` | `png` | Format of the QR code rendered inside the component.
|
|
||||||
`modulesize` | *int* | `5` | Size of the modules in *pixels*.
|
|
||||||
`margin` | *int* | `4` | Margin of the QR code in *modules*.
|
|
||||||
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
1. Fork it!
|
|
||||||
2. Create your feature branch: `git checkout -b my-new-feature`
|
|
||||||
3. Commit your changes: `git commit -m 'Add some feature'`
|
|
||||||
4. Push to the branch: `git push origin my-new-feature`
|
|
||||||
5. Submit a pull request :D
|
|
||||||
|
|
||||||
## History
|
|
||||||
|
|
||||||
* v0.1.7 April 11, 2015
|
|
||||||
* Support for SVG
|
|
||||||
* v0.1.6 April 10, 2015
|
|
||||||
* Default attributes
|
|
||||||
* qr.js removed and used as a dependency
|
|
||||||
* Available in NPM
|
|
||||||
* v0.1.1 March 31, 2015
|
|
||||||
* Framework-agnostic webcomponent (no use of Polymer)
|
|
||||||
* Available in Bower
|
|
||||||
* v0.0.1 September 18, 2013
|
|
||||||
* Started project using [boilerplate-element](https://github.com/customelements/boilerplate-element)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[MIT License](http://opensource.org/licenses/MIT)
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qr-code",
|
|
||||||
"version": "0.1.7",
|
|
||||||
"homepage": "https://github.com/educastellano/qr-code",
|
|
||||||
"authors": [
|
|
||||||
"Eduard Castellano <educastellano08@gmail.com>"
|
|
||||||
],
|
|
||||||
"description": "Web Component for generating QR codes",
|
|
||||||
"main": "src/qr-code.js",
|
|
||||||
"keywords": [
|
|
||||||
"qr",
|
|
||||||
"qrcode",
|
|
||||||
"qr-code",
|
|
||||||
"webcomponent",
|
|
||||||
"customelement",
|
|
||||||
"web-components"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"bower_components",
|
|
||||||
"test",
|
|
||||||
"tests",
|
|
||||||
"demo"
|
|
||||||
],
|
|
||||||
"dependencies": {
|
|
||||||
"qrjs": "~0.1.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title><qr-code></title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<a href="demo">Demo here</a>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
require('./src/qr-code.js')
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
{
|
|
||||||
"name": "webcomponent-qr-code",
|
|
||||||
"version": "0.1.8",
|
|
||||||
"description": "Web Component for generating QR codes",
|
|
||||||
"main": "qr-code.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/educastellano/qr-code.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"qr",
|
|
||||||
"qrcode",
|
|
||||||
"qr-code",
|
|
||||||
"webcomponent",
|
|
||||||
"custom-element"
|
|
||||||
],
|
|
||||||
"author": "Eduard Castellano",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/educastellano/qr-code/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/educastellano/qr-code",
|
|
||||||
"dependencies": {
|
|
||||||
"qrjs": "^0.1.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
<polymer-element name="qr-code"
|
|
||||||
attributes="data format modulesize margin">
|
|
||||||
|
|
||||||
<script>
|
|
||||||
Polymer('qr-code', {
|
|
||||||
|
|
||||||
format: 'png',
|
|
||||||
|
|
||||||
dataChanged: function () {
|
|
||||||
this.generate();
|
|
||||||
},
|
|
||||||
|
|
||||||
generate: function () {
|
|
||||||
var options = {
|
|
||||||
modulesize: this.modulesize,
|
|
||||||
margin: this.margin === 0 ? -1 : this.margin
|
|
||||||
};
|
|
||||||
if (this.format === 'png') {
|
|
||||||
this.generatePNG(options);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.generateHTML(options);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
generatePNG: function (options) {
|
|
||||||
var img;
|
|
||||||
try {
|
|
||||||
img = document.createElement('img');
|
|
||||||
img.src = QRCode.generatePNG(this.data, options);
|
|
||||||
this.clear();
|
|
||||||
this.appendChild(img);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log('no canvas support');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
generateHTML: function (options) {
|
|
||||||
var div = QRCode.generateHTML(this.data, options);
|
|
||||||
this.clear();
|
|
||||||
this.appendChild(div);
|
|
||||||
},
|
|
||||||
|
|
||||||
clear: function () {
|
|
||||||
var i;
|
|
||||||
for (i=0; i<this.children.length; i++) {
|
|
||||||
this.children[i].parentNode.removeChild(this.children[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</polymer-element>
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
<script src="qr-code.js"></script>
|
|
||||||
|
|
@ -1,144 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(function(definition) {
|
|
||||||
if (typeof define === 'function' && define.amd) {
|
|
||||||
define(['QRCode'], definition);
|
|
||||||
} else if (typeof module === 'object' && module.exports) {
|
|
||||||
var QRCode = require('qrjs');
|
|
||||||
module.exports = definition(QRCode);
|
|
||||||
} else {
|
|
||||||
definition(window.QRCode);
|
|
||||||
}
|
|
||||||
})(function(QRCode) {
|
|
||||||
//
|
|
||||||
// Prototype
|
|
||||||
//
|
|
||||||
var proto = Object.create(HTMLElement.prototype, {
|
|
||||||
//
|
|
||||||
// Attributes
|
|
||||||
//
|
|
||||||
attrs: {
|
|
||||||
value: {
|
|
||||||
data: null,
|
|
||||||
format: 'png',
|
|
||||||
modulesize: 5,
|
|
||||||
margin: 4
|
|
||||||
}
|
|
||||||
},
|
|
||||||
defineAttributes: {
|
|
||||||
value: function () {
|
|
||||||
var attrs = Object.keys(this.attrs),
|
|
||||||
attr;
|
|
||||||
for (var i=0; i<attrs.length; i++) {
|
|
||||||
attr = attrs[i];
|
|
||||||
(function (attr) {
|
|
||||||
Object.defineProperty(this, attr, {
|
|
||||||
get: function () {
|
|
||||||
var value = this.getAttribute(attr);
|
|
||||||
return value === null ? this.attrs[attr] : value;
|
|
||||||
},
|
|
||||||
set: function (value) {
|
|
||||||
this.setAttribute(attr, value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}.bind(this))(attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//
|
|
||||||
// LifeCycle Callbacks
|
|
||||||
//
|
|
||||||
createdCallback: {
|
|
||||||
value: function () {
|
|
||||||
this.createShadowRoot();
|
|
||||||
this.defineAttributes();
|
|
||||||
this.generate();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
attributeChangedCallback: {
|
|
||||||
value: function (attrName, oldVal, newVal) {
|
|
||||||
var fn = this[attrName+'Changed'];
|
|
||||||
if (fn && typeof fn === 'function') {
|
|
||||||
fn.call(this, oldVal, newVal);
|
|
||||||
}
|
|
||||||
this.generate();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//
|
|
||||||
// Methods
|
|
||||||
//
|
|
||||||
getOptions: {
|
|
||||||
value: function () {
|
|
||||||
var modulesize = this.modulesize,
|
|
||||||
margin = this.margin;
|
|
||||||
return {
|
|
||||||
modulesize: modulesize !== null ? parseInt(modulesize) : modulesize,
|
|
||||||
margin: margin !== null ? parseInt(margin) : margin
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
generate: {
|
|
||||||
value: function () {
|
|
||||||
if (this.data !== null) {
|
|
||||||
if (this.format === 'png') {
|
|
||||||
this.generatePNG();
|
|
||||||
}
|
|
||||||
else if (this.format === 'html') {
|
|
||||||
this.generateHTML();
|
|
||||||
}
|
|
||||||
else if (this.format === 'svg') {
|
|
||||||
this.generateSVG();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.shadowRoot.innerHTML = '<div>qr-code: '+ this.format +' not supported!</div>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.shadowRoot.innerHTML = '<div>qr-code: no data!</div>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
generatePNG: {
|
|
||||||
value: function () {
|
|
||||||
try {
|
|
||||||
var img = document.createElement('img');
|
|
||||||
img.src = QRCode.generatePNG(this.data, this.getOptions());
|
|
||||||
this.clear();
|
|
||||||
this.shadowRoot.appendChild(img);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
this.shadowRoot.innerHTML = '<div>qr-code: no canvas support!</div>'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
generateHTML: {
|
|
||||||
value: function () {
|
|
||||||
var div = QRCode.generateHTML(this.data, this.getOptions());
|
|
||||||
this.clear();
|
|
||||||
this.shadowRoot.appendChild(div);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
generateSVG: {
|
|
||||||
value: function () {
|
|
||||||
var div = QRCode.generateSVG(this.data, this.getOptions());
|
|
||||||
this.clear();
|
|
||||||
this.shadowRoot.appendChild(div);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clear: {
|
|
||||||
value: function () {
|
|
||||||
while (this.shadowRoot.lastChild) {
|
|
||||||
this.shadowRoot.removeChild(this.shadowRoot.lastChild);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//
|
|
||||||
// Register
|
|
||||||
//
|
|
||||||
document.registerElement('qr-code', {
|
|
||||||
prototype: proto
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qrcodejs",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"homepage": "https://github.com/CatTail/qrcodejs",
|
|
||||||
"authors": [
|
|
||||||
"davidshimjs ssm0123@gmail.com"
|
|
||||||
],
|
|
||||||
"description": "Cross-browser QRCode generator for javascript",
|
|
||||||
"main": "qrcode.js",
|
|
||||||
"keywords": [
|
|
||||||
"qrcode"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"bower_components",
|
|
||||||
"test",
|
|
||||||
"tests"
|
|
||||||
],
|
|
||||||
"_release": "0.1.0",
|
|
||||||
"_resolution": {
|
|
||||||
"type": "version",
|
|
||||||
"tag": "v0.1.0",
|
|
||||||
"commit": "71340740270b3c9d797ecaa7d7a75af36037217d"
|
|
||||||
},
|
|
||||||
"_source": "https://github.com/CatTail/qrcodejs.git",
|
|
||||||
"_target": "^0.1.0",
|
|
||||||
"_originalSource": "qrcodejs",
|
|
||||||
"_direct": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
---------------------
|
|
||||||
Copyright (c) 2012 davidshimjs
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
# QRCode.js
|
|
||||||
QRCode.js is javascript library for making QRCode. QRCode.js supports Cross-browser with HTML5 Canvas and table tag in DOM.
|
|
||||||
QRCode.js has no dependencies.
|
|
||||||
|
|
||||||
## Basic Usages
|
|
||||||
```
|
|
||||||
<div id="qrcode"></div>
|
|
||||||
<script type="text/javascript">
|
|
||||||
new QRCode(document.getElementById("qrcode"), "http://jindo.dev.naver.com/collie");
|
|
||||||
</script>
|
|
||||||
```
|
|
||||||
|
|
||||||
or with some options
|
|
||||||
|
|
||||||
```
|
|
||||||
var qrcode = new QRCode("test", {
|
|
||||||
text: "http://jindo.dev.naver.com/collie",
|
|
||||||
width: 128,
|
|
||||||
height: 128,
|
|
||||||
colorDark : "#000000",
|
|
||||||
colorLight : "#ffffff",
|
|
||||||
correctLevel : QRCode.CorrectLevel.H
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
and you can use some methods
|
|
||||||
|
|
||||||
```
|
|
||||||
qrcode.clear(); // clear the code.
|
|
||||||
qrcode.makeCode("http://naver.com"); // make another code.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Browser Compatibility
|
|
||||||
IE6~10, Chrome, Firefox, Safari, Opera, Mobile Safari, Android, Windows Mobile, ETC.
|
|
||||||
|
|
||||||
## License
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
## Contact
|
|
||||||
twitter @davidshimjs
|
|
||||||
|
|
||||||
[](https://bitdeli.com/free "Bitdeli Badge")
|
|
||||||
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qrcodejs",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"homepage": "https://github.com/CatTail/qrcodejs",
|
|
||||||
"authors": [
|
|
||||||
"davidshimjs ssm0123@gmail.com"
|
|
||||||
],
|
|
||||||
"description": "Cross-browser QRCode generator for javascript",
|
|
||||||
"main": "qrcode.js",
|
|
||||||
"keywords": [
|
|
||||||
"qrcode"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"bower_components",
|
|
||||||
"test",
|
|
||||||
"tests"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko" lang="ko">
|
|
||||||
<head>
|
|
||||||
<title>Cross-Browser QRCode generator for Javascript</title>
|
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
|
|
||||||
<script type="text/javascript" src="jquery.min.js"></script>
|
|
||||||
<script type="text/javascript" src="qrcode.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<input id="text" type="text" value="http://jindo.dev.naver.com/collie" style="width:80%" /><br />
|
|
||||||
<div id="qrcode" style="width:100px; height:100px; margin-top:15px;"></div>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
var qrcode = new QRCode(document.getElementById("qrcode"), {
|
|
||||||
width : 100,
|
|
||||||
height : 100
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeCode () {
|
|
||||||
var elText = document.getElementById("text");
|
|
||||||
|
|
||||||
if (!elText.value) {
|
|
||||||
alert("Input a text");
|
|
||||||
elText.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qrcode.makeCode(elText.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
makeCode();
|
|
||||||
|
|
||||||
$("#text").
|
|
||||||
on("blur", function () {
|
|
||||||
makeCode();
|
|
||||||
}).
|
|
||||||
on("keydown", function (e) {
|
|
||||||
if (e.keyCode == 13) {
|
|
||||||
makeCode();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
<?xml version="1.0" standalone="yes"?>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-50 0 200 100">
|
|
||||||
<g id="qrcode"/>
|
|
||||||
<foreignObject x="-50" y="0" width="100" height="100">
|
|
||||||
<body xmlns="http://www.w3.org/1999/xhtml" style="padding:0; margin:0">
|
|
||||||
<div style="padding:inherit; margin:inherit; height:100%">
|
|
||||||
<textarea id="text" style="height:100%; width:100%; position:absolute; margin:inherit; padding:inherit">james</textarea>
|
|
||||||
</div>
|
|
||||||
<script type="application/ecmascript" src="qrcode.js"></script>
|
|
||||||
<script type="application/ecmascript">
|
|
||||||
var elem = document.getElementById("qrcode");
|
|
||||||
var qrcode = new QRCode(elem, {
|
|
||||||
width : 100,
|
|
||||||
height : 100
|
|
||||||
});
|
|
||||||
|
|
||||||
function makeCode () {
|
|
||||||
var elText = document.getElementById("text");
|
|
||||||
|
|
||||||
if (elText.value === "") {
|
|
||||||
//alert("Input a text");
|
|
||||||
//elText.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qrcode.makeCode(elText.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
makeCode();
|
|
||||||
|
|
||||||
document.getElementById("text").onkeyup = function (e) {
|
|
||||||
makeCode();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</foreignObject>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
File diff suppressed because one or more lines are too long
609
lib/admin/public/bower_components/qrcodejs/qrcode.js
vendored
609
lib/admin/public/bower_components/qrcodejs/qrcode.js
vendored
|
|
@ -1,609 +0,0 @@
|
||||||
/**
|
|
||||||
* @fileoverview
|
|
||||||
* - Using the 'QRCode for Javascript library'
|
|
||||||
* - Fixed dataset of 'QRCode for Javascript library' for support full-spec.
|
|
||||||
* - this library has no dependencies.
|
|
||||||
*
|
|
||||||
* @author davidshimjs
|
|
||||||
* @see <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
|
|
||||||
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
|
|
||||||
*/
|
|
||||||
var QRCode;
|
|
||||||
|
|
||||||
(function () {
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
// QRCode for JavaScript
|
|
||||||
//
|
|
||||||
// Copyright (c) 2009 Kazuhiko Arase
|
|
||||||
//
|
|
||||||
// URL: http://www.d-project.com/
|
|
||||||
//
|
|
||||||
// Licensed under the MIT license:
|
|
||||||
// http://www.opensource.org/licenses/mit-license.php
|
|
||||||
//
|
|
||||||
// The word "QR Code" is registered trademark of
|
|
||||||
// DENSO WAVE INCORPORATED
|
|
||||||
// http://www.denso-wave.com/qrcode/faqpatent-e.html
|
|
||||||
//
|
|
||||||
//---------------------------------------------------------------------
|
|
||||||
function QR8bitByte(data) {
|
|
||||||
this.mode = QRMode.MODE_8BIT_BYTE;
|
|
||||||
this.data = data;
|
|
||||||
this.parsedData = [];
|
|
||||||
|
|
||||||
// Added to support UTF-8 Characters
|
|
||||||
for (var i = 0, l = this.data.length; i < l; i++) {
|
|
||||||
var byteArray = [];
|
|
||||||
var code = this.data.charCodeAt(i);
|
|
||||||
|
|
||||||
if (code > 0x10000) {
|
|
||||||
byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18);
|
|
||||||
byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12);
|
|
||||||
byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6);
|
|
||||||
byteArray[3] = 0x80 | (code & 0x3F);
|
|
||||||
} else if (code > 0x800) {
|
|
||||||
byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12);
|
|
||||||
byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6);
|
|
||||||
byteArray[2] = 0x80 | (code & 0x3F);
|
|
||||||
} else if (code > 0x80) {
|
|
||||||
byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6);
|
|
||||||
byteArray[1] = 0x80 | (code & 0x3F);
|
|
||||||
} else {
|
|
||||||
byteArray[0] = code;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.parsedData.push(byteArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.parsedData = Array.prototype.concat.apply([], this.parsedData);
|
|
||||||
|
|
||||||
if (this.parsedData.length != this.data.length) {
|
|
||||||
this.parsedData.unshift(191);
|
|
||||||
this.parsedData.unshift(187);
|
|
||||||
this.parsedData.unshift(239);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QR8bitByte.prototype = {
|
|
||||||
getLength: function (buffer) {
|
|
||||||
return this.parsedData.length;
|
|
||||||
},
|
|
||||||
write: function (buffer) {
|
|
||||||
for (var i = 0, l = this.parsedData.length; i < l; i++) {
|
|
||||||
buffer.put(this.parsedData[i], 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function QRCodeModel(typeNumber, errorCorrectLevel) {
|
|
||||||
this.typeNumber = typeNumber;
|
|
||||||
this.errorCorrectLevel = errorCorrectLevel;
|
|
||||||
this.modules = null;
|
|
||||||
this.moduleCount = 0;
|
|
||||||
this.dataCache = null;
|
|
||||||
this.dataList = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);}
|
|
||||||
return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
|
|
||||||
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=7){this.setupTypeNumber(test);}
|
|
||||||
if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);}
|
|
||||||
this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}}
|
|
||||||
return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
|
|
||||||
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
|
|
||||||
this.modules[r][6]=(r%2==0);}
|
|
||||||
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
|
|
||||||
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
|
|
||||||
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;}
|
|
||||||
for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}}
|
|
||||||
for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}}
|
|
||||||
this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex<data.length){dark=(((data[byteIndex]>>>bitIndex)&1)==1);}
|
|
||||||
var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;}
|
|
||||||
this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}}
|
|
||||||
row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
|
|
||||||
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
|
|
||||||
if(buffer.getLengthInBits()>totalDataCount*8){throw new Error("code length overflow. ("
|
|
||||||
+buffer.getLengthInBits()
|
|
||||||
+">"
|
|
||||||
+totalDataCount*8
|
|
||||||
+")");}
|
|
||||||
if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);}
|
|
||||||
while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);}
|
|
||||||
while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;}
|
|
||||||
buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;}
|
|
||||||
buffer.put(QRCodeModel.PAD1,8);}
|
|
||||||
return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
|
|
||||||
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
|
|
||||||
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
|
|
||||||
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
|
|
||||||
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
|
|
||||||
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));}
|
|
||||||
return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));}
|
|
||||||
return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;}
|
|
||||||
return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
|
|
||||||
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
|
|
||||||
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
|
|
||||||
if(r==0&&c==0){continue;}
|
|
||||||
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
|
|
||||||
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
|
|
||||||
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
|
|
||||||
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
|
|
||||||
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
|
|
||||||
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
|
|
||||||
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
|
|
||||||
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
|
|
||||||
while(n>=256){n-=255;}
|
|
||||||
return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<<i;}
|
|
||||||
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
|
|
||||||
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
|
|
||||||
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
|
|
||||||
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
|
|
||||||
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
|
|
||||||
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
|
|
||||||
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
|
|
||||||
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
|
|
||||||
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
|
|
||||||
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
|
|
||||||
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
|
|
||||||
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
|
|
||||||
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
|
|
||||||
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);}
|
|
||||||
if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));}
|
|
||||||
this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]];
|
|
||||||
|
|
||||||
function _isSupportCanvas() {
|
|
||||||
return typeof CanvasRenderingContext2D != "undefined";
|
|
||||||
}
|
|
||||||
|
|
||||||
// android 2.x doesn't support Data-URI spec
|
|
||||||
function _getAndroid() {
|
|
||||||
var android = false;
|
|
||||||
var sAgent = navigator.userAgent;
|
|
||||||
|
|
||||||
if (/android/i.test(sAgent)) { // android
|
|
||||||
android = true;
|
|
||||||
aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i);
|
|
||||||
|
|
||||||
if (aMat && aMat[1]) {
|
|
||||||
android = parseFloat(aMat[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return android;
|
|
||||||
}
|
|
||||||
|
|
||||||
var svgDrawer = (function() {
|
|
||||||
|
|
||||||
var Drawing = function (el, htOption) {
|
|
||||||
this._el = el;
|
|
||||||
this._htOption = htOption;
|
|
||||||
};
|
|
||||||
|
|
||||||
Drawing.prototype.draw = function (oQRCode) {
|
|
||||||
var _htOption = this._htOption;
|
|
||||||
var _el = this._el;
|
|
||||||
var nCount = oQRCode.getModuleCount();
|
|
||||||
var nWidth = Math.floor(_htOption.width / nCount);
|
|
||||||
var nHeight = Math.floor(_htOption.height / nCount);
|
|
||||||
|
|
||||||
this.clear();
|
|
||||||
|
|
||||||
function makeSVG(tag, attrs) {
|
|
||||||
var el = document.createElementNS('http://www.w3.org/2000/svg', tag);
|
|
||||||
for (var k in attrs)
|
|
||||||
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight});
|
|
||||||
svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink");
|
|
||||||
_el.appendChild(svg);
|
|
||||||
|
|
||||||
svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"}));
|
|
||||||
|
|
||||||
for (var row = 0; row < nCount; row++) {
|
|
||||||
for (var col = 0; col < nCount; col++) {
|
|
||||||
if (oQRCode.isDark(row, col)) {
|
|
||||||
var child = makeSVG("use", {"x": String(row), "y": String(col)});
|
|
||||||
child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template")
|
|
||||||
svg.appendChild(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Drawing.prototype.clear = function () {
|
|
||||||
while (this._el.hasChildNodes())
|
|
||||||
this._el.removeChild(this._el.lastChild);
|
|
||||||
};
|
|
||||||
return Drawing;
|
|
||||||
})();
|
|
||||||
|
|
||||||
var useSVG = document.documentElement.tagName.toLowerCase() === "svg";
|
|
||||||
|
|
||||||
// Drawing in DOM by using Table tag
|
|
||||||
var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () {
|
|
||||||
var Drawing = function (el, htOption) {
|
|
||||||
this._el = el;
|
|
||||||
this._htOption = htOption;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw the QRCode
|
|
||||||
*
|
|
||||||
* @param {QRCode} oQRCode
|
|
||||||
*/
|
|
||||||
Drawing.prototype.draw = function (oQRCode) {
|
|
||||||
var _htOption = this._htOption;
|
|
||||||
var _el = this._el;
|
|
||||||
var nCount = oQRCode.getModuleCount();
|
|
||||||
var nWidth = Math.floor(_htOption.width / nCount);
|
|
||||||
var nHeight = Math.floor(_htOption.height / nCount);
|
|
||||||
var aHTML = ['<table style="border:0;border-collapse:collapse;">'];
|
|
||||||
|
|
||||||
for (var row = 0; row < nCount; row++) {
|
|
||||||
aHTML.push('<tr>');
|
|
||||||
|
|
||||||
for (var col = 0; col < nCount; col++) {
|
|
||||||
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
|
|
||||||
}
|
|
||||||
|
|
||||||
aHTML.push('</tr>');
|
|
||||||
}
|
|
||||||
|
|
||||||
aHTML.push('</table>');
|
|
||||||
_el.innerHTML = aHTML.join('');
|
|
||||||
|
|
||||||
// Fix the margin values as real size.
|
|
||||||
var elTable = _el.childNodes[0];
|
|
||||||
var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2;
|
|
||||||
var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2;
|
|
||||||
|
|
||||||
if (nLeftMarginTable > 0 && nTopMarginTable > 0) {
|
|
||||||
elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the QRCode
|
|
||||||
*/
|
|
||||||
Drawing.prototype.clear = function () {
|
|
||||||
this._el.innerHTML = '';
|
|
||||||
};
|
|
||||||
|
|
||||||
return Drawing;
|
|
||||||
})() : (function () { // Drawing in Canvas
|
|
||||||
function _onMakeImage() {
|
|
||||||
this._elImage.src = this._elCanvas.toDataURL("image/png");
|
|
||||||
this._elImage.style.display = "block";
|
|
||||||
this._elCanvas.style.display = "none";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Android 2.1 bug workaround
|
|
||||||
// http://code.google.com/p/android/issues/detail?id=5141
|
|
||||||
if (this._android && this._android <= 2.1) {
|
|
||||||
var factor = 1 / window.devicePixelRatio;
|
|
||||||
var drawImage = CanvasRenderingContext2D.prototype.drawImage;
|
|
||||||
CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) {
|
|
||||||
if (("nodeName" in image) && /img/i.test(image.nodeName)) {
|
|
||||||
for (var i = arguments.length - 1; i >= 1; i--) {
|
|
||||||
arguments[i] = arguments[i] * factor;
|
|
||||||
}
|
|
||||||
} else if (typeof dw == "undefined") {
|
|
||||||
arguments[1] *= factor;
|
|
||||||
arguments[2] *= factor;
|
|
||||||
arguments[3] *= factor;
|
|
||||||
arguments[4] *= factor;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawImage.apply(this, arguments);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check whether the user's browser supports Data URI or not
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {Function} fSuccess Occurs if it supports Data URI
|
|
||||||
* @param {Function} fFail Occurs if it doesn't support Data URI
|
|
||||||
*/
|
|
||||||
function _safeSetDataURI(fSuccess, fFail) {
|
|
||||||
var self = this;
|
|
||||||
self._fFail = fFail;
|
|
||||||
self._fSuccess = fSuccess;
|
|
||||||
|
|
||||||
// Check it just once
|
|
||||||
if (self._bSupportDataURI === null) {
|
|
||||||
var el = document.createElement("img");
|
|
||||||
var fOnError = function() {
|
|
||||||
self._bSupportDataURI = false;
|
|
||||||
|
|
||||||
if (self._fFail) {
|
|
||||||
_fFail.call(self);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var fOnSuccess = function() {
|
|
||||||
self._bSupportDataURI = true;
|
|
||||||
|
|
||||||
if (self._fSuccess) {
|
|
||||||
self._fSuccess.call(self);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
el.onabort = fOnError;
|
|
||||||
el.onerror = fOnError;
|
|
||||||
el.onload = fOnSuccess;
|
|
||||||
el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data.
|
|
||||||
return;
|
|
||||||
} else if (self._bSupportDataURI === true && self._fSuccess) {
|
|
||||||
self._fSuccess.call(self);
|
|
||||||
} else if (self._bSupportDataURI === false && self._fFail) {
|
|
||||||
self._fFail.call(self);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drawing QRCode by using canvas
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {HTMLElement} el
|
|
||||||
* @param {Object} htOption QRCode Options
|
|
||||||
*/
|
|
||||||
var Drawing = function (el, htOption) {
|
|
||||||
this._bIsPainted = false;
|
|
||||||
this._android = _getAndroid();
|
|
||||||
|
|
||||||
this._htOption = htOption;
|
|
||||||
this._elCanvas = document.createElement("canvas");
|
|
||||||
this._elCanvas.width = htOption.width;
|
|
||||||
this._elCanvas.height = htOption.height;
|
|
||||||
el.appendChild(this._elCanvas);
|
|
||||||
this._el = el;
|
|
||||||
this._oContext = this._elCanvas.getContext("2d");
|
|
||||||
this._bIsPainted = false;
|
|
||||||
this._elImage = document.createElement("img");
|
|
||||||
this._elImage.alt = "Scan me!";
|
|
||||||
this._elImage.style.display = "none";
|
|
||||||
this._el.appendChild(this._elImage);
|
|
||||||
this._bSupportDataURI = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draw the QRCode
|
|
||||||
*
|
|
||||||
* @param {QRCode} oQRCode
|
|
||||||
*/
|
|
||||||
Drawing.prototype.draw = function (oQRCode) {
|
|
||||||
var _elImage = this._elImage;
|
|
||||||
var _oContext = this._oContext;
|
|
||||||
var _htOption = this._htOption;
|
|
||||||
|
|
||||||
var nCount = oQRCode.getModuleCount();
|
|
||||||
var nWidth = _htOption.width / nCount;
|
|
||||||
var nHeight = _htOption.height / nCount;
|
|
||||||
var nRoundedWidth = Math.round(nWidth);
|
|
||||||
var nRoundedHeight = Math.round(nHeight);
|
|
||||||
|
|
||||||
_elImage.style.display = "none";
|
|
||||||
this.clear();
|
|
||||||
|
|
||||||
for (var row = 0; row < nCount; row++) {
|
|
||||||
for (var col = 0; col < nCount; col++) {
|
|
||||||
var bIsDark = oQRCode.isDark(row, col);
|
|
||||||
var nLeft = col * nWidth;
|
|
||||||
var nTop = row * nHeight;
|
|
||||||
_oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
|
|
||||||
_oContext.lineWidth = 1;
|
|
||||||
_oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight;
|
|
||||||
_oContext.fillRect(nLeft, nTop, nWidth, nHeight);
|
|
||||||
|
|
||||||
// 안티 앨리어싱 방지 처리
|
|
||||||
_oContext.strokeRect(
|
|
||||||
Math.floor(nLeft) + 0.5,
|
|
||||||
Math.floor(nTop) + 0.5,
|
|
||||||
nRoundedWidth,
|
|
||||||
nRoundedHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
_oContext.strokeRect(
|
|
||||||
Math.ceil(nLeft) - 0.5,
|
|
||||||
Math.ceil(nTop) - 0.5,
|
|
||||||
nRoundedWidth,
|
|
||||||
nRoundedHeight
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._bIsPainted = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make the image from Canvas if the browser supports Data URI.
|
|
||||||
*/
|
|
||||||
Drawing.prototype.makeImage = function () {
|
|
||||||
if (this._bIsPainted) {
|
|
||||||
_safeSetDataURI.call(this, _onMakeImage);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return whether the QRCode is painted or not
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
*/
|
|
||||||
Drawing.prototype.isPainted = function () {
|
|
||||||
return this._bIsPainted;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the QRCode
|
|
||||||
*/
|
|
||||||
Drawing.prototype.clear = function () {
|
|
||||||
this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height);
|
|
||||||
this._bIsPainted = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {Number} nNumber
|
|
||||||
*/
|
|
||||||
Drawing.prototype.round = function (nNumber) {
|
|
||||||
if (!nNumber) {
|
|
||||||
return nNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.floor(nNumber * 1000) / 1000;
|
|
||||||
};
|
|
||||||
|
|
||||||
return Drawing;
|
|
||||||
})();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the type by string length
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {String} sText
|
|
||||||
* @param {Number} nCorrectLevel
|
|
||||||
* @return {Number} type
|
|
||||||
*/
|
|
||||||
function _getTypeNumber(sText, nCorrectLevel) {
|
|
||||||
var nType = 1;
|
|
||||||
var length = _getUTF8Length(sText);
|
|
||||||
|
|
||||||
for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) {
|
|
||||||
var nLimit = 0;
|
|
||||||
|
|
||||||
switch (nCorrectLevel) {
|
|
||||||
case QRErrorCorrectLevel.L :
|
|
||||||
nLimit = QRCodeLimitLength[i][0];
|
|
||||||
break;
|
|
||||||
case QRErrorCorrectLevel.M :
|
|
||||||
nLimit = QRCodeLimitLength[i][1];
|
|
||||||
break;
|
|
||||||
case QRErrorCorrectLevel.Q :
|
|
||||||
nLimit = QRCodeLimitLength[i][2];
|
|
||||||
break;
|
|
||||||
case QRErrorCorrectLevel.H :
|
|
||||||
nLimit = QRCodeLimitLength[i][3];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length <= nLimit) {
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
nType++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nType > QRCodeLimitLength.length) {
|
|
||||||
throw new Error("Too long data");
|
|
||||||
}
|
|
||||||
|
|
||||||
return nType;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getUTF8Length(sText) {
|
|
||||||
var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a');
|
|
||||||
return replacedText.length + (replacedText.length != sText ? 3 : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class QRCode
|
|
||||||
* @constructor
|
|
||||||
* @example
|
|
||||||
* new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie");
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* var oQRCode = new QRCode("test", {
|
|
||||||
* text : "http://naver.com",
|
|
||||||
* width : 128,
|
|
||||||
* height : 128
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* oQRCode.clear(); // Clear the QRCode.
|
|
||||||
* oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode.
|
|
||||||
*
|
|
||||||
* @param {HTMLElement|String} el target element or 'id' attribute of element.
|
|
||||||
* @param {Object|String} vOption
|
|
||||||
* @param {String} vOption.text QRCode link data
|
|
||||||
* @param {Number} [vOption.width=256]
|
|
||||||
* @param {Number} [vOption.height=256]
|
|
||||||
* @param {String} [vOption.colorDark="#000000"]
|
|
||||||
* @param {String} [vOption.colorLight="#ffffff"]
|
|
||||||
* @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H]
|
|
||||||
*/
|
|
||||||
QRCode = function (el, vOption) {
|
|
||||||
this._htOption = {
|
|
||||||
width : 256,
|
|
||||||
height : 256,
|
|
||||||
typeNumber : 4,
|
|
||||||
colorDark : "#000000",
|
|
||||||
colorLight : "#ffffff",
|
|
||||||
correctLevel : QRErrorCorrectLevel.H
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof vOption === 'string') {
|
|
||||||
vOption = {
|
|
||||||
text : vOption
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overwrites options
|
|
||||||
if (vOption) {
|
|
||||||
for (var i in vOption) {
|
|
||||||
this._htOption[i] = vOption[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof el == "string") {
|
|
||||||
el = document.getElementById(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._android = _getAndroid();
|
|
||||||
this._el = el;
|
|
||||||
this._oQRCode = null;
|
|
||||||
this._oDrawing = new Drawing(this._el, this._htOption);
|
|
||||||
|
|
||||||
if (this._htOption.text) {
|
|
||||||
this.makeCode(this._htOption.text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make the QRCode
|
|
||||||
*
|
|
||||||
* @param {String} sText link data
|
|
||||||
*/
|
|
||||||
QRCode.prototype.makeCode = function (sText) {
|
|
||||||
this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel);
|
|
||||||
this._oQRCode.addData(sText);
|
|
||||||
this._oQRCode.make();
|
|
||||||
this._el.title = sText;
|
|
||||||
this._oDrawing.draw(this._oQRCode);
|
|
||||||
this.makeImage();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make the Image from Canvas element
|
|
||||||
* - It occurs automatically
|
|
||||||
* - Android below 3 doesn't support Data-URI spec.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
QRCode.prototype.makeImage = function () {
|
|
||||||
if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) {
|
|
||||||
this._oDrawing.makeImage();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear the QRCode
|
|
||||||
*/
|
|
||||||
QRCode.prototype.clear = function () {
|
|
||||||
this._oDrawing.clear();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name QRCode.CorrectLevel
|
|
||||||
*/
|
|
||||||
QRCode.CorrectLevel = QRErrorCorrectLevel;
|
|
||||||
})();
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,47 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qrious",
|
|
||||||
"version": "2.0.2",
|
|
||||||
"description": "Library for QR code generation using canvas",
|
|
||||||
"homepage": "https://github.com/neocotic/qrious",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Alasdair Mercer",
|
|
||||||
"email": "mercer.alasdair@gmail.com",
|
|
||||||
"homepage": "http://neocotic.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "GPL-3.0",
|
|
||||||
"keywords": [
|
|
||||||
"qr",
|
|
||||||
"code",
|
|
||||||
"encode",
|
|
||||||
"canvas",
|
|
||||||
"image"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/neocotic/qrious.git"
|
|
||||||
},
|
|
||||||
"main": "dist/umd/qrious.js",
|
|
||||||
"ignore": [
|
|
||||||
"src/",
|
|
||||||
".*",
|
|
||||||
"AUTHORS.md",
|
|
||||||
"CHANGES.md",
|
|
||||||
"CONTRIBUTING.md",
|
|
||||||
"demo.html",
|
|
||||||
"Gruntfile.js",
|
|
||||||
"package.json",
|
|
||||||
"README.md"
|
|
||||||
],
|
|
||||||
"_release": "2.0.2",
|
|
||||||
"_resolution": {
|
|
||||||
"type": "version",
|
|
||||||
"tag": "2.0.2",
|
|
||||||
"commit": "1ffd092e97ab3ba212fad21ab865eb1754155296"
|
|
||||||
},
|
|
||||||
"_source": "https://github.com/neocotic/qrious.git",
|
|
||||||
"_target": "^2.0.2",
|
|
||||||
"_originalSource": "qrious",
|
|
||||||
"_direct": true
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
QRious
|
|
||||||
Copyright (C) 2016 Alasdair Mercer
|
|
||||||
Copyright (C) 2010 Tom Zerucha
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qrious",
|
|
||||||
"version": "2.0.2",
|
|
||||||
"description": "Library for QR code generation using canvas",
|
|
||||||
"homepage": "https://github.com/neocotic/qrious",
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Alasdair Mercer",
|
|
||||||
"email": "mercer.alasdair@gmail.com",
|
|
||||||
"homepage": "http://neocotic.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "GPL-3.0",
|
|
||||||
"keywords": [
|
|
||||||
"qr",
|
|
||||||
"code",
|
|
||||||
"encode",
|
|
||||||
"canvas",
|
|
||||||
"image"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/neocotic/qrious.git"
|
|
||||||
},
|
|
||||||
"main": "dist/umd/qrious.js",
|
|
||||||
"ignore": [
|
|
||||||
"src/",
|
|
||||||
".*",
|
|
||||||
"AUTHORS.md",
|
|
||||||
"CHANGES.md",
|
|
||||||
"CONTRIBUTING.md",
|
|
||||||
"demo.html",
|
|
||||||
"Gruntfile.js",
|
|
||||||
"package.json",
|
|
||||||
"README.md"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qrjs",
|
|
||||||
"main": "qr.js",
|
|
||||||
"version": "0.1.2",
|
|
||||||
"homepage": "https://github.com/educastellano/qr.js",
|
|
||||||
"authors": [
|
|
||||||
"Kang Seonghoon <kang.seonghoon@mearie.org>",
|
|
||||||
"Eduard Castellano <educastellano08@gmail.com>"
|
|
||||||
],
|
|
||||||
"description": "QR code generator in Javascript",
|
|
||||||
"moduleType": [
|
|
||||||
"globals"
|
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
"qr",
|
|
||||||
"qr.js",
|
|
||||||
"qrcode"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"bower_components",
|
|
||||||
"test",
|
|
||||||
"tests"
|
|
||||||
],
|
|
||||||
"_release": "0.1.2",
|
|
||||||
"_resolution": {
|
|
||||||
"type": "version",
|
|
||||||
"tag": "v0.1.2",
|
|
||||||
"commit": "dbfa732cb309195a51a656b021f309d354154e04"
|
|
||||||
},
|
|
||||||
"_source": "https://github.com/educastellano/qr.js.git",
|
|
||||||
"_target": "~0.1.2",
|
|
||||||
"_originalSource": "qrjs"
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# qr.js: QR code generator in pure Javascript (2011)
|
|
||||||
|
|
||||||
This is a fairly standalone script for producing QR code on the fly.
|
|
||||||
Originally developed for my own interest, the code is fully commented and well-structured.
|
|
||||||
The code is in the public domain (or to be exact, [Creative Commons Zero](https://creativecommons.org/publicdomain/zero/1.0/)),
|
|
||||||
and you can use it for absolutely any purpose.
|
|
||||||
|
|
||||||
See also a [node.js module based on qr.js](https://github.com/shesek/qruri), packaged by Nadav Ivgi.
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qrjs",
|
|
||||||
"main": "qr.js",
|
|
||||||
"version": "0.1.2",
|
|
||||||
"homepage": "https://github.com/educastellano/qr.js",
|
|
||||||
"authors": [
|
|
||||||
"Kang Seonghoon <kang.seonghoon@mearie.org>",
|
|
||||||
"Eduard Castellano <educastellano08@gmail.com>"
|
|
||||||
],
|
|
||||||
"description": "QR code generator in Javascript",
|
|
||||||
"moduleType": [
|
|
||||||
"globals"
|
|
||||||
],
|
|
||||||
"keywords": [
|
|
||||||
"qr",
|
|
||||||
"qr.js",
|
|
||||||
"qrcode"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"ignore": [
|
|
||||||
"**/.*",
|
|
||||||
"node_modules",
|
|
||||||
"bower_components",
|
|
||||||
"test",
|
|
||||||
"tests"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
{
|
|
||||||
"name": "qrjs",
|
|
||||||
"version": "0.1.1",
|
|
||||||
"description": "QR code generator in Javascript",
|
|
||||||
"main": "qr.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/educastellano/qr.js.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"qr",
|
|
||||||
"qr.js",
|
|
||||||
"qrcode"
|
|
||||||
],
|
|
||||||
"author": "Kang Seonghoon <kang.seonghoon@mearie.org>",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/educastellano/qr.js/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/educastellano/qr.js"
|
|
||||||
}
|
|
||||||
804
lib/admin/public/bower_components/qrjs/qr.js
vendored
804
lib/admin/public/bower_components/qrjs/qr.js
vendored
|
|
@ -1,804 +0,0 @@
|
||||||
/* qr.js -- QR code generator in Javascript (revision 2011-01-19)
|
|
||||||
* Written by Kang Seonghoon <public+qrjs@mearie.org>.
|
|
||||||
*
|
|
||||||
* This source code is in the public domain; if your jurisdiction does not
|
|
||||||
* recognize the public domain the terms of Creative Commons CC0 license
|
|
||||||
* apply. In the other words, you can always do what you want.
|
|
||||||
*/
|
|
||||||
(function(root, name, definition) {
|
|
||||||
if (typeof define === 'function' && define.amd) {
|
|
||||||
define([], definition);
|
|
||||||
} else if (typeof module === 'object' && module.exports) {
|
|
||||||
module.exports = definition();
|
|
||||||
} else {
|
|
||||||
root[name] = definition();
|
|
||||||
}
|
|
||||||
})(this, 'QRCode', function() {
|
|
||||||
/* Quick overview: QR code composed of 2D array of modules (a rectangular
|
|
||||||
* area that conveys one bit of information); some modules are fixed to help
|
|
||||||
* the recognition of the code, and remaining data modules are further divided
|
|
||||||
* into 8-bit code words which are augumented by Reed-Solomon error correcting
|
|
||||||
* codes (ECC). There could be multiple ECCs, in the case the code is so large
|
|
||||||
* that it is helpful to split the raw data into several chunks.
|
|
||||||
*
|
|
||||||
* The number of modules is determined by the code's "version", ranging from 1
|
|
||||||
* (21x21) to 40 (177x177). How many ECC bits are used is determined by the
|
|
||||||
* ECC level (L/M/Q/H). The number and size (and thus the order of generator
|
|
||||||
* polynomial) of ECCs depend to the version and ECC level.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// per-version information (cf. JIS X 0510:2004 pp. 30--36, 71)
|
|
||||||
//
|
|
||||||
// [0]: the degree of generator polynomial by ECC levels
|
|
||||||
// [1]: # of code blocks by ECC levels
|
|
||||||
// [2]: left-top positions of alignment patterns
|
|
||||||
//
|
|
||||||
// the number in this table (in particular, [0]) does not exactly match with
|
|
||||||
// the numbers in the specficiation. see augumenteccs below for the reason.
|
|
||||||
var VERSIONS = [
|
|
||||||
null,
|
|
||||||
[[10, 7,17,13], [ 1, 1, 1, 1], []],
|
|
||||||
[[16,10,28,22], [ 1, 1, 1, 1], [4,16]],
|
|
||||||
[[26,15,22,18], [ 1, 1, 2, 2], [4,20]],
|
|
||||||
[[18,20,16,26], [ 2, 1, 4, 2], [4,24]],
|
|
||||||
[[24,26,22,18], [ 2, 1, 4, 4], [4,28]],
|
|
||||||
[[16,18,28,24], [ 4, 2, 4, 4], [4,32]],
|
|
||||||
[[18,20,26,18], [ 4, 2, 5, 6], [4,20,36]],
|
|
||||||
[[22,24,26,22], [ 4, 2, 6, 6], [4,22,40]],
|
|
||||||
[[22,30,24,20], [ 5, 2, 8, 8], [4,24,44]],
|
|
||||||
[[26,18,28,24], [ 5, 4, 8, 8], [4,26,48]],
|
|
||||||
[[30,20,24,28], [ 5, 4,11, 8], [4,28,52]],
|
|
||||||
[[22,24,28,26], [ 8, 4,11,10], [4,30,56]],
|
|
||||||
[[22,26,22,24], [ 9, 4,16,12], [4,32,60]],
|
|
||||||
[[24,30,24,20], [ 9, 4,16,16], [4,24,44,64]],
|
|
||||||
[[24,22,24,30], [10, 6,18,12], [4,24,46,68]],
|
|
||||||
[[28,24,30,24], [10, 6,16,17], [4,24,48,72]],
|
|
||||||
[[28,28,28,28], [11, 6,19,16], [4,28,52,76]],
|
|
||||||
[[26,30,28,28], [13, 6,21,18], [4,28,54,80]],
|
|
||||||
[[26,28,26,26], [14, 7,25,21], [4,28,56,84]],
|
|
||||||
[[26,28,28,30], [16, 8,25,20], [4,32,60,88]],
|
|
||||||
[[26,28,30,28], [17, 8,25,23], [4,26,48,70,92]],
|
|
||||||
[[28,28,24,30], [17, 9,34,23], [4,24,48,72,96]],
|
|
||||||
[[28,30,30,30], [18, 9,30,25], [4,28,52,76,100]],
|
|
||||||
[[28,30,30,30], [20,10,32,27], [4,26,52,78,104]],
|
|
||||||
[[28,26,30,30], [21,12,35,29], [4,30,56,82,108]],
|
|
||||||
[[28,28,30,28], [23,12,37,34], [4,28,56,84,112]],
|
|
||||||
[[28,30,30,30], [25,12,40,34], [4,32,60,88,116]],
|
|
||||||
[[28,30,30,30], [26,13,42,35], [4,24,48,72,96,120]],
|
|
||||||
[[28,30,30,30], [28,14,45,38], [4,28,52,76,100,124]],
|
|
||||||
[[28,30,30,30], [29,15,48,40], [4,24,50,76,102,128]],
|
|
||||||
[[28,30,30,30], [31,16,51,43], [4,28,54,80,106,132]],
|
|
||||||
[[28,30,30,30], [33,17,54,45], [4,32,58,84,110,136]],
|
|
||||||
[[28,30,30,30], [35,18,57,48], [4,28,56,84,112,140]],
|
|
||||||
[[28,30,30,30], [37,19,60,51], [4,32,60,88,116,144]],
|
|
||||||
[[28,30,30,30], [38,19,63,53], [4,28,52,76,100,124,148]],
|
|
||||||
[[28,30,30,30], [40,20,66,56], [4,22,48,74,100,126,152]],
|
|
||||||
[[28,30,30,30], [43,21,70,59], [4,26,52,78,104,130,156]],
|
|
||||||
[[28,30,30,30], [45,22,74,62], [4,30,56,82,108,134,160]],
|
|
||||||
[[28,30,30,30], [47,24,77,65], [4,24,52,80,108,136,164]],
|
|
||||||
[[28,30,30,30], [49,25,81,68], [4,28,56,84,112,140,168]]];
|
|
||||||
|
|
||||||
// mode constants (cf. Table 2 in JIS X 0510:2004 p. 16)
|
|
||||||
var MODE_TERMINATOR = 0;
|
|
||||||
var MODE_NUMERIC = 1, MODE_ALPHANUMERIC = 2, MODE_OCTET = 4, MODE_KANJI = 8;
|
|
||||||
|
|
||||||
// validation regexps
|
|
||||||
var NUMERIC_REGEXP = /^\d*$/;
|
|
||||||
var ALPHANUMERIC_REGEXP = /^[A-Za-z0-9 $%*+\-./:]*$/;
|
|
||||||
var ALPHANUMERIC_OUT_REGEXP = /^[A-Z0-9 $%*+\-./:]*$/;
|
|
||||||
|
|
||||||
// ECC levels (cf. Table 22 in JIS X 0510:2004 p. 45)
|
|
||||||
var ECCLEVEL_L = 1, ECCLEVEL_M = 0, ECCLEVEL_Q = 3, ECCLEVEL_H = 2;
|
|
||||||
|
|
||||||
// GF(2^8)-to-integer mapping with a reducing polynomial x^8+x^4+x^3+x^2+1
|
|
||||||
// invariant: GF256_MAP[GF256_INVMAP[i]] == i for all i in [1,256)
|
|
||||||
var GF256_MAP = [], GF256_INVMAP = [-1];
|
|
||||||
for (var i = 0, v = 1; i < 255; ++i) {
|
|
||||||
GF256_MAP.push(v);
|
|
||||||
GF256_INVMAP[v] = i;
|
|
||||||
v = (v * 2) ^ (v >= 128 ? 0x11d : 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
// generator polynomials up to degree 30
|
|
||||||
// (should match with polynomials in JIS X 0510:2004 Appendix A)
|
|
||||||
//
|
|
||||||
// generator polynomial of degree K is product of (x-\alpha^0), (x-\alpha^1),
|
|
||||||
// ..., (x-\alpha^(K-1)). by convention, we omit the K-th coefficient (always 1)
|
|
||||||
// from the result; also other coefficients are written in terms of the exponent
|
|
||||||
// to \alpha to avoid the redundant calculation. (see also calculateecc below.)
|
|
||||||
var GF256_GENPOLY = [[]];
|
|
||||||
for (var i = 0; i < 30; ++i) {
|
|
||||||
var prevpoly = GF256_GENPOLY[i], poly = [];
|
|
||||||
for (var j = 0; j <= i; ++j) {
|
|
||||||
var a = (j < i ? GF256_MAP[prevpoly[j]] : 0);
|
|
||||||
var b = GF256_MAP[(i + (prevpoly[j-1] || 0)) % 255];
|
|
||||||
poly.push(GF256_INVMAP[a ^ b]);
|
|
||||||
}
|
|
||||||
GF256_GENPOLY.push(poly);
|
|
||||||
}
|
|
||||||
|
|
||||||
// alphanumeric character mapping (cf. Table 5 in JIS X 0510:2004 p. 19)
|
|
||||||
var ALPHANUMERIC_MAP = {};
|
|
||||||
for (var i = 0; i < 45; ++i) {
|
|
||||||
ALPHANUMERIC_MAP['0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'.charAt(i)] = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mask functions in terms of row # and column #
|
|
||||||
// (cf. Table 20 in JIS X 0510:2004 p. 42)
|
|
||||||
var MASKFUNCS = [
|
|
||||||
function(i,j) { return (i+j) % 2 == 0; },
|
|
||||||
function(i,j) { return i % 2 == 0; },
|
|
||||||
function(i,j) { return j % 3 == 0; },
|
|
||||||
function(i,j) { return (i+j) % 3 == 0; },
|
|
||||||
function(i,j) { return (((i/2)|0) + ((j/3)|0)) % 2 == 0; },
|
|
||||||
function(i,j) { return (i*j) % 2 + (i*j) % 3 == 0; },
|
|
||||||
function(i,j) { return ((i*j) % 2 + (i*j) % 3) % 2 == 0; },
|
|
||||||
function(i,j) { return ((i+j) % 2 + (i*j) % 3) % 2 == 0; }];
|
|
||||||
|
|
||||||
// returns true when the version information has to be embeded.
|
|
||||||
var needsverinfo = function(ver) { return ver > 6; };
|
|
||||||
|
|
||||||
// returns the size of entire QR code for given version.
|
|
||||||
var getsizebyver = function(ver) { return 4 * ver + 17; };
|
|
||||||
|
|
||||||
// returns the number of bits available for code words in this version.
|
|
||||||
var nfullbits = function(ver) {
|
|
||||||
/*
|
|
||||||
* |<--------------- n --------------->|
|
|
||||||
* | |<----- n-17 ---->| |
|
|
||||||
* +-------+ ///+-------+ ----
|
|
||||||
* | | ///| | ^
|
|
||||||
* | 9x9 | @@@@@ ///| 9x8 | |
|
|
||||||
* | | # # # @5x5@ # # # | | |
|
|
||||||
* +-------+ @@@@@ +-------+ |
|
|
||||||
* # ---|
|
|
||||||
* ^ |
|
|
||||||
* # |
|
|
||||||
* @@@@@ @@@@@ @@@@@ | n
|
|
||||||
* @5x5@ @5x5@ @5x5@ n-17
|
|
||||||
* @@@@@ @@@@@ @@@@@ | |
|
|
||||||
* # | |
|
|
||||||
* ////// v |
|
|
||||||
* //////# ---|
|
|
||||||
* +-------+ @@@@@ @@@@@ |
|
|
||||||
* | | @5x5@ @5x5@ |
|
|
||||||
* | 8x9 | @@@@@ @@@@@ |
|
|
||||||
* | | v
|
|
||||||
* +-------+ ----
|
|
||||||
*
|
|
||||||
* when the entire code has n^2 modules and there are m^2-3 alignment
|
|
||||||
* patterns, we have:
|
|
||||||
* - 225 (= 9x9 + 9x8 + 8x9) modules for finder patterns and
|
|
||||||
* format information;
|
|
||||||
* - 2n-34 (= 2(n-17)) modules for timing patterns;
|
|
||||||
* - 36 (= 3x6 + 6x3) modules for version information, if any;
|
|
||||||
* - 25m^2-75 (= (m^2-3)(5x5)) modules for alignment patterns
|
|
||||||
* if any, but 10m-20 (= 2(m-2)x5) of them overlaps with
|
|
||||||
* timing patterns.
|
|
||||||
*/
|
|
||||||
var v = VERSIONS[ver];
|
|
||||||
var nbits = 16*ver*ver + 128*ver + 64; // finder, timing and format info.
|
|
||||||
if (needsverinfo(ver)) nbits -= 36; // version information
|
|
||||||
if (v[2].length) { // alignment patterns
|
|
||||||
nbits -= 25 * v[2].length * v[2].length - 10 * v[2].length - 55;
|
|
||||||
}
|
|
||||||
return nbits;
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns the number of bits available for data portions (i.e. excludes ECC
|
|
||||||
// bits but includes mode and length bits) in this version and ECC level.
|
|
||||||
var ndatabits = function(ver, ecclevel) {
|
|
||||||
var nbits = nfullbits(ver) & ~7; // no sub-octet code words
|
|
||||||
var v = VERSIONS[ver];
|
|
||||||
nbits -= 8 * v[0][ecclevel] * v[1][ecclevel]; // ecc bits
|
|
||||||
return nbits;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns the number of bits required for the length of data.
|
|
||||||
// (cf. Table 3 in JIS X 0510:2004 p. 16)
|
|
||||||
var ndatalenbits = function(ver, mode) {
|
|
||||||
switch (mode) {
|
|
||||||
case MODE_NUMERIC: return (ver < 10 ? 10 : ver < 27 ? 12 : 14);
|
|
||||||
case MODE_ALPHANUMERIC: return (ver < 10 ? 9 : ver < 27 ? 11 : 13);
|
|
||||||
case MODE_OCTET: return (ver < 10 ? 8 : 16);
|
|
||||||
case MODE_KANJI: return (ver < 10 ? 8 : ver < 27 ? 10 : 12);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns the maximum length of data possible in given configuration.
|
|
||||||
var getmaxdatalen = function(ver, mode, ecclevel) {
|
|
||||||
var nbits = ndatabits(ver, ecclevel) - 4 - ndatalenbits(ver, mode); // 4 for mode bits
|
|
||||||
switch (mode) {
|
|
||||||
case MODE_NUMERIC:
|
|
||||||
return ((nbits/10) | 0) * 3 + (nbits%10 < 4 ? 0 : nbits%10 < 7 ? 1 : 2);
|
|
||||||
case MODE_ALPHANUMERIC:
|
|
||||||
return ((nbits/11) | 0) * 2 + (nbits%11 < 6 ? 0 : 1);
|
|
||||||
case MODE_OCTET:
|
|
||||||
return (nbits/8) | 0;
|
|
||||||
case MODE_KANJI:
|
|
||||||
return (nbits/13) | 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// checks if the given data can be encoded in given mode, and returns
|
|
||||||
// the converted data for the further processing if possible. otherwise
|
|
||||||
// returns null.
|
|
||||||
//
|
|
||||||
// this function does not check the length of data; it is a duty of
|
|
||||||
// encode function below (as it depends on the version and ECC level too).
|
|
||||||
var validatedata = function(mode, data) {
|
|
||||||
switch (mode) {
|
|
||||||
case MODE_NUMERIC:
|
|
||||||
if (!data.match(NUMERIC_REGEXP)) return null;
|
|
||||||
return data;
|
|
||||||
|
|
||||||
case MODE_ALPHANUMERIC:
|
|
||||||
if (!data.match(ALPHANUMERIC_REGEXP)) return null;
|
|
||||||
return data.toUpperCase();
|
|
||||||
|
|
||||||
case MODE_OCTET:
|
|
||||||
if (typeof data === 'string') { // encode as utf-8 string
|
|
||||||
var newdata = [];
|
|
||||||
for (var i = 0; i < data.length; ++i) {
|
|
||||||
var ch = data.charCodeAt(i);
|
|
||||||
if (ch < 0x80) {
|
|
||||||
newdata.push(ch);
|
|
||||||
} else if (ch < 0x800) {
|
|
||||||
newdata.push(0xc0 | (ch >> 6),
|
|
||||||
0x80 | (ch & 0x3f));
|
|
||||||
} else if (ch < 0x10000) {
|
|
||||||
newdata.push(0xe0 | (ch >> 12),
|
|
||||||
0x80 | ((ch >> 6) & 0x3f),
|
|
||||||
0x80 | (ch & 0x3f));
|
|
||||||
} else {
|
|
||||||
newdata.push(0xf0 | (ch >> 18),
|
|
||||||
0x80 | ((ch >> 12) & 0x3f),
|
|
||||||
0x80 | ((ch >> 6) & 0x3f),
|
|
||||||
0x80 | (ch & 0x3f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newdata;
|
|
||||||
} else {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns the code words (sans ECC bits) for given data and configurations.
|
|
||||||
// requires data to be preprocessed by validatedata. no length check is
|
|
||||||
// performed, and everything has to be checked before calling this function.
|
|
||||||
var encode = function(ver, mode, data, maxbuflen) {
|
|
||||||
var buf = [];
|
|
||||||
var bits = 0, remaining = 8;
|
|
||||||
var datalen = data.length;
|
|
||||||
|
|
||||||
// this function is intentionally no-op when n=0.
|
|
||||||
var pack = function(x, n) {
|
|
||||||
if (n >= remaining) {
|
|
||||||
buf.push(bits | (x >> (n -= remaining)));
|
|
||||||
while (n >= 8) buf.push((x >> (n -= 8)) & 255);
|
|
||||||
bits = 0;
|
|
||||||
remaining = 8;
|
|
||||||
}
|
|
||||||
if (n > 0) bits |= (x & ((1 << n) - 1)) << (remaining -= n);
|
|
||||||
};
|
|
||||||
|
|
||||||
var nlenbits = ndatalenbits(ver, mode);
|
|
||||||
pack(mode, 4);
|
|
||||||
pack(datalen, nlenbits);
|
|
||||||
|
|
||||||
switch (mode) {
|
|
||||||
case MODE_NUMERIC:
|
|
||||||
for (var i = 2; i < datalen; i += 3) {
|
|
||||||
pack(parseInt(data.substring(i-2,i+1), 10), 10);
|
|
||||||
}
|
|
||||||
pack(parseInt(data.substring(i-2), 10), [0,4,7][datalen%3]);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MODE_ALPHANUMERIC:
|
|
||||||
for (var i = 1; i < datalen; i += 2) {
|
|
||||||
pack(ALPHANUMERIC_MAP[data.charAt(i-1)] * 45 +
|
|
||||||
ALPHANUMERIC_MAP[data.charAt(i)], 11);
|
|
||||||
}
|
|
||||||
if (datalen % 2 == 1) {
|
|
||||||
pack(ALPHANUMERIC_MAP[data.charAt(i-1)], 6);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MODE_OCTET:
|
|
||||||
for (var i = 0; i < datalen; ++i) {
|
|
||||||
pack(data[i], 8);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
// final bits. it is possible that adding terminator causes the buffer
|
|
||||||
// to overflow, but then the buffer truncated to the maximum size will
|
|
||||||
// be valid as the truncated terminator mode bits and padding is
|
|
||||||
// identical in appearance (cf. JIS X 0510:2004 sec 8.4.8).
|
|
||||||
pack(MODE_TERMINATOR, 4);
|
|
||||||
if (remaining < 8) buf.push(bits);
|
|
||||||
|
|
||||||
// the padding to fill up the remaining space. we should not add any
|
|
||||||
// words when the overflow already occurred.
|
|
||||||
while (buf.length + 1 < maxbuflen) buf.push(0xec, 0x11);
|
|
||||||
if (buf.length < maxbuflen) buf.push(0xec);
|
|
||||||
return buf;
|
|
||||||
};
|
|
||||||
|
|
||||||
// calculates ECC code words for given code words and generator polynomial.
|
|
||||||
//
|
|
||||||
// this is quite similar to CRC calculation as both Reed-Solomon and CRC use
|
|
||||||
// the certain kind of cyclic codes, which is effectively the division of
|
|
||||||
// zero-augumented polynomial by the generator polynomial. the only difference
|
|
||||||
// is that Reed-Solomon uses GF(2^8), instead of CRC's GF(2), and Reed-Solomon
|
|
||||||
// uses the different generator polynomial than CRC's.
|
|
||||||
var calculateecc = function(poly, genpoly) {
|
|
||||||
var modulus = poly.slice(0);
|
|
||||||
var polylen = poly.length, genpolylen = genpoly.length;
|
|
||||||
for (var i = 0; i < genpolylen; ++i) modulus.push(0);
|
|
||||||
for (var i = 0; i < polylen; ) {
|
|
||||||
var quotient = GF256_INVMAP[modulus[i++]];
|
|
||||||
if (quotient >= 0) {
|
|
||||||
for (var j = 0; j < genpolylen; ++j) {
|
|
||||||
modulus[i+j] ^= GF256_MAP[(quotient + genpoly[j]) % 255];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modulus.slice(polylen);
|
|
||||||
};
|
|
||||||
|
|
||||||
// auguments ECC code words to given code words. the resulting words are
|
|
||||||
// ready to be encoded in the matrix.
|
|
||||||
//
|
|
||||||
// the much of actual augumenting procedure follows JIS X 0510:2004 sec 8.7.
|
|
||||||
// the code is simplified using the fact that the size of each code & ECC
|
|
||||||
// blocks is almost same; for example, when we have 4 blocks and 46 data words
|
|
||||||
// the number of code words in those blocks are 11, 11, 12, 12 respectively.
|
|
||||||
var augumenteccs = function(poly, nblocks, genpoly) {
|
|
||||||
var subsizes = [];
|
|
||||||
var subsize = (poly.length / nblocks) | 0, subsize0 = 0;
|
|
||||||
var pivot = nblocks - poly.length % nblocks;
|
|
||||||
for (var i = 0; i < pivot; ++i) {
|
|
||||||
subsizes.push(subsize0);
|
|
||||||
subsize0 += subsize;
|
|
||||||
}
|
|
||||||
for (var i = pivot; i < nblocks; ++i) {
|
|
||||||
subsizes.push(subsize0);
|
|
||||||
subsize0 += subsize+1;
|
|
||||||
}
|
|
||||||
subsizes.push(subsize0);
|
|
||||||
|
|
||||||
var eccs = [];
|
|
||||||
for (var i = 0; i < nblocks; ++i) {
|
|
||||||
eccs.push(calculateecc(poly.slice(subsizes[i], subsizes[i+1]), genpoly));
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = [];
|
|
||||||
var nitemsperblock = (poly.length / nblocks) | 0;
|
|
||||||
for (var i = 0; i < nitemsperblock; ++i) {
|
|
||||||
for (var j = 0; j < nblocks; ++j) {
|
|
||||||
result.push(poly[subsizes[j] + i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (var j = pivot; j < nblocks; ++j) {
|
|
||||||
result.push(poly[subsizes[j+1] - 1]);
|
|
||||||
}
|
|
||||||
for (var i = 0; i < genpoly.length; ++i) {
|
|
||||||
for (var j = 0; j < nblocks; ++j) {
|
|
||||||
result.push(eccs[j][i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// auguments BCH(p+q,q) code to the polynomial over GF(2), given the proper
|
|
||||||
// genpoly. the both input and output are in binary numbers, and unlike
|
|
||||||
// calculateecc genpoly should include the 1 bit for the highest degree.
|
|
||||||
//
|
|
||||||
// actual polynomials used for this procedure are as follows:
|
|
||||||
// - p=10, q=5, genpoly=x^10+x^8+x^5+x^4+x^2+x+1 (JIS X 0510:2004 Appendix C)
|
|
||||||
// - p=18, q=6, genpoly=x^12+x^11+x^10+x^9+x^8+x^5+x^2+1 (ibid. Appendix D)
|
|
||||||
var augumentbch = function(poly, p, genpoly, q) {
|
|
||||||
var modulus = poly << q;
|
|
||||||
for (var i = p - 1; i >= 0; --i) {
|
|
||||||
if ((modulus >> (q+i)) & 1) modulus ^= genpoly << i;
|
|
||||||
}
|
|
||||||
return (poly << q) | modulus;
|
|
||||||
};
|
|
||||||
|
|
||||||
// creates the base matrix for given version. it returns two matrices, one of
|
|
||||||
// them is the actual one and the another represents the "reserved" portion
|
|
||||||
// (e.g. finder and timing patterns) of the matrix.
|
|
||||||
//
|
|
||||||
// some entries in the matrix may be undefined, rather than 0 or 1. this is
|
|
||||||
// intentional (no initialization needed!), and putdata below will fill
|
|
||||||
// the remaining ones.
|
|
||||||
var makebasematrix = function(ver) {
|
|
||||||
var v = VERSIONS[ver], n = getsizebyver(ver);
|
|
||||||
var matrix = [], reserved = [];
|
|
||||||
for (var i = 0; i < n; ++i) {
|
|
||||||
matrix.push([]);
|
|
||||||
reserved.push([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var blit = function(y, x, h, w, bits) {
|
|
||||||
for (var i = 0; i < h; ++i) {
|
|
||||||
for (var j = 0; j < w; ++j) {
|
|
||||||
matrix[y+i][x+j] = (bits[i] >> j) & 1;
|
|
||||||
reserved[y+i][x+j] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// finder patterns and a part of timing patterns
|
|
||||||
// will also mark the format information area (not yet written) as reserved.
|
|
||||||
blit(0, 0, 9, 9, [0x7f, 0x41, 0x5d, 0x5d, 0x5d, 0x41, 0x17f, 0x00, 0x40]);
|
|
||||||
blit(n-8, 0, 8, 9, [0x100, 0x7f, 0x41, 0x5d, 0x5d, 0x5d, 0x41, 0x7f]);
|
|
||||||
blit(0, n-8, 9, 8, [0xfe, 0x82, 0xba, 0xba, 0xba, 0x82, 0xfe, 0x00, 0x00]);
|
|
||||||
|
|
||||||
// the rest of timing patterns
|
|
||||||
for (var i = 9; i < n-8; ++i) {
|
|
||||||
matrix[6][i] = matrix[i][6] = ~i & 1;
|
|
||||||
reserved[6][i] = reserved[i][6] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// alignment patterns
|
|
||||||
var aligns = v[2], m = aligns.length;
|
|
||||||
for (var i = 0; i < m; ++i) {
|
|
||||||
var minj = (i==0 || i==m-1 ? 1 : 0), maxj = (i==0 ? m-1 : m);
|
|
||||||
for (var j = minj; j < maxj; ++j) {
|
|
||||||
blit(aligns[i], aligns[j], 5, 5, [0x1f, 0x11, 0x15, 0x11, 0x1f]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// version information
|
|
||||||
if (needsverinfo(ver)) {
|
|
||||||
var code = augumentbch(ver, 6, 0x1f25, 12);
|
|
||||||
var k = 0;
|
|
||||||
for (var i = 0; i < 6; ++i) {
|
|
||||||
for (var j = 0; j < 3; ++j) {
|
|
||||||
matrix[i][(n-11)+j] = matrix[(n-11)+j][i] = (code >> k++) & 1;
|
|
||||||
reserved[i][(n-11)+j] = reserved[(n-11)+j][i] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {matrix: matrix, reserved: reserved};
|
|
||||||
};
|
|
||||||
|
|
||||||
// fills the data portion (i.e. unmarked in reserved) of the matrix with given
|
|
||||||
// code words. the size of code words should be no more than available bits,
|
|
||||||
// and remaining bits are padded to 0 (cf. JIS X 0510:2004 sec 8.7.3).
|
|
||||||
var putdata = function(matrix, reserved, buf) {
|
|
||||||
var n = matrix.length;
|
|
||||||
var k = 0, dir = -1;
|
|
||||||
for (var i = n-1; i >= 0; i -= 2) {
|
|
||||||
if (i == 6) --i; // skip the entire timing pattern column
|
|
||||||
var jj = (dir < 0 ? n-1 : 0);
|
|
||||||
for (var j = 0; j < n; ++j) {
|
|
||||||
for (var ii = i; ii > i-2; --ii) {
|
|
||||||
if (!reserved[jj][ii]) {
|
|
||||||
// may overflow, but (undefined >> x)
|
|
||||||
// is 0 so it will auto-pad to zero.
|
|
||||||
matrix[jj][ii] = (buf[k >> 3] >> (~k&7)) & 1;
|
|
||||||
++k;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jj += dir;
|
|
||||||
}
|
|
||||||
dir = -dir;
|
|
||||||
}
|
|
||||||
return matrix;
|
|
||||||
};
|
|
||||||
|
|
||||||
// XOR-masks the data portion of the matrix. repeating the call with the same
|
|
||||||
// arguments will revert the prior call (convenient in the matrix evaluation).
|
|
||||||
var maskdata = function(matrix, reserved, mask) {
|
|
||||||
var maskf = MASKFUNCS[mask];
|
|
||||||
var n = matrix.length;
|
|
||||||
for (var i = 0; i < n; ++i) {
|
|
||||||
for (var j = 0; j < n; ++j) {
|
|
||||||
if (!reserved[i][j]) matrix[i][j] ^= maskf(i,j);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matrix;
|
|
||||||
}
|
|
||||||
|
|
||||||
// puts the format information.
|
|
||||||
var putformatinfo = function(matrix, reserved, ecclevel, mask) {
|
|
||||||
var n = matrix.length;
|
|
||||||
var code = augumentbch((ecclevel << 3) | mask, 5, 0x537, 10) ^ 0x5412;
|
|
||||||
for (var i = 0; i < 15; ++i) {
|
|
||||||
var r = [0,1,2,3,4,5,7,8,n-7,n-6,n-5,n-4,n-3,n-2,n-1][i];
|
|
||||||
var c = [n-1,n-2,n-3,n-4,n-5,n-6,n-7,n-8,7,5,4,3,2,1,0][i];
|
|
||||||
matrix[r][8] = matrix[8][c] = (code >> i) & 1;
|
|
||||||
// we don't have to mark those bits reserved; always done
|
|
||||||
// in makebasematrix above.
|
|
||||||
}
|
|
||||||
return matrix;
|
|
||||||
};
|
|
||||||
|
|
||||||
// evaluates the resulting matrix and returns the score (lower is better).
|
|
||||||
// (cf. JIS X 0510:2004 sec 8.8.2)
|
|
||||||
//
|
|
||||||
// the evaluation procedure tries to avoid the problematic patterns naturally
|
|
||||||
// occuring from the original matrix. for example, it penaltizes the patterns
|
|
||||||
// which just look like the finder pattern which will confuse the decoder.
|
|
||||||
// we choose the mask which results in the lowest score among 8 possible ones.
|
|
||||||
//
|
|
||||||
// note: zxing seems to use the same procedure and in many cases its choice
|
|
||||||
// agrees to ours, but sometimes it does not. practically it doesn't matter.
|
|
||||||
var evaluatematrix = function(matrix) {
|
|
||||||
// N1+(k-5) points for each consecutive row of k same-colored modules,
|
|
||||||
// where k >= 5. no overlapping row counts.
|
|
||||||
var PENALTY_CONSECUTIVE = 3;
|
|
||||||
// N2 points for each 2x2 block of same-colored modules.
|
|
||||||
// overlapping block does count.
|
|
||||||
var PENALTY_TWOBYTWO = 3;
|
|
||||||
// N3 points for each pattern with >4W:1B:1W:3B:1W:1B or
|
|
||||||
// 1B:1W:3B:1W:1B:>4W, or their multiples (e.g. highly unlikely,
|
|
||||||
// but 13W:3B:3W:9B:3W:3B counts).
|
|
||||||
var PENALTY_FINDERLIKE = 40;
|
|
||||||
// N4*k points for every (5*k)% deviation from 50% black density.
|
|
||||||
// i.e. k=1 for 55~60% and 40~45%, k=2 for 60~65% and 35~40%, etc.
|
|
||||||
var PENALTY_DENSITY = 10;
|
|
||||||
|
|
||||||
var evaluategroup = function(groups) { // assumes [W,B,W,B,W,...,B,W]
|
|
||||||
var score = 0;
|
|
||||||
for (var i = 0; i < groups.length; ++i) {
|
|
||||||
if (groups[i] >= 5) score += PENALTY_CONSECUTIVE + (groups[i]-5);
|
|
||||||
}
|
|
||||||
for (var i = 5; i < groups.length; i += 2) {
|
|
||||||
var p = groups[i];
|
|
||||||
if (groups[i-1] == p && groups[i-2] == 3*p && groups[i-3] == p &&
|
|
||||||
groups[i-4] == p && (groups[i-5] >= 4*p || groups[i+1] >= 4*p)) {
|
|
||||||
// this part differs from zxing...
|
|
||||||
score += PENALTY_FINDERLIKE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return score;
|
|
||||||
};
|
|
||||||
|
|
||||||
var n = matrix.length;
|
|
||||||
var score = 0, nblacks = 0;
|
|
||||||
for (var i = 0; i < n; ++i) {
|
|
||||||
var row = matrix[i];
|
|
||||||
var groups;
|
|
||||||
|
|
||||||
// evaluate the current row
|
|
||||||
groups = [0]; // the first empty group of white
|
|
||||||
for (var j = 0; j < n; ) {
|
|
||||||
var k;
|
|
||||||
for (k = 0; j < n && row[j]; ++k) ++j;
|
|
||||||
groups.push(k);
|
|
||||||
for (k = 0; j < n && !row[j]; ++k) ++j;
|
|
||||||
groups.push(k);
|
|
||||||
}
|
|
||||||
score += evaluategroup(groups);
|
|
||||||
|
|
||||||
// evaluate the current column
|
|
||||||
groups = [0];
|
|
||||||
for (var j = 0; j < n; ) {
|
|
||||||
var k;
|
|
||||||
for (k = 0; j < n && matrix[j][i]; ++k) ++j;
|
|
||||||
groups.push(k);
|
|
||||||
for (k = 0; j < n && !matrix[j][i]; ++k) ++j;
|
|
||||||
groups.push(k);
|
|
||||||
}
|
|
||||||
score += evaluategroup(groups);
|
|
||||||
|
|
||||||
// check the 2x2 box and calculate the density
|
|
||||||
var nextrow = matrix[i+1] || [];
|
|
||||||
nblacks += row[0];
|
|
||||||
for (var j = 1; j < n; ++j) {
|
|
||||||
var p = row[j];
|
|
||||||
nblacks += p;
|
|
||||||
// at least comparison with next row should be strict...
|
|
||||||
if (row[j-1] == p && nextrow[j] === p && nextrow[j-1] === p) {
|
|
||||||
score += PENALTY_TWOBYTWO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
score += PENALTY_DENSITY * ((Math.abs(nblacks / n / n - 0.5) / 0.05) | 0);
|
|
||||||
return score;
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns the fully encoded QR code matrix which contains given data.
|
|
||||||
// it also chooses the best mask automatically when mask is -1.
|
|
||||||
var generate = function(data, ver, mode, ecclevel, mask) {
|
|
||||||
var v = VERSIONS[ver];
|
|
||||||
var buf = encode(ver, mode, data, ndatabits(ver, ecclevel) >> 3);
|
|
||||||
buf = augumenteccs(buf, v[1][ecclevel], GF256_GENPOLY[v[0][ecclevel]]);
|
|
||||||
|
|
||||||
var result = makebasematrix(ver);
|
|
||||||
var matrix = result.matrix, reserved = result.reserved;
|
|
||||||
putdata(matrix, reserved, buf);
|
|
||||||
|
|
||||||
if (mask < 0) {
|
|
||||||
// find the best mask
|
|
||||||
maskdata(matrix, reserved, 0);
|
|
||||||
putformatinfo(matrix, reserved, ecclevel, 0);
|
|
||||||
var bestmask = 0, bestscore = evaluatematrix(matrix);
|
|
||||||
maskdata(matrix, reserved, 0);
|
|
||||||
for (mask = 1; mask < 8; ++mask) {
|
|
||||||
maskdata(matrix, reserved, mask);
|
|
||||||
putformatinfo(matrix, reserved, ecclevel, mask);
|
|
||||||
var score = evaluatematrix(matrix);
|
|
||||||
if (bestscore > score) {
|
|
||||||
bestscore = score;
|
|
||||||
bestmask = mask;
|
|
||||||
}
|
|
||||||
maskdata(matrix, reserved, mask);
|
|
||||||
}
|
|
||||||
mask = bestmask;
|
|
||||||
}
|
|
||||||
|
|
||||||
maskdata(matrix, reserved, mask);
|
|
||||||
putformatinfo(matrix, reserved, ecclevel, mask);
|
|
||||||
return matrix;
|
|
||||||
};
|
|
||||||
|
|
||||||
// the public interface is trivial; the options available are as follows:
|
|
||||||
//
|
|
||||||
// - version: an integer in [1,40]. when omitted (or -1) the smallest possible
|
|
||||||
// version is chosen.
|
|
||||||
// - mode: one of 'numeric', 'alphanumeric', 'octet'. when omitted the smallest
|
|
||||||
// possible mode is chosen.
|
|
||||||
// - ecclevel: one of 'L', 'M', 'Q', 'H'. defaults to 'L'.
|
|
||||||
// - mask: an integer in [0,7]. when omitted (or -1) the best mask is chosen.
|
|
||||||
//
|
|
||||||
// for generate{HTML,PNG}:
|
|
||||||
//
|
|
||||||
// - modulesize: a number. this is a size of each modules in pixels, and
|
|
||||||
// defaults to 5px.
|
|
||||||
// - margin: a number. this is a size of margin in *modules*, and defaults to
|
|
||||||
// 4 (white modules). the specficiation mandates the margin no less than 4
|
|
||||||
// modules, so it is better not to alter this value unless you know what
|
|
||||||
// you're doing.
|
|
||||||
var QRCode = {
|
|
||||||
'generate': function(data, options) {
|
|
||||||
var MODES = {'numeric': MODE_NUMERIC, 'alphanumeric': MODE_ALPHANUMERIC,
|
|
||||||
'octet': MODE_OCTET};
|
|
||||||
var ECCLEVELS = {'L': ECCLEVEL_L, 'M': ECCLEVEL_M, 'Q': ECCLEVEL_Q,
|
|
||||||
'H': ECCLEVEL_H};
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
var ver = options.version || -1;
|
|
||||||
var ecclevel = ECCLEVELS[(options.ecclevel || 'L').toUpperCase()];
|
|
||||||
var mode = options.mode ? MODES[options.mode.toLowerCase()] : -1;
|
|
||||||
var mask = 'mask' in options ? options.mask : -1;
|
|
||||||
|
|
||||||
if (mode < 0) {
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
if (data.match(NUMERIC_REGEXP)) {
|
|
||||||
mode = MODE_NUMERIC;
|
|
||||||
} else if (data.match(ALPHANUMERIC_OUT_REGEXP)) {
|
|
||||||
// while encode supports case-insensitive
|
|
||||||
// encoding, we restrict the data to be
|
|
||||||
// uppercased when auto-selecting the mode.
|
|
||||||
mode = MODE_ALPHANUMERIC;
|
|
||||||
} else {
|
|
||||||
mode = MODE_OCTET;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
mode = MODE_OCTET;
|
|
||||||
}
|
|
||||||
} else if (!(mode == MODE_NUMERIC || mode == MODE_ALPHANUMERIC ||
|
|
||||||
mode == MODE_OCTET)) {
|
|
||||||
throw 'invalid or unsupported mode';
|
|
||||||
}
|
|
||||||
|
|
||||||
data = validatedata(mode, data);
|
|
||||||
if (data === null) throw 'invalid data format';
|
|
||||||
|
|
||||||
if (ecclevel < 0 || ecclevel > 3) throw 'invalid ECC level';
|
|
||||||
|
|
||||||
if (ver < 0) {
|
|
||||||
for (ver = 1; ver <= 40; ++ver) {
|
|
||||||
if (data.length <= getmaxdatalen(ver, mode, ecclevel)) break;
|
|
||||||
}
|
|
||||||
if (ver > 40) throw 'too large data';
|
|
||||||
} else if (ver < 1 || ver > 40) {
|
|
||||||
throw 'invalid version';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mask != -1 && (mask < 0 || mask > 8)) throw 'invalid mask';
|
|
||||||
|
|
||||||
return generate(data, ver, mode, ecclevel, mask);
|
|
||||||
},
|
|
||||||
|
|
||||||
'generateHTML': function(data, options) {
|
|
||||||
options = options || {};
|
|
||||||
var matrix = QRCode['generate'](data, options);
|
|
||||||
var modsize = Math.max(options.modulesize || 5, 0.5);
|
|
||||||
var margin = Math.max(options.margin !== null ? options.margin : 4, 0.0);
|
|
||||||
|
|
||||||
var e = document.createElement('div');
|
|
||||||
var n = matrix.length;
|
|
||||||
var html = ['<table border="0" cellspacing="0" cellpadding="0" style="border:' +
|
|
||||||
modsize*margin + 'px solid #fff;background:#fff">'];
|
|
||||||
for (var i = 0; i < n; ++i) {
|
|
||||||
html.push('<tr>');
|
|
||||||
for (var j = 0; j < n; ++j) {
|
|
||||||
html.push('<td style="width:' + modsize + 'px;height:' + modsize + 'px' +
|
|
||||||
(matrix[i][j] ? ';background:#000' : '') + '"></td>');
|
|
||||||
}
|
|
||||||
html.push('</tr>');
|
|
||||||
}
|
|
||||||
e.className = 'qrcode';
|
|
||||||
e.innerHTML = html.join('') + '</table>';
|
|
||||||
return e;
|
|
||||||
},
|
|
||||||
|
|
||||||
'generateSVG': function(data, options) {
|
|
||||||
options = options || {};
|
|
||||||
var matrix = QRCode['generate'](data, options);
|
|
||||||
var n = matrix.length;
|
|
||||||
var modsize = Math.max(options.modulesize || 5, 0.5);
|
|
||||||
var margin = Math.max(options.margin !== null ? options.margin : 4, 0.0);
|
|
||||||
var size = modsize * (n + 2 * margin);
|
|
||||||
|
|
||||||
var common = ' class= "fg"'+' width="'+modsize+'" height="'+modsize+'"/>';
|
|
||||||
|
|
||||||
var e = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
||||||
e.setAttribute('viewBox', '0 0 '+size+' '+size);
|
|
||||||
e.setAttribute('style', 'shape-rendering:crispEdges');
|
|
||||||
if (options.modulesize) {
|
|
||||||
e.setAttribute('width', size);
|
|
||||||
e.setAttribute('height', size);
|
|
||||||
}
|
|
||||||
|
|
||||||
var svg = [
|
|
||||||
'<style scoped>.bg{fill:#FFF}.fg{fill:#000}</style>',
|
|
||||||
'<rect class="bg" x="0" y="0"',
|
|
||||||
'width="'+size+'" height="'+size+'"/>',
|
|
||||||
];
|
|
||||||
|
|
||||||
var yo = margin * modsize;
|
|
||||||
for (var y = 0; y < n; ++y) {
|
|
||||||
var xo = margin * modsize;
|
|
||||||
for (var x = 0; x < n; ++x) {
|
|
||||||
if (matrix[y][x])
|
|
||||||
svg.push('<rect x="'+xo+'" y="'+yo+'"', common);
|
|
||||||
xo += modsize;
|
|
||||||
}
|
|
||||||
yo += modsize;
|
|
||||||
}
|
|
||||||
e.innerHTML = svg.join('');
|
|
||||||
return e;
|
|
||||||
},
|
|
||||||
|
|
||||||
'generatePNG': function(data, options) {
|
|
||||||
options = options || {};
|
|
||||||
var matrix = QRCode['generate'](data, options);
|
|
||||||
var modsize = Math.max(options.modulesize || 5, 0.5);
|
|
||||||
var margin = Math.max(options.margin !== null ? options.margin : 4, 0.0);
|
|
||||||
var n = matrix.length;
|
|
||||||
var size = modsize * (n + 2 * margin);
|
|
||||||
|
|
||||||
var canvas = document.createElement('canvas'), context;
|
|
||||||
canvas.width = canvas.height = size;
|
|
||||||
context = canvas.getContext('2d');
|
|
||||||
if (!context) throw 'canvas support is needed for PNG output';
|
|
||||||
|
|
||||||
context.fillStyle = '#fff';
|
|
||||||
context.fillRect(0, 0, size, size);
|
|
||||||
context.fillStyle = '#000';
|
|
||||||
for (var i = 0; i < n; ++i) {
|
|
||||||
for (var j = 0; j < n; ++j) {
|
|
||||||
if (matrix[i][j]) {
|
|
||||||
context.fillRect(modsize * (margin + j),
|
|
||||||
modsize * (margin + i),
|
|
||||||
modsize, modsize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//context.fillText('evaluation: ' + evaluatematrix(matrix), 10, 10);
|
|
||||||
return canvas.toDataURL();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return QRCode;
|
|
||||||
});
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"name": "webcomponentsjs",
|
|
||||||
"main": "webcomponents.js",
|
|
||||||
"version": "0.7.22",
|
|
||||||
"homepage": "http://webcomponents.org",
|
|
||||||
"authors": [
|
|
||||||
"The Polymer Authors"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/webcomponents/webcomponentsjs.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"webcomponents"
|
|
||||||
],
|
|
||||||
"license": "BSD",
|
|
||||||
"ignore": [],
|
|
||||||
"devDependencies": {
|
|
||||||
"web-component-tester": "^4.0.1"
|
|
||||||
},
|
|
||||||
"_release": "0.7.22",
|
|
||||||
"_resolution": {
|
|
||||||
"type": "version",
|
|
||||||
"tag": "v0.7.22",
|
|
||||||
"commit": "50f9751f8e638301603aebb33ba9f1e90d2b0d32"
|
|
||||||
},
|
|
||||||
"_source": "https://github.com/Polymer/webcomponentsjs.git",
|
|
||||||
"_target": "^0.7.22",
|
|
||||||
"_originalSource": "webcomponentsjs",
|
|
||||||
"_direct": true
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -1,350 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright (c) 2014 The Polymer Project Authors. All rights reserved.
|
|
||||||
* This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
|
|
||||||
* The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
|
|
||||||
* The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
|
|
||||||
* Code distributed by Google as part of the polymer project is also
|
|
||||||
* subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
|
|
||||||
*/
|
|
||||||
// @version 0.7.22
|
|
||||||
if (typeof WeakMap === "undefined") {
|
|
||||||
(function() {
|
|
||||||
var defineProperty = Object.defineProperty;
|
|
||||||
var counter = Date.now() % 1e9;
|
|
||||||
var WeakMap = function() {
|
|
||||||
this.name = "__st" + (Math.random() * 1e9 >>> 0) + (counter++ + "__");
|
|
||||||
};
|
|
||||||
WeakMap.prototype = {
|
|
||||||
set: function(key, value) {
|
|
||||||
var entry = key[this.name];
|
|
||||||
if (entry && entry[0] === key) entry[1] = value; else defineProperty(key, this.name, {
|
|
||||||
value: [ key, value ],
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
},
|
|
||||||
get: function(key) {
|
|
||||||
var entry;
|
|
||||||
return (entry = key[this.name]) && entry[0] === key ? entry[1] : undefined;
|
|
||||||
},
|
|
||||||
"delete": function(key) {
|
|
||||||
var entry = key[this.name];
|
|
||||||
if (!entry || entry[0] !== key) return false;
|
|
||||||
entry[0] = entry[1] = undefined;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
has: function(key) {
|
|
||||||
var entry = key[this.name];
|
|
||||||
if (!entry) return false;
|
|
||||||
return entry[0] === key;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
window.WeakMap = WeakMap;
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
(function(global) {
|
|
||||||
if (global.JsMutationObserver) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var registrationsTable = new WeakMap();
|
|
||||||
var setImmediate;
|
|
||||||
if (/Trident|Edge/.test(navigator.userAgent)) {
|
|
||||||
setImmediate = setTimeout;
|
|
||||||
} else if (window.setImmediate) {
|
|
||||||
setImmediate = window.setImmediate;
|
|
||||||
} else {
|
|
||||||
var setImmediateQueue = [];
|
|
||||||
var sentinel = String(Math.random());
|
|
||||||
window.addEventListener("message", function(e) {
|
|
||||||
if (e.data === sentinel) {
|
|
||||||
var queue = setImmediateQueue;
|
|
||||||
setImmediateQueue = [];
|
|
||||||
queue.forEach(function(func) {
|
|
||||||
func();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setImmediate = function(func) {
|
|
||||||
setImmediateQueue.push(func);
|
|
||||||
window.postMessage(sentinel, "*");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
var isScheduled = false;
|
|
||||||
var scheduledObservers = [];
|
|
||||||
function scheduleCallback(observer) {
|
|
||||||
scheduledObservers.push(observer);
|
|
||||||
if (!isScheduled) {
|
|
||||||
isScheduled = true;
|
|
||||||
setImmediate(dispatchCallbacks);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function wrapIfNeeded(node) {
|
|
||||||
return window.ShadowDOMPolyfill && window.ShadowDOMPolyfill.wrapIfNeeded(node) || node;
|
|
||||||
}
|
|
||||||
function dispatchCallbacks() {
|
|
||||||
isScheduled = false;
|
|
||||||
var observers = scheduledObservers;
|
|
||||||
scheduledObservers = [];
|
|
||||||
observers.sort(function(o1, o2) {
|
|
||||||
return o1.uid_ - o2.uid_;
|
|
||||||
});
|
|
||||||
var anyNonEmpty = false;
|
|
||||||
observers.forEach(function(observer) {
|
|
||||||
var queue = observer.takeRecords();
|
|
||||||
removeTransientObserversFor(observer);
|
|
||||||
if (queue.length) {
|
|
||||||
observer.callback_(queue, observer);
|
|
||||||
anyNonEmpty = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (anyNonEmpty) dispatchCallbacks();
|
|
||||||
}
|
|
||||||
function removeTransientObserversFor(observer) {
|
|
||||||
observer.nodes_.forEach(function(node) {
|
|
||||||
var registrations = registrationsTable.get(node);
|
|
||||||
if (!registrations) return;
|
|
||||||
registrations.forEach(function(registration) {
|
|
||||||
if (registration.observer === observer) registration.removeTransientObservers();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function forEachAncestorAndObserverEnqueueRecord(target, callback) {
|
|
||||||
for (var node = target; node; node = node.parentNode) {
|
|
||||||
var registrations = registrationsTable.get(node);
|
|
||||||
if (registrations) {
|
|
||||||
for (var j = 0; j < registrations.length; j++) {
|
|
||||||
var registration = registrations[j];
|
|
||||||
var options = registration.options;
|
|
||||||
if (node !== target && !options.subtree) continue;
|
|
||||||
var record = callback(options);
|
|
||||||
if (record) registration.enqueue(record);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var uidCounter = 0;
|
|
||||||
function JsMutationObserver(callback) {
|
|
||||||
this.callback_ = callback;
|
|
||||||
this.nodes_ = [];
|
|
||||||
this.records_ = [];
|
|
||||||
this.uid_ = ++uidCounter;
|
|
||||||
}
|
|
||||||
JsMutationObserver.prototype = {
|
|
||||||
observe: function(target, options) {
|
|
||||||
target = wrapIfNeeded(target);
|
|
||||||
if (!options.childList && !options.attributes && !options.characterData || options.attributeOldValue && !options.attributes || options.attributeFilter && options.attributeFilter.length && !options.attributes || options.characterDataOldValue && !options.characterData) {
|
|
||||||
throw new SyntaxError();
|
|
||||||
}
|
|
||||||
var registrations = registrationsTable.get(target);
|
|
||||||
if (!registrations) registrationsTable.set(target, registrations = []);
|
|
||||||
var registration;
|
|
||||||
for (var i = 0; i < registrations.length; i++) {
|
|
||||||
if (registrations[i].observer === this) {
|
|
||||||
registration = registrations[i];
|
|
||||||
registration.removeListeners();
|
|
||||||
registration.options = options;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!registration) {
|
|
||||||
registration = new Registration(this, target, options);
|
|
||||||
registrations.push(registration);
|
|
||||||
this.nodes_.push(target);
|
|
||||||
}
|
|
||||||
registration.addListeners();
|
|
||||||
},
|
|
||||||
disconnect: function() {
|
|
||||||
this.nodes_.forEach(function(node) {
|
|
||||||
var registrations = registrationsTable.get(node);
|
|
||||||
for (var i = 0; i < registrations.length; i++) {
|
|
||||||
var registration = registrations[i];
|
|
||||||
if (registration.observer === this) {
|
|
||||||
registration.removeListeners();
|
|
||||||
registrations.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
this.records_ = [];
|
|
||||||
},
|
|
||||||
takeRecords: function() {
|
|
||||||
var copyOfRecords = this.records_;
|
|
||||||
this.records_ = [];
|
|
||||||
return copyOfRecords;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
function MutationRecord(type, target) {
|
|
||||||
this.type = type;
|
|
||||||
this.target = target;
|
|
||||||
this.addedNodes = [];
|
|
||||||
this.removedNodes = [];
|
|
||||||
this.previousSibling = null;
|
|
||||||
this.nextSibling = null;
|
|
||||||
this.attributeName = null;
|
|
||||||
this.attributeNamespace = null;
|
|
||||||
this.oldValue = null;
|
|
||||||
}
|
|
||||||
function copyMutationRecord(original) {
|
|
||||||
var record = new MutationRecord(original.type, original.target);
|
|
||||||
record.addedNodes = original.addedNodes.slice();
|
|
||||||
record.removedNodes = original.removedNodes.slice();
|
|
||||||
record.previousSibling = original.previousSibling;
|
|
||||||
record.nextSibling = original.nextSibling;
|
|
||||||
record.attributeName = original.attributeName;
|
|
||||||
record.attributeNamespace = original.attributeNamespace;
|
|
||||||
record.oldValue = original.oldValue;
|
|
||||||
return record;
|
|
||||||
}
|
|
||||||
var currentRecord, recordWithOldValue;
|
|
||||||
function getRecord(type, target) {
|
|
||||||
return currentRecord = new MutationRecord(type, target);
|
|
||||||
}
|
|
||||||
function getRecordWithOldValue(oldValue) {
|
|
||||||
if (recordWithOldValue) return recordWithOldValue;
|
|
||||||
recordWithOldValue = copyMutationRecord(currentRecord);
|
|
||||||
recordWithOldValue.oldValue = oldValue;
|
|
||||||
return recordWithOldValue;
|
|
||||||
}
|
|
||||||
function clearRecords() {
|
|
||||||
currentRecord = recordWithOldValue = undefined;
|
|
||||||
}
|
|
||||||
function recordRepresentsCurrentMutation(record) {
|
|
||||||
return record === recordWithOldValue || record === currentRecord;
|
|
||||||
}
|
|
||||||
function selectRecord(lastRecord, newRecord) {
|
|
||||||
if (lastRecord === newRecord) return lastRecord;
|
|
||||||
if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) return recordWithOldValue;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
function Registration(observer, target, options) {
|
|
||||||
this.observer = observer;
|
|
||||||
this.target = target;
|
|
||||||
this.options = options;
|
|
||||||
this.transientObservedNodes = [];
|
|
||||||
}
|
|
||||||
Registration.prototype = {
|
|
||||||
enqueue: function(record) {
|
|
||||||
var records = this.observer.records_;
|
|
||||||
var length = records.length;
|
|
||||||
if (records.length > 0) {
|
|
||||||
var lastRecord = records[length - 1];
|
|
||||||
var recordToReplaceLast = selectRecord(lastRecord, record);
|
|
||||||
if (recordToReplaceLast) {
|
|
||||||
records[length - 1] = recordToReplaceLast;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
scheduleCallback(this.observer);
|
|
||||||
}
|
|
||||||
records[length] = record;
|
|
||||||
},
|
|
||||||
addListeners: function() {
|
|
||||||
this.addListeners_(this.target);
|
|
||||||
},
|
|
||||||
addListeners_: function(node) {
|
|
||||||
var options = this.options;
|
|
||||||
if (options.attributes) node.addEventListener("DOMAttrModified", this, true);
|
|
||||||
if (options.characterData) node.addEventListener("DOMCharacterDataModified", this, true);
|
|
||||||
if (options.childList) node.addEventListener("DOMNodeInserted", this, true);
|
|
||||||
if (options.childList || options.subtree) node.addEventListener("DOMNodeRemoved", this, true);
|
|
||||||
},
|
|
||||||
removeListeners: function() {
|
|
||||||
this.removeListeners_(this.target);
|
|
||||||
},
|
|
||||||
removeListeners_: function(node) {
|
|
||||||
var options = this.options;
|
|
||||||
if (options.attributes) node.removeEventListener("DOMAttrModified", this, true);
|
|
||||||
if (options.characterData) node.removeEventListener("DOMCharacterDataModified", this, true);
|
|
||||||
if (options.childList) node.removeEventListener("DOMNodeInserted", this, true);
|
|
||||||
if (options.childList || options.subtree) node.removeEventListener("DOMNodeRemoved", this, true);
|
|
||||||
},
|
|
||||||
addTransientObserver: function(node) {
|
|
||||||
if (node === this.target) return;
|
|
||||||
this.addListeners_(node);
|
|
||||||
this.transientObservedNodes.push(node);
|
|
||||||
var registrations = registrationsTable.get(node);
|
|
||||||
if (!registrations) registrationsTable.set(node, registrations = []);
|
|
||||||
registrations.push(this);
|
|
||||||
},
|
|
||||||
removeTransientObservers: function() {
|
|
||||||
var transientObservedNodes = this.transientObservedNodes;
|
|
||||||
this.transientObservedNodes = [];
|
|
||||||
transientObservedNodes.forEach(function(node) {
|
|
||||||
this.removeListeners_(node);
|
|
||||||
var registrations = registrationsTable.get(node);
|
|
||||||
for (var i = 0; i < registrations.length; i++) {
|
|
||||||
if (registrations[i] === this) {
|
|
||||||
registrations.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
handleEvent: function(e) {
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
switch (e.type) {
|
|
||||||
case "DOMAttrModified":
|
|
||||||
var name = e.attrName;
|
|
||||||
var namespace = e.relatedNode.namespaceURI;
|
|
||||||
var target = e.target;
|
|
||||||
var record = new getRecord("attributes", target);
|
|
||||||
record.attributeName = name;
|
|
||||||
record.attributeNamespace = namespace;
|
|
||||||
var oldValue = e.attrChange === MutationEvent.ADDITION ? null : e.prevValue;
|
|
||||||
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
|
|
||||||
if (!options.attributes) return;
|
|
||||||
if (options.attributeFilter && options.attributeFilter.length && options.attributeFilter.indexOf(name) === -1 && options.attributeFilter.indexOf(namespace) === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (options.attributeOldValue) return getRecordWithOldValue(oldValue);
|
|
||||||
return record;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "DOMCharacterDataModified":
|
|
||||||
var target = e.target;
|
|
||||||
var record = getRecord("characterData", target);
|
|
||||||
var oldValue = e.prevValue;
|
|
||||||
forEachAncestorAndObserverEnqueueRecord(target, function(options) {
|
|
||||||
if (!options.characterData) return;
|
|
||||||
if (options.characterDataOldValue) return getRecordWithOldValue(oldValue);
|
|
||||||
return record;
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "DOMNodeRemoved":
|
|
||||||
this.addTransientObserver(e.target);
|
|
||||||
|
|
||||||
case "DOMNodeInserted":
|
|
||||||
var changedNode = e.target;
|
|
||||||
var addedNodes, removedNodes;
|
|
||||||
if (e.type === "DOMNodeInserted") {
|
|
||||||
addedNodes = [ changedNode ];
|
|
||||||
removedNodes = [];
|
|
||||||
} else {
|
|
||||||
addedNodes = [];
|
|
||||||
removedNodes = [ changedNode ];
|
|
||||||
}
|
|
||||||
var previousSibling = changedNode.previousSibling;
|
|
||||||
var nextSibling = changedNode.nextSibling;
|
|
||||||
var record = getRecord("childList", e.target.parentNode);
|
|
||||||
record.addedNodes = addedNodes;
|
|
||||||
record.removedNodes = removedNodes;
|
|
||||||
record.previousSibling = previousSibling;
|
|
||||||
record.nextSibling = nextSibling;
|
|
||||||
forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) {
|
|
||||||
if (!options.childList) return;
|
|
||||||
return record;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
clearRecords();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
global.JsMutationObserver = JsMutationObserver;
|
|
||||||
if (!global.MutationObserver) {
|
|
||||||
global.MutationObserver = JsMutationObserver;
|
|
||||||
JsMutationObserver._isPolyfilled = true;
|
|
||||||
}
|
|
||||||
})(self);
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,155 +0,0 @@
|
||||||
webcomponents.js
|
|
||||||
================
|
|
||||||
|
|
||||||
[](https://gitter.im/webcomponents/webcomponentsjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
|
||||||
|
|
||||||
A suite of polyfills supporting the [Web Components](http://webcomponents.org) specs:
|
|
||||||
|
|
||||||
**Custom Elements**: allows authors to define their own custom tags ([spec](https://w3c.github.io/webcomponents/spec/custom/)).
|
|
||||||
|
|
||||||
**HTML Imports**: a way to include and reuse HTML documents via other HTML documents ([spec](https://w3c.github.io/webcomponents/spec/imports/)).
|
|
||||||
|
|
||||||
**Shadow DOM**: provides encapsulation by hiding DOM subtrees under shadow roots ([spec](https://w3c.github.io/webcomponents/spec/shadow/)).
|
|
||||||
|
|
||||||
This also folds in polyfills for `MutationObserver` and `WeakMap`.
|
|
||||||
|
|
||||||
|
|
||||||
## Releases
|
|
||||||
|
|
||||||
Pre-built (concatenated & minified) versions of the polyfills are maintained in the [tagged versions](https://github.com/webcomponents/webcomponentsjs/releases) of this repo. There are two variants:
|
|
||||||
|
|
||||||
`webcomponents.js` includes all of the polyfills.
|
|
||||||
|
|
||||||
`webcomponents-lite.js` includes all polyfills except for shadow DOM.
|
|
||||||
|
|
||||||
|
|
||||||
## Browser Support
|
|
||||||
|
|
||||||
Our polyfills are intended to work in the latest versions of evergreen browsers. See below
|
|
||||||
for our complete browser support matrix:
|
|
||||||
|
|
||||||
| Polyfill | IE10 | IE11+ | Chrome* | Firefox* | Safari 7+* | Chrome Android* | Mobile Safari* |
|
|
||||||
| ---------- |:----:|:-----:|:-------:|:--------:|:----------:|:---------------:|:--------------:|
|
|
||||||
| Custom Elements | ~ | ✓ | ✓ | ✓ | ✓ | ✓| ✓ |
|
|
||||||
| HTML Imports | ~ | ✓ | ✓ | ✓ | ✓| ✓| ✓ |
|
|
||||||
| Shadow DOM | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
||||||
| Templates | ✓ | ✓ | ✓ | ✓| ✓ | ✓ | ✓ |
|
|
||||||
|
|
||||||
|
|
||||||
*Indicates the current version of the browser
|
|
||||||
|
|
||||||
~Indicates support may be flaky. If using Custom Elements or HTML Imports with Shadow DOM,
|
|
||||||
you will get the non-flaky Mutation Observer polyfill that Shadow DOM includes.
|
|
||||||
|
|
||||||
The polyfills may work in older browsers, however require additional polyfills (such as classList)
|
|
||||||
to be used. We cannot guarantee support for browsers outside of our compatibility matrix.
|
|
||||||
|
|
||||||
|
|
||||||
### Manually Building
|
|
||||||
|
|
||||||
If you wish to build the polyfills yourself, you'll need `node` and `gulp` on your system:
|
|
||||||
|
|
||||||
* install [node.js](http://nodejs.org/) using the instructions on their website
|
|
||||||
* use `npm` to install [gulp.js](http://gulpjs.com/): `npm install -g gulp`
|
|
||||||
|
|
||||||
Now you are ready to build the polyfills with:
|
|
||||||
|
|
||||||
# install dependencies
|
|
||||||
npm install
|
|
||||||
# build
|
|
||||||
gulp build
|
|
||||||
|
|
||||||
The builds will be placed into the `dist/` directory.
|
|
||||||
|
|
||||||
## Contribute
|
|
||||||
|
|
||||||
See the [contributing guide](CONTRIBUTING.md)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Everything in this repository is BSD style license unless otherwise specified.
|
|
||||||
|
|
||||||
Copyright (c) 2015 The Polymer Authors. All rights reserved.
|
|
||||||
|
|
||||||
## Helper utilities
|
|
||||||
|
|
||||||
### `WebComponentsReady`
|
|
||||||
|
|
||||||
Under native HTML Imports, `<script>` tags in the main document block the loading of such imports. This is to ensure the imports have loaded and any registered elements in them have been upgraded.
|
|
||||||
|
|
||||||
The webcomponents.js and webcomponents-lite.js polyfills parse element definitions and handle their upgrade asynchronously. If prematurely fetching the element from the DOM before it has an opportunity to upgrade, you'll be working with an `HTMLUnknownElement`.
|
|
||||||
|
|
||||||
For these situations (or when you need an approximate replacement for the Polymer 0.5 `polymer-ready` behavior), you can use the `WebComponentsReady` event as a signal before interacting with the element. The criteria for this event to fire is all Custom Elements with definitions registered by the time HTML Imports available at load time have loaded have upgraded.
|
|
||||||
|
|
||||||
```js
|
|
||||||
window.addEventListener('WebComponentsReady', function(e) {
|
|
||||||
// imports are loaded and elements have been registered
|
|
||||||
console.log('Components are ready');
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
|
|
||||||
* [Limited CSS encapsulation](#encapsulation)
|
|
||||||
* [Element wrapping / unwrapping limitations](#wrapping)
|
|
||||||
* [Custom element's constructor property is unreliable](#constructor)
|
|
||||||
* [Contenteditable elements do not trigger MutationObserver](#contentedit)
|
|
||||||
* [ShadowCSS: :host-context(...):host(...) doesn't work](#hostcontext)
|
|
||||||
* [ShadowCSS: :host(.zot:not(.bar:nth-child(2))) doesn't work](#nestedparens)
|
|
||||||
* [HTML imports: document.currentScript doesn't work as expected](#currentscript)
|
|
||||||
* [execCommand isn't supported under Shadow DOM](#execcommand)
|
|
||||||
|
|
||||||
### Limited CSS encapsulation <a id="encapsulation"></a>
|
|
||||||
Under native Shadow DOM, CSS selectors cannot cross the shadow boundary. This means document level styles don't apply to shadow roots, and styles defined within a shadow root don't apply outside of that shadow root. [Several selectors](http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/) are provided to be able to deal with the shadow boundary.
|
|
||||||
|
|
||||||
The Shadow DOM polyfill can't prevent document styles from leaking into shadow roots. It can, however, encapsulate styles within shadow roots to some extent. This behavior isn't automatically emulated by the Shadow DOM polyfill, but it can be achieved by manually using the included ShadowCSS shim:
|
|
||||||
|
|
||||||
```
|
|
||||||
WebComponents.ShadowCSS.shimStyling( shadowRoot, scope );
|
|
||||||
```
|
|
||||||
|
|
||||||
... where `shadowRoot` is the shadow root of a DOM element, and `scope` is the name of the scope used to prefix the selectors. This removes all `<style>` elements from the shadow root, rewrites it rules using the given scope and reinserts the style as a document level stylesheet. Note that the `:host` and `:host-context` pseudo classes are also rewritten.
|
|
||||||
|
|
||||||
For a full explanation on the implementation and both the possibilities and the limitations of ShadowCSS please view the documentation in the [ShadowCSS source](src/ShadowCSS/ShadowCSS.js).
|
|
||||||
|
|
||||||
### Element wrapping / unwrapping limitations <a id="wrapping"></a>
|
|
||||||
The Shadow DOM polyfill is implemented by [wrapping](http://webcomponents.org/polyfills/shadow-dom/#wrappers) DOM elements whenever possible. It does this by wrapping methods like `document.querySelector` to return wrapped DOM elements. This has a few caveats:
|
|
||||||
* Not _everything_ can be wrapped. For example, elements like `document`, `window`, `document.body`, `document.fullscreenElement` and others are non-configurable and thus cannot be overridden.
|
|
||||||
* Wrappers don't support [live NodeLists](https://developer.mozilla.org/en-US/docs/Web/API/NodeList#A_sometimes-live_collection) like `HTMLElement.childNodes` and `HTMLFormElement.elements`. All NodeLists are snapshotted upon read. See [#217](https://github.com/webcomponents/webcomponentsjs/issues/217) for an explanation.
|
|
||||||
|
|
||||||
In order to work around these limitations the polyfill provides the `ShadowDOMPolyfill.wrap` and `ShadowDOMPolyfill.unwrap` methods to respectively wrap and unwrap DOM elements manually.
|
|
||||||
|
|
||||||
### Custom element's constructor property is unreliable <a id="constructor"></a>
|
|
||||||
See [#215](https://github.com/webcomponents/webcomponentsjs/issues/215) for background.
|
|
||||||
|
|
||||||
In Safari and IE, instances of Custom Elements have a `constructor` property of `HTMLUnknownElementConstructor` and `HTMLUnknownElement`, respectively. It's unsafe to rely on this property for checking element types.
|
|
||||||
|
|
||||||
It's worth noting that `customElement.__proto__.__proto__.constructor` is `HTMLElementPrototype` and that the prototype chain isn't modified by the polyfills(onto `ElementPrototype`, etc.)
|
|
||||||
|
|
||||||
### Contenteditable elements do not trigger MutationObserver <a id="contentedit"></a>
|
|
||||||
Using the MutationObserver polyfill, it isn't possible to monitor mutations of an element marked `contenteditable`.
|
|
||||||
See [the mailing list](https://groups.google.com/forum/#!msg/polymer-dev/LHdtRVXXVsA/v1sGoiTYWUkJ)
|
|
||||||
|
|
||||||
### ShadowCSS: :host-context(...):host(...) doesn't work <a id="hostcontext"></a>
|
|
||||||
See [#16](https://github.com/webcomponents/webcomponentsjs/issues/16) for background.
|
|
||||||
|
|
||||||
Under the shadow DOM polyfill, rules like:
|
|
||||||
```
|
|
||||||
:host-context(.foo):host(.bar) {...}
|
|
||||||
```
|
|
||||||
don't work, despite working under native Shadow DOM. The solution is to use `polyfill-next-selector` like:
|
|
||||||
|
|
||||||
```
|
|
||||||
polyfill-next-selector { content: '.foo :host.bar, :host.foo.bar'; }
|
|
||||||
```
|
|
||||||
|
|
||||||
### ShadowCSS: :host(.zot:not(.bar:nth-child(2))) doesn't work <a id="nestedparens"></a>
|
|
||||||
ShadowCSS `:host()` rules can only have (at most) 1-level of nested parentheses in its argument selector under ShadowCSS. For example, `:host(.zot)` and `:host(.zot:not(.bar))` both work, but `:host(.zot:not(.bar:nth-child(2)))` does not.
|
|
||||||
|
|
||||||
### HTML imports: document.currentScript doesn't work as expected <a id="currentscript"></a>
|
|
||||||
In native HTML Imports, document.currentScript.ownerDocument references the import document itself. In the polyfill use document._currentScript.ownerDocument (note the underscore).
|
|
||||||
|
|
||||||
### execCommand and contenteditable isn't supported under Shadow DOM <a id="execcommand"></a>
|
|
||||||
See [#212](https://github.com/webcomponents/webcomponentsjs/issues/212)
|
|
||||||
|
|
||||||
`execCommand`, and `contenteditable` aren't supported under the ShadowDOM polyfill, with commands that insert or remove nodes being especially prone to failure.
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"name": "webcomponentsjs",
|
|
||||||
"main": "webcomponents.js",
|
|
||||||
"version": "0.7.22",
|
|
||||||
"homepage": "http://webcomponents.org",
|
|
||||||
"authors": [
|
|
||||||
"The Polymer Authors"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/webcomponents/webcomponentsjs.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"webcomponents"
|
|
||||||
],
|
|
||||||
"license": "BSD",
|
|
||||||
"ignore": [],
|
|
||||||
"devDependencies": {
|
|
||||||
"web-component-tester": "^4.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
{
|
|
||||||
"name": "webcomponents.js",
|
|
||||||
"version": "0.7.22",
|
|
||||||
"description": "webcomponents.js",
|
|
||||||
"main": "webcomponents.js",
|
|
||||||
"directories": {
|
|
||||||
"test": "tests"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/webcomponents/webcomponentsjs.git"
|
|
||||||
},
|
|
||||||
"author": "The Polymer Authors",
|
|
||||||
"license": "BSD-3-Clause",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/webcomponents/webcomponentsjs/issues"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"test": "wct"
|
|
||||||
},
|
|
||||||
"homepage": "http://webcomponents.org",
|
|
||||||
"devDependencies": {
|
|
||||||
"gulp": "^3.8.8",
|
|
||||||
"gulp-audit": "^1.0.0",
|
|
||||||
"gulp-concat": "^2.4.1",
|
|
||||||
"gulp-header": "^1.1.1",
|
|
||||||
"gulp-uglify": "^1.0.1",
|
|
||||||
"run-sequence": "^1.0.1",
|
|
||||||
"web-component-tester": "^4.0.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
File diff suppressed because one or more lines are too long
40367
lib/admin/public/elm.js
40367
lib/admin/public/elm.js
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
|
|
@ -1,29 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<base href="/">
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Nunito:400,700&subset=latin-ext" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
<script src="elm.js"></script>
|
|
||||||
<style>
|
|
||||||
@keyframes fadein {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-outer-spin-button,
|
|
||||||
input[type="number"]::-webkit-inner-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
Elm.Main.fullscreen()
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,631 +0,0 @@
|
||||||
body {
|
|
||||||
font-family: Nunito, sans-serif;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminQrCode {
|
|
||||||
background-color: #f6f6f4;
|
|
||||||
padding: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminQrCode svg {
|
|
||||||
height: 400px;
|
|
||||||
width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminMain {
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
margin-bottom: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminPaneWrapper {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminLeftPane {
|
|
||||||
min-width: 270px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminContentPane {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminStatusBar {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background-color: #5f5f56;
|
|
||||||
color: #ffffff;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCashOut {
|
|
||||||
background-color: #f6f6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminFormRow {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminFormRow:first-child {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminFormRow label {
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminFormRow label > div {
|
|
||||||
margin: 0 0 5px;
|
|
||||||
color: #5f5f56;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminFormRow input {
|
|
||||||
border: 0;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 6px;
|
|
||||||
text-align: left;
|
|
||||||
font-family: Inconsolata, monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
width: 90%;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminButtonRow {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminButton {
|
|
||||||
color: #ffffff;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #004062;
|
|
||||||
padding: 10px 15px;
|
|
||||||
display: inline-block;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminButton:hover {
|
|
||||||
background-color: #042c47;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminButton:active {
|
|
||||||
color: #37e8d7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminButton.lamassuAdminActive {
|
|
||||||
color: #37e8d7;
|
|
||||||
background-color: #042c47;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminButton.lamassuAdminDisabled {
|
|
||||||
background-color: #E6E6E3;
|
|
||||||
color: #ffffff;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminMainLeft {
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminMainRight {
|
|
||||||
background-color: #f6f6f4;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminContent {
|
|
||||||
margin: 20px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminContainer {
|
|
||||||
padding: 30px;
|
|
||||||
background-color: #f6f6f4;
|
|
||||||
border-radius: 0px 5px 5px 5px;
|
|
||||||
width: 30em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCryptoAddress {
|
|
||||||
font-family: Inconsolata, monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminBalanceSection {
|
|
||||||
margin-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminBalanceSection h2 {
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin-bottom: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTextarea {
|
|
||||||
width: 100%;
|
|
||||||
border: 0px;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCryptoTabs {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCryptoTabs > .lamassuAdminCryptoTab {
|
|
||||||
padding: 10px 15px;
|
|
||||||
color: #5f5f56;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #E6E6E3;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCryptoTabs > .lamassuAdminCryptoTab:hover {
|
|
||||||
background-color: #fcfcfa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCryptoTabs > .lamassuAdminCryptoTab:active {
|
|
||||||
color: #5f5f56;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCryptoTabs > .lamassuAdminCryptoTab.lamassuAdminActive {
|
|
||||||
color: #5f5f56;
|
|
||||||
background-color: #f6f6f4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCryptoTabs > .lamassuAdminCryptoTab:first-child {
|
|
||||||
border-radius: 5px 0px 0px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminCryptoTabs > .lamassuAdminCryptoTab:last-child {
|
|
||||||
border-radius: 0px 5px 0px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminSectionLabel {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 30px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigContainer {
|
|
||||||
padding: 20px 60px;
|
|
||||||
border-radius: 0px 7px 7px 7px;
|
|
||||||
background-color: #f6f6f4;
|
|
||||||
margin: 0 0 10px;
|
|
||||||
animation: fadein 0.8s;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 15em;
|
|
||||||
min-width: 20em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNoInput {
|
|
||||||
font-family: Inconsolata, monospace;
|
|
||||||
color: #5f5f56;
|
|
||||||
font-weight: normal;
|
|
||||||
text-align: left !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable {
|
|
||||||
border-radius: 7px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 14px;
|
|
||||||
width: 100%;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable a {
|
|
||||||
text-decoration: none;
|
|
||||||
color: #5f5f56;
|
|
||||||
border-bottom: 1px solid #37e8d7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable .lamassuAdminNumberColumn {
|
|
||||||
text-align: right;
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable .lamassuAdminDirectionColumn {
|
|
||||||
text-align: left;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable .lamassuAdminTxCancelled {
|
|
||||||
background-color: #efd1d2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable tbody {
|
|
||||||
font-family: Inconsolata, monospace;
|
|
||||||
color: #5f5f56;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable tbody td {
|
|
||||||
padding: 2px 14px;
|
|
||||||
border-bottom: 1px solid #f6f6f4;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable tbody .lamassuAdminTruncatedColumn {
|
|
||||||
max-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 300px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable tbody .lamassuAdminTxDate {
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable tbody .lamassuAdminTxAddress {
|
|
||||||
width: 25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable thead {
|
|
||||||
font-size: 14px;
|
|
||||||
text-align: center;
|
|
||||||
color: #5f5f56;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminTxTable thead td {
|
|
||||||
border-bottom: 2px solid #f6f6f4;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminEmptyTable {
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable {
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: bold;
|
|
||||||
border-radius: 7px;
|
|
||||||
margin: 20px 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer {
|
|
||||||
border-radius: 3px;
|
|
||||||
position: relative;
|
|
||||||
margin: 0;
|
|
||||||
border: 2px solid #E6E6E3;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminNoOptions {
|
|
||||||
background-color: #fcfcfa;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #5f5f56;
|
|
||||||
padding: 5px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: default;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminSelectBox {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 5px;
|
|
||||||
background-color: inherit;
|
|
||||||
width: 60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminBoxContainer {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 100;
|
|
||||||
left: -3px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
text-align: left;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 80%;
|
|
||||||
border-radius: 3px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
border: 2px solid #E6E6E3;
|
|
||||||
border-top: 0;
|
|
||||||
color: #5f5f56;
|
|
||||||
width: 15em;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminBoxItemActive {
|
|
||||||
color: #004062;
|
|
||||||
font-weight: 900;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminBoxItem {
|
|
||||||
padding: 3px 6px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminInfo {
|
|
||||||
padding: 3px 6px;
|
|
||||||
color: #2d2d2d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminMultiItemContainer .lamassuAdminSelectedItem {
|
|
||||||
background-color: #004062;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 2px;
|
|
||||||
margin: 0 1px;
|
|
||||||
font-family: Inconsolata, monospace;
|
|
||||||
font-size: 70%;
|
|
||||||
font-weight: normal;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminMultiItemContainer .lamassuAdminFallbackItem {
|
|
||||||
background-color: #37e8d7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminSingleItemContainer .lamassuAdminSelectedItem {
|
|
||||||
font-family: Inconsolata, monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminSingleItemContainer .lamassuAdminFallbackItem {
|
|
||||||
color: #5f5f56;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminSelectizeLanguage .lamassuAdminSelectBox {
|
|
||||||
width: 140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer .lamassuAdminSelectizeCryptoCurrency .lamassuAdminSelectBox {
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminSelectizeContainer input {
|
|
||||||
text-align: left;
|
|
||||||
background-color: inherit;
|
|
||||||
padding: 6px 2px;
|
|
||||||
width: 6em;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminInputContainer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
border: 2px solid #E6E6E3;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminUnitDisplay {
|
|
||||||
background-color: #E6E6E3;
|
|
||||||
color: #5f5f56;
|
|
||||||
padding: 0 5px;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 80%;
|
|
||||||
line-height: 25px;
|
|
||||||
cursor: default;
|
|
||||||
font-family: Nunito, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable input {
|
|
||||||
border: 0;
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 6px;
|
|
||||||
text-align: right;
|
|
||||||
width: 100%;
|
|
||||||
font-family: Inconsolata, monospace;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
outline: none;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminCellDisabled {
|
|
||||||
background: repeating-linear-gradient(45deg,#dfdfdc,#dfdfdc 2px,#e6e6e3 5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminBasicInput::placeholder {
|
|
||||||
color: #37e8d7;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminBasicInputDisabled {
|
|
||||||
height: 25px;
|
|
||||||
line-height: 25px;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #5f5f56;
|
|
||||||
opacity: 0.7;
|
|
||||||
text-align: left;
|
|
||||||
padding: 0 1em;
|
|
||||||
background: repeating-linear-gradient(45deg,#dfdfdc,#dfdfdc 2px,#e6e6e3 5px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminReadOnly {
|
|
||||||
line-height: 25px;
|
|
||||||
background-color: #f6f6f4;
|
|
||||||
font-family: Inconsolata, monospace;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #5f5f56;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminReadOnly > .lamassuAdminBasicInputReadOnly {
|
|
||||||
padding: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable td {
|
|
||||||
padding: 3px 4px;
|
|
||||||
text-align: center;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminComponent {
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 2px solid #f6f6f4;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminFocusedComponent > .lamassuAdminInputContainer {
|
|
||||||
border-color: #37e8d7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminInvalidComponent input {
|
|
||||||
color: #eb6b6e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminInvalidComponent > .lamassuAdminInputContainer {
|
|
||||||
border-color: #eb6b6e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminInvalidComponent > .lamassuAdminSelectizeContainer {
|
|
||||||
border-color: #eb6b6e;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable tbody td {
|
|
||||||
text-align: right;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable tbody td:first-child {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable thead {
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminMultiDisplay {
|
|
||||||
background-color: #E6E6E3;
|
|
||||||
border-left: 3px solid #f6f6f4;
|
|
||||||
border-right: 3px solid #f6f6f4;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable th {
|
|
||||||
padding: 3px 4px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminConfigTableGlobalRow td:first-child {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminTextCell {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminShortCell {
|
|
||||||
min-width: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminMediumCell {
|
|
||||||
min-width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminConfigTable .lamassuAdminLongCell {
|
|
||||||
min-width: 20em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminSaving {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: normal;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 0 110px 0;
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
font-size: 18px;
|
|
||||||
width: 15em;
|
|
||||||
max-width: 15em;
|
|
||||||
min-width: 15em;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarRoute {
|
|
||||||
height: 60px;
|
|
||||||
display: block;
|
|
||||||
line-height: 60px;
|
|
||||||
padding: 0px 20px;
|
|
||||||
color: #5f5f56;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarRoute:hover {
|
|
||||||
background-color: #282828;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarRoute:active {
|
|
||||||
color: #37e8d7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarRoute.lamassuAdminActive {
|
|
||||||
color: #37e8d7;
|
|
||||||
background-color: #282828;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarCategory {
|
|
||||||
height: 60px;
|
|
||||||
display: block;
|
|
||||||
line-height: 60px;
|
|
||||||
padding: 0px 20px;
|
|
||||||
color: #5f5f56;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarCategory:hover {
|
|
||||||
background-color: #282828;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarCategory:active {
|
|
||||||
color: #37e8d7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarCategory.lamassuAdminActive {
|
|
||||||
color: #37e8d7;
|
|
||||||
background-color: #282828;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminInvalidGroup {
|
|
||||||
color: #eb6b6e !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarCategoryContainer .lamassuAdminNavBarRoute {
|
|
||||||
color: #5f5f56;
|
|
||||||
font-weight: bold;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: #2d2d2d;
|
|
||||||
padding: 0 20px 0 30px;
|
|
||||||
font-weight: 500;
|
|
||||||
animation: fadein 0.8s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarCategoryContainer .lamassuAdminNavBarRoute:hover {
|
|
||||||
background-color: #282828;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarCategoryContainer .lamassuAdminNavBarRoute:active {
|
|
||||||
color: #37e8d7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lamassuAdminNavBar .lamassuAdminNavBarCategoryContainer .lamassuAdminNavBarRoute.lamassuAdminActive {
|
|
||||||
color: #37e8d7;
|
|
||||||
background-color: #282828;
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<base href="/">
|
|
||||||
<link href="https://fonts.googleapis.com/css?family=Nunito:400,700&subset=latin-ext" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
<script src="lamassu-elm.js"></script>
|
|
||||||
<style>
|
|
||||||
@keyframes fadein {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="number"]::-webkit-outer-spin-button,
|
|
||||||
input[type="number"]::-webkit-inner-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<script>
|
|
||||||
Elm.Main.fullscreen()
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
{
|
|
||||||
"code": "bitgo",
|
|
||||||
"display": "BitGo",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "token",
|
|
||||||
"display": "API token",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "BTCWalletId",
|
|
||||||
"display": "BTC Wallet ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": false,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "BTCWalletPassphrase",
|
|
||||||
"display": "BTC Wallet passphrase",
|
|
||||||
"fieldType": "password",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "LTCWalletId",
|
|
||||||
"display": "LTC Wallet ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": false,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "LTCWalletPassphrase",
|
|
||||||
"display": "LTC Wallet passphrase",
|
|
||||||
"fieldType": "password",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ZECWalletId",
|
|
||||||
"display": "ZEC Wallet ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": false,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ZECWalletPassphrase",
|
|
||||||
"display": "ZEC Wallet passphrase",
|
|
||||||
"fieldType": "password",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "BCHWalletId",
|
|
||||||
"display": "BCH Wallet ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": false,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "BCHWalletPassphrase",
|
|
||||||
"display": "BCH Wallet passphrase",
|
|
||||||
"fieldType": "password",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "DASHWalletId",
|
|
||||||
"display": "DASH Wallet ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": false,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "DASHWalletPassphrase",
|
|
||||||
"display": "DASH Wallet passphrase",
|
|
||||||
"fieldType": "password",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "environment",
|
|
||||||
"display": "Environment (prod or test)",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": false,
|
|
||||||
"required": false,
|
|
||||||
"placeholder": "prod",
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
{
|
|
||||||
"code": "bitstamp",
|
|
||||||
"display": "Bitstamp",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "clientId",
|
|
||||||
"display": "Client ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "key",
|
|
||||||
"display": "API key",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "secret",
|
|
||||||
"display": "API secret",
|
|
||||||
"fieldType": "password",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"code": "blockcypher",
|
|
||||||
"display": "Blockcypher",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "token",
|
|
||||||
"display": "API token",
|
|
||||||
"fieldType": "password",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "confidenceFactor",
|
|
||||||
"display": "Confidence Factor",
|
|
||||||
"fieldType": "integer",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
{
|
|
||||||
"code": "infura",
|
|
||||||
"display": "Infura",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "apiKey",
|
|
||||||
"display": "Project ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "apiSecret",
|
|
||||||
"display": "Project secret",
|
|
||||||
"fieldType": "password",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "endpoint",
|
|
||||||
"display": "Endpoint",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"code": "itbit",
|
|
||||||
"display": "itBit",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "userId",
|
|
||||||
"display": "User ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "walletId",
|
|
||||||
"display": "Wallet ID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "clientKey",
|
|
||||||
"display": "Client key",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "clientSecret",
|
|
||||||
"display": "Client secret",
|
|
||||||
"fieldType": "password",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"code": "kraken",
|
|
||||||
"display": "Kraken",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "apiKey",
|
|
||||||
"display": "API Key",
|
|
||||||
"fieldType": "string",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "privateKey",
|
|
||||||
"display": "Private Key",
|
|
||||||
"fieldType": "password",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"code": "mailgun",
|
|
||||||
"display": "Mailgun",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "apiKey",
|
|
||||||
"display": "API key",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "domain",
|
|
||||||
"display": "Domain",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "fromEmail",
|
|
||||||
"display": "From email",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "toEmail",
|
|
||||||
"display": "To email",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"code": "strike",
|
|
||||||
"display": "Strike",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "token",
|
|
||||||
"display": "API Token",
|
|
||||||
"fieldType": "password",
|
|
||||||
"secret": true,
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{
|
|
||||||
"code": "twilio",
|
|
||||||
"display": "Twilio",
|
|
||||||
"fields": [
|
|
||||||
{
|
|
||||||
"code": "accountSid",
|
|
||||||
"display": "Account SID",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "authToken",
|
|
||||||
"display": "Auth token",
|
|
||||||
"fieldType": "password",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "fromNumber",
|
|
||||||
"display": "Twilio number (international format)",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "toNumber",
|
|
||||||
"display": "Notifications number (international format)",
|
|
||||||
"fieldType": "string",
|
|
||||||
"required": true,
|
|
||||||
"value": ""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
const { intervalToDuration, secondsToMilliseconds, formatDuration } = require('date-fns/fp')
|
|
||||||
const _ = require('lodash/fp')
|
|
||||||
|
|
||||||
const ticker = require('../ticker')
|
|
||||||
const settingsLoader = require('./settings-loader')
|
|
||||||
|
|
||||||
const db = require('../db')
|
|
||||||
const machineLoader = require('../machine-loader')
|
|
||||||
|
|
||||||
const CONSIDERED_UP_SECS = 30
|
|
||||||
|
|
||||||
function checkWasConfigured () {
|
|
||||||
return settingsLoader.loadLatest()
|
|
||||||
.then(() => true)
|
|
||||||
.catch(() => false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function machinesLastPing () {
|
|
||||||
const sql = `select min(extract(epoch from (now() - created))) as age
|
|
||||||
from machine_events
|
|
||||||
group by device_id`
|
|
||||||
|
|
||||||
return Promise.all([machineLoader.getMachineNames(), db.any(sql)])
|
|
||||||
.then(([machines, events]) => {
|
|
||||||
if (machines.length === 0) return 'No paired machines'
|
|
||||||
|
|
||||||
const addName = event => {
|
|
||||||
const machine = _.find(['deviceId', event.deviceId], machines)
|
|
||||||
if (!machine) return null
|
|
||||||
return _.set('name', machine.name, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapper = _.flow(_.filter(row => row.age > CONSIDERED_UP_SECS), _.map(addName), _.compact)
|
|
||||||
const downRows = mapper(events)
|
|
||||||
|
|
||||||
if (downRows.length === 0) return 'All machines are up'
|
|
||||||
|
|
||||||
if (downRows.length === 1) {
|
|
||||||
const row = downRows[0]
|
|
||||||
const age = intervalToDuration({ start: 0, end: secondsToMilliseconds(row.age) })
|
|
||||||
return `${row.name} down for ${formatDuration(age)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'Multiple machines down'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function status () {
|
|
||||||
const sql = `select extract(epoch from (now() - created)) as age
|
|
||||||
from server_events
|
|
||||||
where event_type=$1
|
|
||||||
order by created desc
|
|
||||||
limit 1`
|
|
||||||
|
|
||||||
return Promise.all([checkWasConfigured(), db.oneOrNone(sql, ['ping']), machinesLastPing()])
|
|
||||||
.then(([wasConfigured, statusRow, machineStatus]) => {
|
|
||||||
const age = statusRow && intervalToDuration({ start: 0, end: secondsToMilliseconds(statusRow.age) })
|
|
||||||
const up = statusRow ? statusRow.age < CONSIDERED_UP_SECS : false
|
|
||||||
const lastPing = statusRow && formatDuration(age)
|
|
||||||
|
|
||||||
return settingsLoader.loadLatest()
|
|
||||||
.catch(() => null)
|
|
||||||
.then(settings => {
|
|
||||||
return getRates(settings)
|
|
||||||
.then(rates => ({wasConfigured, up, lastPing, rates, machineStatus}))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function getRates (settings) {
|
|
||||||
if (!settings) return Promise.resolve([])
|
|
||||||
|
|
||||||
return ticker.getRates(settings, 'USD', 'BTC')
|
|
||||||
.then(ratesRec => {
|
|
||||||
return [{
|
|
||||||
crypto: 'BTC',
|
|
||||||
bid: parseFloat(ratesRec.rates.bid),
|
|
||||||
ask: parseFloat(ratesRec.rates.ask)
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
.catch(() => [])
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {status}
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
const _ = require('lodash/fp')
|
|
||||||
|
|
||||||
const db = require('../db')
|
|
||||||
const machineLoader = require('../machine-loader')
|
|
||||||
const tx = require('../tx')
|
|
||||||
const cashInTx = require('../cash-in/cash-in-tx')
|
|
||||||
const { REDEEMABLE_AGE } = require('../cash-out/cash-out-helper')
|
|
||||||
|
|
||||||
const NUM_RESULTS = 1000
|
|
||||||
|
|
||||||
function addNames (txs) {
|
|
||||||
return machineLoader.getMachineNames()
|
|
||||||
.then(machines => {
|
|
||||||
const addName = tx => {
|
|
||||||
const machine = _.find(['deviceId', tx.deviceId], machines)
|
|
||||||
const name = machine ? machine.name : 'Unpaired'
|
|
||||||
return _.set('machineName', name, tx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return _.map(addName, txs)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const camelize = _.mapKeys(_.camelCase)
|
|
||||||
|
|
||||||
function batch () {
|
|
||||||
const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']),
|
|
||||||
_.take(NUM_RESULTS), _.map(camelize), addNames)
|
|
||||||
|
|
||||||
const cashInSql = `select 'cashIn' as tx_class, cash_in_txs.*,
|
|
||||||
((not send_confirmed) and (created <= now() - interval $1)) as expired
|
|
||||||
from cash_in_txs
|
|
||||||
order by created desc limit $2`
|
|
||||||
|
|
||||||
const cashOutSql = `select 'cashOut' as tx_class, cash_out_txs.*,
|
|
||||||
(NOT dispense AND extract(epoch from (now() - greatest(created, confirmed_at))) >= $2) as expired
|
|
||||||
from cash_out_txs
|
|
||||||
order by created desc limit $1`
|
|
||||||
|
|
||||||
return Promise.all([db.any(cashInSql, [cashInTx.PENDING_INTERVAL, NUM_RESULTS]), db.any(cashOutSql, [NUM_RESULTS, REDEEMABLE_AGE])])
|
|
||||||
.then(packager)
|
|
||||||
}
|
|
||||||
|
|
||||||
function single (txId) {
|
|
||||||
const packager = _.flow(_.compact, _.map(camelize), addNames)
|
|
||||||
|
|
||||||
const cashInSql = `select 'cashIn' as tx_class,
|
|
||||||
((not send_confirmed) and (created <= now() - interval $1)) as expired,
|
|
||||||
cash_in_txs.*
|
|
||||||
from cash_in_txs
|
|
||||||
where id=$2`
|
|
||||||
|
|
||||||
const cashOutSql = `select 'cashOut' as tx_class,
|
|
||||||
(NOT dispense AND extract(epoch from (now() - greatest(created, confirmed_at))) >= $2) as expired,
|
|
||||||
cash_out_txs.*
|
|
||||||
from cash_out_txs
|
|
||||||
where id=$1`
|
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]),
|
|
||||||
db.oneOrNone(cashOutSql, [txId, REDEEMABLE_AGE])
|
|
||||||
])
|
|
||||||
.then(packager)
|
|
||||||
.then(_.head)
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel (txId) {
|
|
||||||
return tx.cancel(txId)
|
|
||||||
.then(() => single(txId))
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {batch, single, cancel}
|
|
||||||
80
lib/compliance-external.js
Normal file
80
lib/compliance-external.js
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
const _ = require('lodash/fp')
|
||||||
|
|
||||||
|
const logger = require('./logger')
|
||||||
|
const configManager = require('./new-config-manager')
|
||||||
|
const ph = require('./plugin-helper')
|
||||||
|
|
||||||
|
const getPlugin = (settings, pluginCode) => {
|
||||||
|
const account = settings.accounts[pluginCode]
|
||||||
|
const plugin = ph.load(ph.COMPLIANCE, pluginCode)
|
||||||
|
|
||||||
|
return ({ plugin, account })
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatus = (settings, service, customerId) => {
|
||||||
|
try {
|
||||||
|
const { plugin, account } = getPlugin(settings, service)
|
||||||
|
|
||||||
|
return plugin.getApplicantStatus(account, customerId)
|
||||||
|
.then((status) => ({
|
||||||
|
service,
|
||||||
|
status
|
||||||
|
}))
|
||||||
|
.catch((error) => {
|
||||||
|
if (error.response.status !== 404) logger.error(`Error getting applicant for service ${service}:`, error.message)
|
||||||
|
return {
|
||||||
|
service: service,
|
||||||
|
status: null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error loading plugin for service ${service}:`, error)
|
||||||
|
return Promise.resolve({
|
||||||
|
service: service,
|
||||||
|
status: null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusMap = (settings, customerExternalCompliance) => {
|
||||||
|
const triggers = configManager.getTriggers(settings.config)
|
||||||
|
const services = _.flow(
|
||||||
|
_.map('externalService'),
|
||||||
|
_.compact,
|
||||||
|
_.uniq
|
||||||
|
)(triggers)
|
||||||
|
|
||||||
|
const applicantPromises = _.map(service => {
|
||||||
|
return getStatus(settings, service, customerExternalCompliance)
|
||||||
|
})(services)
|
||||||
|
|
||||||
|
return Promise.all(applicantPromises)
|
||||||
|
.then((applicantResults) => {
|
||||||
|
return _.reduce((map, result) => {
|
||||||
|
if (result.status) map[result.service] = result.status
|
||||||
|
return map
|
||||||
|
}, {})(applicantResults)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const createApplicant = (settings, externalService, customerId) => {
|
||||||
|
const account = settings.accounts[externalService]
|
||||||
|
const { plugin } = getPlugin(settings, externalService)
|
||||||
|
|
||||||
|
return plugin.createApplicant(account, customerId, account.applicantLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
const createLink = (settings, externalService, customerId) => {
|
||||||
|
const account = settings.accounts[externalService]
|
||||||
|
const { plugin } = getPlugin(settings, externalService)
|
||||||
|
|
||||||
|
return plugin.createLink(account, customerId, account.applicantLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getStatusMap,
|
||||||
|
getStatus,
|
||||||
|
createApplicant,
|
||||||
|
createLink
|
||||||
|
}
|
||||||
106
lib/customers.js
106
lib/customers.js
|
|
@ -17,6 +17,9 @@ const NUM_RESULTS = 1000
|
||||||
const sms = require('./sms')
|
const sms = require('./sms')
|
||||||
const settingsLoader = require('./new-settings-loader')
|
const settingsLoader = require('./new-settings-loader')
|
||||||
const logger = require('./logger')
|
const logger = require('./logger')
|
||||||
|
const externalCompliance = require('./compliance-external')
|
||||||
|
|
||||||
|
const { APPROVED, RETRY } = require('./plugins/compliance/consts')
|
||||||
|
|
||||||
const TX_PASSTHROUGH_ERROR_CODES = ['operatorCancel', 'scoreThresholdReached', 'walletScoringError']
|
const TX_PASSTHROUGH_ERROR_CODES = ['operatorCancel', 'scoreThresholdReached', 'walletScoringError']
|
||||||
|
|
||||||
|
|
@ -243,7 +246,7 @@ function deleteEditedData (id, data) {
|
||||||
'id_card_data',
|
'id_card_data',
|
||||||
'id_card_photo',
|
'id_card_photo',
|
||||||
'us_ssn',
|
'us_ssn',
|
||||||
'subcriber_info',
|
'subscriber_info',
|
||||||
'name'
|
'name'
|
||||||
]
|
]
|
||||||
const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, data))
|
const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, data))
|
||||||
|
|
@ -322,6 +325,7 @@ function getById (id) {
|
||||||
return db.oneOrNone(sql, [id])
|
return db.oneOrNone(sql, [id])
|
||||||
.then(assignCustomerData)
|
.then(assignCustomerData)
|
||||||
.then(getCustomInfoRequestsData)
|
.then(getCustomInfoRequestsData)
|
||||||
|
.then(getExternalComplianceMachine)
|
||||||
.then(camelize)
|
.then(camelize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -342,7 +346,11 @@ function camelize (customer) {
|
||||||
function camelizeDeep (customer) {
|
function camelizeDeep (customer) {
|
||||||
return _.flow(
|
return _.flow(
|
||||||
camelize,
|
camelize,
|
||||||
it => ({ ...it, notes: (it.notes ?? []).map(camelize) })
|
it => ({
|
||||||
|
...it,
|
||||||
|
notes: (it.notes ?? []).map(camelize),
|
||||||
|
externalCompliance: (it.externalCompliance ?? []).map(camelize)
|
||||||
|
})
|
||||||
)(customer)
|
)(customer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -587,6 +595,7 @@ function getCustomerById (id) {
|
||||||
return db.oneOrNone(sql, [passableErrorCodes, id])
|
return db.oneOrNone(sql, [passableErrorCodes, id])
|
||||||
.then(assignCustomerData)
|
.then(assignCustomerData)
|
||||||
.then(getCustomInfoRequestsData)
|
.then(getCustomInfoRequestsData)
|
||||||
|
.then(getExternalCompliance)
|
||||||
.then(camelizeDeep)
|
.then(camelizeDeep)
|
||||||
.then(formatSubscriberInfo)
|
.then(formatSubscriberInfo)
|
||||||
}
|
}
|
||||||
|
|
@ -926,6 +935,95 @@ function updateLastAuthAttempt (customerId) {
|
||||||
return db.none(sql, [customerId])
|
return db.none(sql, [customerId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getExternalComplianceMachine (customer) {
|
||||||
|
return settingsLoader.loadLatest()
|
||||||
|
.then(settings => externalCompliance.getStatusMap(settings, customer.id))
|
||||||
|
.then(statusMap => {
|
||||||
|
return updateExternalComplianceByMap(customer.id, statusMap)
|
||||||
|
.then(() => customer.externalCompliance = statusMap)
|
||||||
|
.then(() => customer)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExternalCompliance(customerId, service, status) {
|
||||||
|
const sql = `
|
||||||
|
UPDATE customer_external_compliance SET last_known_status = $1, last_updated = now()
|
||||||
|
WHERE customer_id=$2 AND service=$3
|
||||||
|
`
|
||||||
|
return db.none(sql, [status, customerId, service])
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateExternalComplianceByMap(customerId, serviceMap) {
|
||||||
|
const sql = `
|
||||||
|
UPDATE customer_external_compliance SET last_known_status = $1, last_updated = now()
|
||||||
|
WHERE customer_id=$2 AND service=$3
|
||||||
|
`
|
||||||
|
const pairs = _.toPairs(serviceMap)
|
||||||
|
const promises = _.map(([service, status]) => db.none(sql, [status.answer, customerId, service]))(pairs)
|
||||||
|
return Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getExternalCompliance(customer) {
|
||||||
|
const sql = `SELECT external_id, service, last_known_status, last_updated
|
||||||
|
FROM customer_external_compliance where customer_id=$1`
|
||||||
|
return db.manyOrNone(sql, [customer.id])
|
||||||
|
.then(compliance => {
|
||||||
|
customer.externalCompliance = compliance
|
||||||
|
})
|
||||||
|
.then(() => customer)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOpenExternalCompliance() {
|
||||||
|
const sql = `SELECT customer_id, service, last_known_status FROM customer_external_compliance where last_known_status in ('PENDING', 'RETRY') or last_known_status is null`
|
||||||
|
return db.manyOrNone(sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyRetryExternalCompliance(settings, customerId, service) {
|
||||||
|
const sql = 'SELECT phone FROM customers WHERE id=$1'
|
||||||
|
const promises = [db.one(sql, [customerId]), externalCompliance.createLink(settings, service, customerId)]
|
||||||
|
|
||||||
|
return Promise.all(promises)
|
||||||
|
.then(([toNumber, link]) => {
|
||||||
|
const body = `Your external compliance verification has failed. Please try again. Link for retry: ${link}`
|
||||||
|
|
||||||
|
return sms.sendMessage(settings, { toNumber, body })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyApprovedExternalCompliance(settings, customerId) {
|
||||||
|
const sql = 'SELECT phone FROM customers WHERE id=$1'
|
||||||
|
return db.one(sql, [customerId])
|
||||||
|
.then((toNumber) => {
|
||||||
|
const body = 'Your external compliance verification has been approved.'
|
||||||
|
|
||||||
|
return sms.sendMessage(settings, { toNumber, body })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkExternalCompliance(settings) {
|
||||||
|
return getOpenExternalCompliance()
|
||||||
|
.then(externals => {
|
||||||
|
console.log(externals)
|
||||||
|
const promises = _.map(external => {
|
||||||
|
return externalCompliance.getStatus(settings, external.service, external.customer_id)
|
||||||
|
.then(status => {
|
||||||
|
console.log('status', status, external.customer_id, external.service)
|
||||||
|
if (status.status.answer === RETRY) notifyRetryExternalCompliance(settings, external.customer_id, status.service)
|
||||||
|
if (status.status.answer === APPROVED) notifyApprovedExternalCompliance(settings, external.customer_id)
|
||||||
|
|
||||||
|
return updateExternalCompliance(external.customer_id, external.service, status.status.answer)
|
||||||
|
})
|
||||||
|
}, externals)
|
||||||
|
return Promise.all(promises)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function addExternalCompliance(customerId, service, id) {
|
||||||
|
const sql = `INSERT INTO customer_external_compliance (customer_id, external_id, service) VALUES ($1, $2, $3)`
|
||||||
|
return db.none(sql, [customerId, id, service])
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
add,
|
add,
|
||||||
addWithEmail,
|
addWithEmail,
|
||||||
|
|
@ -949,5 +1047,7 @@ module.exports = {
|
||||||
updateTxCustomerPhoto,
|
updateTxCustomerPhoto,
|
||||||
enableTestCustomer,
|
enableTestCustomer,
|
||||||
disableTestCustomer,
|
disableTestCustomer,
|
||||||
updateLastAuthAttempt
|
updateLastAuthAttempt,
|
||||||
|
addExternalCompliance,
|
||||||
|
checkExternalCompliance
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,6 +109,7 @@ type Trigger {
|
||||||
thresholdDays: Int
|
thresholdDays: Int
|
||||||
customInfoRequestId: String
|
customInfoRequestId: String
|
||||||
customInfoRequest: CustomInfoRequest
|
customInfoRequest: CustomInfoRequest
|
||||||
|
externalService: String
|
||||||
}
|
}
|
||||||
|
|
||||||
type TermsDetails {
|
type TermsDetails {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue