Merge branch 'release-10.0' into feat/lam-1062/save-unsuccessful-qr-scans
This commit is contained in:
commit
345e869826
339 changed files with 1007 additions and 81794 deletions
|
|
@ -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 settingsLoader = require('./new-settings-loader')
|
||||
const logger = require('./logger')
|
||||
const externalCompliance = require('./compliance-external')
|
||||
|
||||
const { APPROVED, RETRY } = require('./plugins/compliance/consts')
|
||||
|
||||
const TX_PASSTHROUGH_ERROR_CODES = ['operatorCancel', 'scoreThresholdReached', 'walletScoringError']
|
||||
|
||||
|
|
@ -243,7 +246,7 @@ function deleteEditedData (id, data) {
|
|||
'id_card_data',
|
||||
'id_card_photo',
|
||||
'us_ssn',
|
||||
'subcriber_info',
|
||||
'subscriber_info',
|
||||
'name'
|
||||
]
|
||||
const filteredData = _.pick(defaults, _.mapKeys(_.snakeCase, data))
|
||||
|
|
@ -322,6 +325,7 @@ function getById (id) {
|
|||
return db.oneOrNone(sql, [id])
|
||||
.then(assignCustomerData)
|
||||
.then(getCustomInfoRequestsData)
|
||||
.then(getExternalComplianceMachine)
|
||||
.then(camelize)
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +346,11 @@ function camelize (customer) {
|
|||
function camelizeDeep (customer) {
|
||||
return _.flow(
|
||||
camelize,
|
||||
it => ({ ...it, notes: (it.notes ?? []).map(camelize) })
|
||||
it => ({
|
||||
...it,
|
||||
notes: (it.notes ?? []).map(camelize),
|
||||
externalCompliance: (it.externalCompliance ?? []).map(camelize)
|
||||
})
|
||||
)(customer)
|
||||
}
|
||||
|
||||
|
|
@ -587,6 +595,7 @@ function getCustomerById (id) {
|
|||
return db.oneOrNone(sql, [passableErrorCodes, id])
|
||||
.then(assignCustomerData)
|
||||
.then(getCustomInfoRequestsData)
|
||||
.then(getExternalCompliance)
|
||||
.then(camelizeDeep)
|
||||
.then(formatSubscriberInfo)
|
||||
}
|
||||
|
|
@ -927,6 +936,95 @@ function updateLastAuthAttempt (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 = {
|
||||
add,
|
||||
addWithEmail,
|
||||
|
|
@ -950,5 +1048,7 @@ module.exports = {
|
|||
updateTxCustomerPhoto,
|
||||
enableTestCustomer,
|
||||
disableTestCustomer,
|
||||
updateLastAuthAttempt
|
||||
updateLastAuthAttempt,
|
||||
addExternalCompliance,
|
||||
checkExternalCompliance
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ type Trigger {
|
|||
thresholdDays: Int
|
||||
customInfoRequestId: String
|
||||
customInfoRequest: CustomInfoRequest
|
||||
externalService: String
|
||||
}
|
||||
|
||||
type TermsDetails {
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
## Running
|
||||
|
||||
Differences from main lamassu-admin:
|
||||
|
||||
- `bin/new-lamassu-register <username>` to add a user
|
||||
- `bin/insecure-dev.sh` to run the server
|
||||
|
|
@ -15,6 +15,7 @@ const ID_VERIFIER = 'idVerifier'
|
|||
const EMAIL = 'email'
|
||||
const ZERO_CONF = 'zeroConf'
|
||||
const WALLET_SCORING = 'wallet_scoring'
|
||||
const COMPLIANCE = 'compliance'
|
||||
|
||||
const ALL_ACCOUNTS = [
|
||||
{ code: 'bitfinex', display: 'Bitfinex', class: TICKER, cryptos: bitfinex.CRYPTO },
|
||||
|
|
@ -54,13 +55,16 @@ const ALL_ACCOUNTS = [
|
|||
{ code: 'twilio', display: 'Twilio', class: SMS },
|
||||
{ code: 'telnyx', display: 'Telnyx', class: SMS },
|
||||
{ code: 'vonage', display: 'Vonage', class: SMS },
|
||||
{ code: 'inforu', display: 'InforU', class: SMS },
|
||||
{ code: 'mailgun', display: 'Mailgun', class: EMAIL },
|
||||
{ code: 'mock-email', display: 'Mock Email', class: EMAIL, dev: true },
|
||||
{ code: 'none', display: 'None', class: ZERO_CONF, cryptos: ALL_CRYPTOS },
|
||||
{ code: 'blockcypher', display: 'Blockcypher', class: ZERO_CONF, cryptos: [BTC] },
|
||||
{ code: 'mock-zero-conf', display: 'Mock 0-conf', class: ZERO_CONF, cryptos: ALL_CRYPTOS, dev: true },
|
||||
{ code: 'scorechain', display: 'Scorechain', class: WALLET_SCORING, cryptos: [BTC, ETH, LTC, BCH, DASH, USDT, USDT_TRON, TRX] },
|
||||
{ code: 'mock-scoring', display: 'Mock scoring', class: WALLET_SCORING, cryptos: ALL_CRYPTOS, dev: true }
|
||||
{ code: 'mock-scoring', display: 'Mock scoring', class: WALLET_SCORING, cryptos: ALL_CRYPTOS, dev: true },
|
||||
{ code: 'sumsub', display: 'Sumsub', class: COMPLIANCE },
|
||||
{ code: 'mock-compliance', display: 'Mock Compliance', class: COMPLIANCE, dev: true },
|
||||
]
|
||||
|
||||
const devMode = require('minimist')(process.argv.slice(2)).dev
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ const typeDef = gql`
|
|||
customInfoRequests: [CustomRequestData]
|
||||
notes: [CustomerNote]
|
||||
isTestCustomer: Boolean
|
||||
externalCompliance: [JSONObject]
|
||||
}
|
||||
|
||||
input CustomerInput {
|
||||
|
|
|
|||
|
|
@ -26,9 +26,12 @@ const SECRET_FIELDS = [
|
|||
'twilio.authToken',
|
||||
'telnyx.apiKey',
|
||||
'vonage.apiSecret',
|
||||
'inforu.apiKey',
|
||||
'galoy.walletId',
|
||||
'galoy.apiSecret',
|
||||
'bitfinex.secret'
|
||||
'bitfinex.secret',
|
||||
'sumsub.apiToken',
|
||||
'sumsub.privateKey'
|
||||
]
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ function transactionNotify (tx, rec) {
|
|||
}
|
||||
|
||||
function complianceNotify (settings, customer, deviceId, action, period) {
|
||||
const timestamp = (new Date()).toLocaleString()
|
||||
return queries.getMachineName(deviceId)
|
||||
.then(machineName => {
|
||||
const notifications = configManager.getGlobalNotifications(settings.config)
|
||||
|
|
@ -179,15 +180,15 @@ function complianceNotify (settings, customer, deviceId, action, period) {
|
|||
|
||||
const rec = {
|
||||
sms: {
|
||||
body: `Customer ${customer.phone} ${msgCore[action]} - ${machineName}`
|
||||
body: `Customer ${customer.phone} ${msgCore[action]} - ${machineName}. ${timestamp}`
|
||||
},
|
||||
email: {
|
||||
subject: `Customer compliance`,
|
||||
body: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}`
|
||||
body: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}. ${timestamp}`
|
||||
},
|
||||
webhook: {
|
||||
topic: `Customer compliance`,
|
||||
content: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}`
|
||||
content: `Customer ${customer.phone} ${msgCore[action]} in machine ${machineName}. ${timestamp}`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +208,7 @@ function complianceNotify (settings, customer, deviceId, action, period) {
|
|||
if (smsActive) promises.push(smsFuncs.sendMessage(settings, rec))
|
||||
if (webhookActive) promises.push(webhookFuncs.sendMessage(settings, rec))
|
||||
|
||||
notifyIfActive('compliance', 'customerComplianceNotify', customer, deviceId, action, period)
|
||||
notifyIfActive('compliance', 'customerComplianceNotify', customer, deviceId, action, machineName, period)
|
||||
|
||||
return Promise.all(promises)
|
||||
.catch(err => console.error(`An error occurred when sending a notification. Please check your notification preferences and 3rd party account configuration: ${err.stack}`))
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const clearOldCustomerSuspendedNotifications = (customerId, deviceId) => {
|
|||
return queries.invalidateNotification(detailB, 'compliance')
|
||||
}
|
||||
|
||||
const customerComplianceNotify = (customer, deviceId, code, days = null) => {
|
||||
const customerComplianceNotify = (customer, deviceId, code, machineName, days = null) => {
|
||||
// code for now can be "BLOCKED", "SUSPENDED"
|
||||
const detailB = utils.buildDetail({ customerId: customer.id, code, deviceId })
|
||||
const date = new Date()
|
||||
|
|
@ -40,7 +40,7 @@ const customerComplianceNotify = (customer, deviceId, code, days = null) => {
|
|||
}
|
||||
const message = code === 'SUSPENDED' ? `Customer ${customer.phone} suspended until ${date.toLocaleString()}` :
|
||||
code === 'BLOCKED' ? `Customer ${customer.phone} blocked` :
|
||||
`Customer ${customer.phone} has pending compliance`
|
||||
`Customer ${customer.phone} has pending compliance in machine ${machineName}`
|
||||
|
||||
return clearOldCustomerSuspendedNotifications(customer.id, deviceId)
|
||||
.then(() => queries.getValidNotifications(COMPLIANCE, detailB))
|
||||
|
|
|
|||
|
|
@ -1,20 +1,15 @@
|
|||
const fs = require('fs')
|
||||
const { readdir } = require('fs/promises')
|
||||
const path = require('path')
|
||||
const util = require('util')
|
||||
const loader = require('./loading')
|
||||
const matcher = require('./matching')
|
||||
const nameUtils = require('./name-utils')
|
||||
const _ = require('lodash/fp')
|
||||
const logger = require('../logger')
|
||||
|
||||
const debugLog = require('../pp')(__filename) // KOSTIS TODO: remove
|
||||
|
||||
const OFAC_DATA_DIR = process.env.OFAC_DATA_DIR
|
||||
|
||||
let structs = null
|
||||
|
||||
const readdir = util.promisify(fs.readdir)
|
||||
|
||||
function load () {
|
||||
if (!OFAC_DATA_DIR) {
|
||||
const message = 'The ofacDataDir option has not been set in the environment'
|
||||
|
|
@ -43,8 +38,6 @@ function makeCompatible (nameParts) {
|
|||
}
|
||||
|
||||
function match (nameParts, birthDateString, options) {
|
||||
const {debug} = options
|
||||
|
||||
if (!structs) {
|
||||
logger.error(new Error('The OFAC data sources have not been loaded yet.'))
|
||||
return false
|
||||
|
|
@ -69,10 +62,7 @@ function match (nameParts, birthDateString, options) {
|
|||
])(birthDateString)
|
||||
|
||||
const candidate = {parts, fullName, words, birthDate}
|
||||
debug && debugLog(candidate)
|
||||
|
||||
const result = matcher.match(structs, candidate, options)
|
||||
debug && debugLog(result)
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const jaro = require('talisman/metrics/distance/jaro')
|
||||
const _ = require('lodash/fp')
|
||||
|
||||
const debugLog = require('../pp')(__filename) // KOSTIS TODO: remove
|
||||
const logger = require('../logger')
|
||||
|
||||
const stringSimilarity = _.curry(jaro)
|
||||
|
|
@ -30,7 +29,7 @@ const isBornTooLongSince = _.curry((days, dateObject, individual) => {
|
|||
// algorithm
|
||||
|
||||
function match (structs, candidate, options) {
|
||||
const {threshold, fullNameThreshold, ratio = 0.5, debug, verboseFor} = options
|
||||
const {threshold, fullNameThreshold, ratio = 0.5, verboseFor} = options
|
||||
const {fullName, words, birthDate} = candidate
|
||||
|
||||
// Accept aliases who's full name matches.
|
||||
|
|
@ -90,9 +89,6 @@ function match (structs, candidate, options) {
|
|||
_.map(_.first)
|
||||
)(matches)
|
||||
|
||||
debug && debugLog(aliasIdsFromFullName)
|
||||
debug && debugLog(aliasIdsFromNamePart)
|
||||
|
||||
// Get the full record for each matched id
|
||||
const getIndividual = aliasId => {
|
||||
const individualId = structs.aliasToIndividual.get(aliasId)
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ const nameUtils = require('./name-utils')
|
|||
const logger = require('../logger')
|
||||
const _ = require('lodash/fp')
|
||||
|
||||
const debug_log = require('../pp')(__filename) // KOSTIS TODO: remove
|
||||
|
||||
// KOSTIS TODO: get these from the document itself
|
||||
const INDIVIDUAL = '4'
|
||||
const NAME = '1403'
|
||||
|
|
@ -132,8 +130,6 @@ function processProfile (profileNode) {
|
|||
const birthDatePeriods = mapCompact(processFeature, profileNode.Feature)
|
||||
const individual = {id, aliases, birthDatePeriods}
|
||||
|
||||
// debug_log(individual)
|
||||
|
||||
return individual
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
const parser = require('./parsing')
|
||||
const https = require('https')
|
||||
const url = require('url')
|
||||
const fs = require('fs')
|
||||
const URL = require('url')
|
||||
const { createWriteStream } = require('fs')
|
||||
const fs = require('fs/promises')
|
||||
const { readFile, writeFile, rename, unlink } = fs
|
||||
const path = require('path')
|
||||
const util = require('util')
|
||||
const _ = require('lodash/fp')
|
||||
const logger = require('../logger')
|
||||
|
||||
|
|
@ -14,36 +15,17 @@ const OFAC_SOURCES_NAMES = process.env.OFAC_SOURCES_NAMES.split(',')
|
|||
const OFAC_SOURCES_URLS = process.env.OFAC_SOURCES_URLS.split(',')
|
||||
|
||||
const ofacSources = _.map(
|
||||
it => ({
|
||||
name: it[0],
|
||||
url: it[1]
|
||||
}),
|
||||
([name, url]) => ({ name, url }),
|
||||
_.zip(OFAC_SOURCES_NAMES, OFAC_SOURCES_URLS)
|
||||
)
|
||||
|
||||
function mkdir (path) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.mkdir(path, err => {
|
||||
if (!err) return resolve()
|
||||
if (err.code === 'EEXIST') return resolve()
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
const mkdir = path =>
|
||||
fs.mkdir(path)
|
||||
.catch(err => err.code === 'EEXIST' ? Promise.resolve() : Promise.reject(err))
|
||||
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
const writeFile = util.promisify(fs.writeFile)
|
||||
const rename = util.promisify(fs.rename)
|
||||
const unlink = util.promisify(fs.unlink)
|
||||
|
||||
const remove = file => {
|
||||
return unlink(file)
|
||||
}
|
||||
|
||||
const promiseGetEtag = (source) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {url: sourceUrl} = source
|
||||
const parsed = url.parse(sourceUrl)
|
||||
const promiseGetEtag = ({ url }) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const parsed = URL.parse(url)
|
||||
const requestOptions = {
|
||||
hostname: parsed.hostname,
|
||||
path: parsed.path,
|
||||
|
|
@ -59,26 +41,24 @@ const promiseGetEtag = (source) => {
|
|||
|
||||
request.end()
|
||||
})
|
||||
}
|
||||
|
||||
const download = _.curry((dstDir, source) => {
|
||||
const {name, url: sourceUrl} = source
|
||||
const download = (dstDir, { name, url }) => {
|
||||
const dstFile = path.join(dstDir, name + '.xml')
|
||||
const file = fs.createWriteStream(dstFile)
|
||||
const file = createWriteStream(dstFile)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = https.get(sourceUrl, response => {
|
||||
const request = https.get(url, response => {
|
||||
response.pipe(file)
|
||||
file.on('finish', () => file.close(() => resolve(dstFile)))
|
||||
})
|
||||
|
||||
request.on('error', reject)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const parseToJson = srcFile => {
|
||||
const dstFile = srcFile.replace(/\.xml$/, '.json')
|
||||
const writeStream = fs.createWriteStream(dstFile)
|
||||
const writeStream = createWriteStream(dstFile)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
parser.parse(srcFile, (err, profile) => {
|
||||
|
|
@ -137,7 +117,7 @@ function update () {
|
|||
const promiseNewEtags = Promise.resolve(ofacSources || [])
|
||||
.then(sources => Promise.all(_.map(promiseGetEtag, sources))
|
||||
.then(etags => _.map(
|
||||
([source, etag]) => ({...source, etag}),
|
||||
([source, etag]) => _.set('etag', etag, source),
|
||||
_.zip(sources, etags)
|
||||
))
|
||||
)
|
||||
|
|
@ -166,7 +146,7 @@ function update () {
|
|||
return Promise.all(downloads)
|
||||
.then(parsed => {
|
||||
const moves = _.map(src => moveToSourcesDir(src, OFAC_SOURCES_DIR), parsed)
|
||||
const deletions = _.map(remove, missing)
|
||||
const deletions = _.map(unlink, missing)
|
||||
const updateEtags = writeFile(OFAC_ETAGS_FILE, etagsJson)
|
||||
|
||||
return Promise.all([updateEtags, ...moves, ...deletions])
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ const pluginCodes = {
|
|||
LAYER2: 'layer2',
|
||||
SMS: 'sms',
|
||||
EMAIL: 'email',
|
||||
ZERO_CONF: 'zero-conf'
|
||||
ZERO_CONF: 'zero-conf',
|
||||
COMPLIANCE: 'compliance'
|
||||
}
|
||||
|
||||
module.exports = _.assign({load}, pluginCodes)
|
||||
|
|
|
|||
6
lib/plugins/compliance/consts.js
Normal file
6
lib/plugins/compliance/consts.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = {
|
||||
PENDING: 'PENDING',
|
||||
RETRY: 'RETRY',
|
||||
APPROVED: 'APPROVED',
|
||||
REJECTED: 'REJECTED'
|
||||
}
|
||||
31
lib/plugins/compliance/mock-compliance/mock-compliance.js
Normal file
31
lib/plugins/compliance/mock-compliance/mock-compliance.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
const uuid = require('uuid')
|
||||
|
||||
const {APPROVED} = require('../consts')
|
||||
|
||||
const CODE = 'mock-compliance'
|
||||
|
||||
const createLink = (settings, userId, level) => {
|
||||
return `this is a mock external link, ${userId}, ${level}`
|
||||
}
|
||||
|
||||
const getApplicantStatus = (account, userId) => {
|
||||
return Promise.resolve({
|
||||
service: CODE,
|
||||
status: {
|
||||
level: account.applicantLevel, answer: APPROVED
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const createApplicant = () => {
|
||||
return Promise.resolve({
|
||||
id: uuid.v4()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CODE,
|
||||
createApplicant,
|
||||
getApplicantStatus,
|
||||
createLink
|
||||
}
|
||||
34
lib/plugins/compliance/sumsub/request.js
Normal file
34
lib/plugins/compliance/sumsub/request.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
const axios = require('axios')
|
||||
const crypto = require('crypto')
|
||||
const _ = require('lodash/fp')
|
||||
const FormData = require('form-data')
|
||||
|
||||
const axiosConfig = {
|
||||
baseURL: 'https://api.sumsub.com'
|
||||
}
|
||||
|
||||
const getSigBuilder = (apiToken, secretKey) => config => {
|
||||
const timestamp = Math.floor(Date.now() / 1000)
|
||||
const signature = crypto.createHmac('sha256', secretKey)
|
||||
|
||||
signature.update(`${timestamp}${_.toUpper(config.method)}${config.url}`)
|
||||
if (config.data instanceof FormData) {
|
||||
signature.update(config.data.getBuffer())
|
||||
} else if (config.data) {
|
||||
signature.update(JSON.stringify(config.data))
|
||||
}
|
||||
|
||||
config.headers['X-App-Token'] = apiToken
|
||||
config.headers['X-App-Access-Sig'] = signature.digest('hex')
|
||||
config.headers['X-App-Access-Ts'] = timestamp
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
const request = ((account, config) => {
|
||||
const instance = axios.create(axiosConfig)
|
||||
instance.interceptors.request.use(getSigBuilder(account.apiToken, account.secretKey), Promise.reject)
|
||||
return instance(config)
|
||||
})
|
||||
|
||||
module.exports = request
|
||||
98
lib/plugins/compliance/sumsub/sumsub.api.js
Normal file
98
lib/plugins/compliance/sumsub/sumsub.api.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
const request = require('./request')
|
||||
|
||||
const createApplicant = (account, userId, level) => {
|
||||
if (!userId || !level) {
|
||||
return Promise.reject(`Missing required fields: userId: ${userId}, level: ${level}`)
|
||||
}
|
||||
|
||||
const config = {
|
||||
method: 'POST',
|
||||
url: `/resources/applicants?levelName=${level}`,
|
||||
data: {
|
||||
externalUserId: userId,
|
||||
sourceKey: 'lamassu'
|
||||
},
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
return request(account, config)
|
||||
}
|
||||
|
||||
const createLink = (account, userId, level) => {
|
||||
if (!userId || !level) {
|
||||
return Promise.reject(`Missing required fields: userId: ${userId}, level: ${level}`)
|
||||
}
|
||||
|
||||
const config = {
|
||||
method: 'POST',
|
||||
url: `/resources/sdkIntegrations/levels/${level}/websdkLink?ttlInSecs=${600}&externalUserId=${userId}`,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
return request(account, config)
|
||||
}
|
||||
|
||||
const getApplicantByExternalId = (account, id) => {
|
||||
if (!id) {
|
||||
return Promise.reject('Missing required fields: id')
|
||||
}
|
||||
|
||||
const config = {
|
||||
method: 'GET',
|
||||
url: `/resources/applicants/-;externalUserId=${id}/one`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
return request(account, config)
|
||||
}
|
||||
|
||||
const getApplicantStatus = (account, id) => {
|
||||
if (!id) {
|
||||
return Promise.reject(`Missing required fields: id`)
|
||||
}
|
||||
|
||||
const config = {
|
||||
method: 'GET',
|
||||
url: `/resources/applicants/${id}/status`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
return request(account, config)
|
||||
}
|
||||
|
||||
const getApplicantById = (account, id) => {
|
||||
if (!id) {
|
||||
return Promise.reject(`Missing required fields: id`)
|
||||
}
|
||||
|
||||
const config = {
|
||||
method: 'GET',
|
||||
url: `/resources/applicants/${id}/one`,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
return request(account, config)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createLink,
|
||||
createApplicant,
|
||||
getApplicantByExternalId,
|
||||
getApplicantById,
|
||||
getApplicantStatus
|
||||
}
|
||||
52
lib/plugins/compliance/sumsub/sumsub.js
Normal file
52
lib/plugins/compliance/sumsub/sumsub.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
const _ = require('lodash/fp')
|
||||
|
||||
const sumsubApi = require('./sumsub.api')
|
||||
const { PENDING, RETRY, APPROVED, REJECTED } = require('../consts')
|
||||
|
||||
const CODE = 'sumsub'
|
||||
|
||||
const getApplicantByExternalId = (account, userId) => {
|
||||
return sumsubApi.getApplicantByExternalId(account, userId)
|
||||
.then(r => r.data)
|
||||
}
|
||||
|
||||
const createApplicant = (account, userId, level) => {
|
||||
return sumsubApi.createApplicant(account, userId, level)
|
||||
.then(r => r.data)
|
||||
.catch(err => {
|
||||
if (err.response.status === 409) return getApplicantByExternalId(account, userId)
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
const createLink = (account, userId, level) => {
|
||||
return sumsubApi.createLink(account, userId, level)
|
||||
.then(r => r.data.url)
|
||||
}
|
||||
|
||||
const getApplicantStatus = (account, userId) => {
|
||||
return sumsubApi.getApplicantByExternalId(account, userId)
|
||||
.then(r => {
|
||||
const levelName = _.get('data.review.levelName', r)
|
||||
const reviewStatus = _.get('data.review.reviewStatus', r)
|
||||
const reviewAnswer = _.get('data.review.reviewResult.reviewAnswer', r)
|
||||
const reviewRejectType = _.get('data.review.reviewResult.reviewRejectType', r)
|
||||
|
||||
// if last review was from a different level, return the current level and RETRY
|
||||
if (levelName !== account.applicantLevel) return { level: account.applicantLevel, answer: RETRY }
|
||||
|
||||
let answer = PENDING
|
||||
if (reviewAnswer === 'GREEN' && reviewStatus === 'completed') answer = APPROVED
|
||||
if (reviewAnswer === 'RED' && reviewRejectType === 'RETRY') answer = RETRY
|
||||
if (reviewAnswer === 'RED' && reviewRejectType === 'FINAL') answer = REJECTED
|
||||
|
||||
return { level: levelName, answer }
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CODE,
|
||||
createApplicant,
|
||||
getApplicantStatus,
|
||||
createLink
|
||||
}
|
||||
51
lib/plugins/sms/inforu/inforu.js
Normal file
51
lib/plugins/sms/inforu/inforu.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
const axios = require('axios')
|
||||
|
||||
const NAME = 'InforU'
|
||||
|
||||
function sendMessage (account, rec) {
|
||||
const username = account.username
|
||||
const apiKey = account.apiKey
|
||||
|
||||
const to = rec.sms.toNumber || account.toNumber
|
||||
const text = rec.sms.body
|
||||
const from = account.fromNumber
|
||||
|
||||
const url = 'https://capi.inforu.co.il/api/v2/SMS/SendSms'
|
||||
|
||||
const config = {
|
||||
auth: {
|
||||
username: username,
|
||||
password: apiKey
|
||||
},
|
||||
maxBodyLength: Infinity,
|
||||
headers:{
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
Message: text,
|
||||
Recipients: [{
|
||||
Phone: to
|
||||
}],
|
||||
Settings: {
|
||||
Sender: from
|
||||
}
|
||||
}
|
||||
|
||||
axios.post(url, data, config)
|
||||
.catch(err => {
|
||||
// console.log(err)
|
||||
throw new Error(`inforu error: ${err.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
function getLookup () {
|
||||
throw new Error('inforu error: lookup not supported')
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NAME,
|
||||
sendMessage,
|
||||
getLookup
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ const T = require('./time')
|
|||
const logger = require('./logger')
|
||||
const cashOutTx = require('./cash-out/cash-out-tx')
|
||||
const cashInTx = require('./cash-in/cash-in-tx')
|
||||
const customers = require('./customers')
|
||||
const sanctionsUpdater = require('./ofac/update')
|
||||
const sanctions = require('./ofac/index')
|
||||
const coinAtmRadar = require('./coinatmradar/coinatmradar')
|
||||
|
|
@ -34,6 +35,7 @@ const PRUNE_MACHINES_HEARTBEAT = 1 * T.day
|
|||
const TRANSACTION_BATCH_LIFECYCLE = 20 * T.minutes
|
||||
const TICKER_RATES_INTERVAL = 59 * T.seconds
|
||||
const FAILED_SCANS_INTERVAL = 1 * T.day
|
||||
const EXTERNAL_COMPLIANCE_INTERVAL = 1 * T.minutes
|
||||
|
||||
const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds
|
||||
const PENDING_INTERVAL = 10 * T.seconds
|
||||
|
|
@ -203,6 +205,10 @@ const cleanOldFailedQRScans = () => {
|
|||
})
|
||||
}
|
||||
|
||||
// function checkExternalCompliance (settings) {
|
||||
// return customers.checkExternalCompliance(settings)
|
||||
// }
|
||||
|
||||
function initializeEachSchema (schemas = ['public']) {
|
||||
// for each schema set "thread variables" and do polling
|
||||
return _.forEach(schema => {
|
||||
|
|
@ -266,6 +272,7 @@ function doPolling (schema) {
|
|||
pi().sweepHd()
|
||||
notifier.checkNotification(pi())
|
||||
updateCoinAtmRadar()
|
||||
// checkExternalCompliance(settings())
|
||||
|
||||
addToQueue(pi().getRawRates, TICKER_RATES_INTERVAL, schema, QUEUE.FAST)
|
||||
addToQueue(pi().executeTrades, TRADE_INTERVAL, schema, QUEUE.FAST)
|
||||
|
|
@ -284,6 +291,7 @@ function doPolling (schema) {
|
|||
addToQueue(pi().pruneMachinesHeartbeat, PRUNE_MACHINES_HEARTBEAT, schema, QUEUE.SLOW, settings)
|
||||
addToQueue(cleanOldFailedQRScans, FAILED_SCANS_INTERVAL, schema, QUEUE.SLOW, settings)
|
||||
addToQueue(cleanOldFailedPDF417Scans, FAILED_SCANS_INTERVAL, schema, QUEUE.SLOW, settings)
|
||||
// addToQueue(checkExternalCompliance, EXTERNAL_COMPLIANCE_INTERVAL, schema, QUEUE.SLOW, settings)
|
||||
}
|
||||
|
||||
function setup (schemasToAdd = [], schemasToRemove = []) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const plugins = require('../plugins')
|
|||
const Tx = require('../tx')
|
||||
const loyalty = require('../loyalty')
|
||||
const logger = require('../logger')
|
||||
const externalCompliance = require('../compliance-external')
|
||||
|
||||
function updateCustomerCustomInfoRequest (customerId, patch) {
|
||||
const promise = _.isNil(patch.data) ?
|
||||
|
|
@ -234,6 +235,28 @@ function sendSmsReceipt (req, res, next) {
|
|||
})
|
||||
}
|
||||
|
||||
function getExternalComplianceLink (req, res, next) {
|
||||
const customerId = req.query.customer
|
||||
const triggerId = req.query.trigger
|
||||
const isRetry = req.query.isRetry
|
||||
if (_.isNil(customerId) || _.isNil(triggerId)) return next(httpError('Not Found', 404))
|
||||
|
||||
const settings = req.settings
|
||||
const triggers = configManager.getTriggers(settings.config)
|
||||
const trigger = _.find(it => it.id === triggerId)(triggers)
|
||||
const externalService = trigger.externalService
|
||||
|
||||
if (isRetry) {
|
||||
return externalCompliance.createLink(settings, externalService, customerId)
|
||||
.then(url => respond(req, res, { url }))
|
||||
}
|
||||
|
||||
return externalCompliance.createApplicant(settings, externalService, customerId)
|
||||
.then(applicant => customers.addExternalCompliance(customerId, externalService, applicant.id))
|
||||
.then(() => externalCompliance.createLink(settings, externalService, customerId))
|
||||
.then(url => respond(req, res, { url }))
|
||||
}
|
||||
|
||||
function addOrUpdateCustomer (customerData, config, isEmailAuth) {
|
||||
const triggers = configManager.getTriggers(config)
|
||||
const maxDaysThreshold = complianceTriggers.maxDaysThreshold(triggers)
|
||||
|
|
@ -311,6 +334,7 @@ router.patch('/:id/suspend', triggerSuspend)
|
|||
router.patch('/:id/photos/idcarddata', updateIdCardData)
|
||||
router.patch('/:id/:txId/photos/customerphoto', updateTxCustomerPhoto)
|
||||
router.post('/:id/smsreceipt', sendSmsReceipt)
|
||||
router.get('/external', getExternalComplianceLink)
|
||||
router.post('/phone_code', getOrAddCustomerPhone)
|
||||
router.post('/email_code', getOrAddCustomerEmail)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue