main
Sean Rabaut 6 years ago
parent bc3d5e85a9
commit d472434388

@ -1,7 +0,0 @@
TWITTER_CONSUMER_KEY=
TWITTER_CONSUMER_SECRET=
TWITTER_ACCESS_TOKEN_KEY=
TWITTER_ACCESS_TOKEN_SECRET=
UNSPLASH_ACCESS_KEY=
UNSPLASH_SECRET_KEY=
UNSPLASH_CALLBACK_URL=

@ -1,38 +0,0 @@
FROM node:9-alpine
# Source https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md
# Installs latest Chromium package.
ENV CHROME_BIN=/usr/bin/chromium-browser
RUN apk update && apk upgrade && \
echo @edge http://nl.alpinelinux.org/alpine/edge/community >> /etc/apk/repositories && \
echo @edge http://nl.alpinelinux.org/alpine/edge/main >> /etc/apk/repositories && \
apk add --no-cache \
chromium@edge \
nss@edge \
freetype@edge \
harfbuzz@edge
WORKDIR /app
COPY package.json ./
COPY yarn.lock ./
# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
RUN yarn
COPY . .
# Add user so we don't need --no-sandbox.
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
&& mkdir -p /home/pptruser/Downloads \
&& chown -R pptruser:pptruser /home/pptruser \
&& chown -R pptruser:pptruser /app
# Run everything after as non-privileged user.
USER pptruser
ENV NODE_ENV production
EXPOSE 4000
CMD [ "node", "server.js" ]

@ -1,75 +0,0 @@
/* global domtoimage */
const ARBITRARY_WAIT_TIME = 250
module.exports = browser => async (req, res) => {
const page = await browser.newPage()
const { state } = req.body
if (!state) res.status(400).send('Invalid Request')
try {
const path = require.resolve('dom-to-image')
await page.goto(`http://carbon.now.sh?state=${state}`)
await page.addScriptTag({ path })
// wait for page to detect language
await delay(ARBITRARY_WAIT_TIME)
const targetElement = await page.$('.export-container')
const dataUrl = await page.evaluate((target = document) => {
const query = new URLSearchParams(document.location.search.slice(1))
const EXPORT_SIZES_HASH = {
'1x': '1',
'2x': '2',
'4x': '4'
}
const exportSize = EXPORT_SIZES_HASH[query.get('es')] || '2'
target.querySelectorAll('span[role="presentation"]').forEach(node => {
if (node.innerText && node.innerText.match(/%\S\S/)) {
node.innerText = encodeURIComponent(node.innerText)
}
})
const width = target.offsetWidth * exportSize
const height = query.get('si')
? target.offsetWidth * exportSize
: target.offsetHeight * exportSize
const config = {
style: {
transform: `scale(${exportSize})`,
'transform-origin': 'center',
background: query.get('si') ? query.get('bg') : 'none'
},
filter: n => {
if (n.className) {
return String(n.className).indexOf('eliminateOnRender') < 0
}
return true
},
width,
height
}
return domtoimage.toPng(target, config)
}, targetElement)
res.status(200).send(dataUrl)
} catch (e) {
// eslint-disable-next-line
console.error(e)
res.status(500).send()
} finally {
await page.close()
}
}
// private
function delay(ms) {
return new Promise(r => setTimeout(r, ms))
}

@ -1,44 +0,0 @@
/*
* See oEmbed standard here: https://oembed.com/
*/
const url = require('url')
const toIFrame = (url, width, height) =>
`<iframe
src="https://carbon.now.sh/embed${url}"
style="transform:scale(0.7); width:${width}px; height:${height}px; border:0; overflow:hidden;"
sandbox="allow-scripts allow-same-origin">
</iframe>
`
module.exports = (req, res) => {
let embedUrl = req.query.url
try {
embedUrl = decodeURIComponent(req.query.url)
} catch (e) {
// eslint-disable-next-line
console.log(e)
/* URL is already decoded */
}
try {
const { query } = url.parse(embedUrl)
const width = Math.min(Number(req.query.maxwidth) || Infinity, 1024)
const height = Math.min(Number(req.query.maxheight) || Infinity, 473)
const obj = {
version: '1.0',
type: 'rich',
provider_name: 'Carbon',
width,
height,
html: toIFrame(`?${query}`, width, height)
}
return res.json(obj)
} catch (e) {
return res.status(500).send()
}
}

@ -1,70 +0,0 @@
const Twitter = require('twit')
const morph = require('morphmorph')
const RATE_LIMIT_CODE = 420
const MAX_ALT_TEXT_LENGTH = 420
let client
try {
client = new Twitter({
consumer_key: process.env.TWITTER_CONSUMER_KEY,
consumer_secret: process.env.TWITTER_CONSUMER_SECRET,
access_token: process.env.TWITTER_ACCESS_TOKEN_KEY,
access_token_secret: process.env.TWITTER_ACCESS_TOKEN_SECRET
})
} catch (e) {
// eslint-disable-next-line
console.warn(e.message)
}
const extractMediaId = morph.get('data.media_id_string')
const extractImageUrl = morph.get('data.entities.media.0.display_url')
const extractErrorCode = morph.get('0.code')
const uploadImage = data => client.post('media/upload', { media_data: data })
const uploadMetadata = (altText, twitterRes = {}) => {
if (!altText) return twitterRes
const formattedAltText =
altText.length > MAX_ALT_TEXT_LENGTH
? `${altText.slice(0, MAX_ALT_TEXT_LENGTH - 3)}...`
: altText
return client
.post('media/metadata/create', {
media_id: extractMediaId(twitterRes),
alt_text: { text: formattedAltText }
})
.then(() => twitterRes)
}
const uploadTweet = (twitterRes = {}) =>
client.post('statuses/update', {
status: `Carbon Copy #${extractMediaId(twitterRes).slice(0, 8)}`,
media_ids: extractMediaId(twitterRes)
})
const respondSuccess = (res, url) => res.json({ url })
const respondFail = (res, err) => {
const errorCode = extractErrorCode(err)
// check for rate limit
if (errorCode === RATE_LIMIT_CODE) {
return res.status(420).send()
}
// eslint-disable-next-line
console.error(`Error: ${err.message || JSON.stringify(err, null, 2)}`)
return res.status(500).send()
}
module.exports = (req, res) => {
if (!req.body.imageData) {
return res.status(400).send()
}
return uploadImage(req.body.imageData)
.then(uploadMetadata.bind(null, req.body.altText))
.then(uploadTweet)
.then(extractImageUrl)
.then(respondSuccess.bind(null, res))
.catch(respondFail.bind(null, res))
}

@ -1,40 +0,0 @@
require('isomorphic-fetch')
const { default: Unsplash, toJson } = require('unsplash-js')
const WALLPAPER_COLLECTION_ID = 136026
const client = new Unsplash({
applicationId: process.env.UNSPLASH_ACCESS_KEY,
secret: process.env.UNSPLASH_SECRET_KEY,
callbackUrl: process.env.UNSPLASH_CALLBACK_URL
})
const parseImageResult = img => ({
id: img.id,
photographer: {
name: img.user.name,
profile_url: img.user.links.html
},
url: img.urls.small
})
const getRandomImages = () =>
client.photos
.getRandomPhoto({
collections: [WALLPAPER_COLLECTION_ID],
count: 20
})
.then(toJson)
.then(imgs => imgs.map(parseImageResult))
const downloadImage = imageId =>
client.photos
.getPhoto(imageId)
.then(toJson)
.then(client.photos.downloadPhoto)
.then(toJson)
module.exports = {
randomImages: (req, res) => getRandomImages().then(imgs => res.json(imgs)),
downloadImage: (req, res) => downloadImage(req.params.imageId).then(url => res.json(url))
}

@ -1,24 +0,0 @@
{
"name": "carbon-api",
"alias": "carbon-api.now.sh",
"type": "docker",
"public": true,
"version": 1,
"env": {
"NODE_ENV": "production",
"TWITTER_CONSUMER_KEY": "@twitter-consumer-key",
"TWITTER_CONSUMER_SECRET": "@twitter-consumer-secret",
"TWITTER_ACCESS_TOKEN_KEY": "@twitter-access-token-key",
"TWITTER_ACCESS_TOKEN_SECRET": "@twitter-access-token-secret",
"LOGS_SECRET_PREFIX": "@logs_secret_prefix",
"UNSPLASH_SECRET_KEY": "@unsplash_secret_key",
"UNSPLASH_ACCESS_KEY": "@unsplash_access_key",
"UNSPLASH_CALLBACK_URL": "@unsplash_callback_url"
},
"features": {
"cloud": "v1"
},
"github": {
"autoAlias": false
}
}

@ -1,25 +0,0 @@
{
"name": "carbon-api",
"version": "0.0.0-semantically-released",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "node server.js",
"start": "node server.js",
"deploy": "now"
},
"dependencies": {
"body-parser": "^1.17.2",
"compression": "^1.7.3",
"cors": "^2.8.5",
"dom-to-image": "^2.6.0",
"express": "^4.16.4",
"isomorphic-fetch": "^2.2.1",
"morgan": "^1.9.1",
"morphmorph": "^0.1.2",
"now-logs": "^0.0.7",
"puppeteer": "^1.10.0",
"twit": "^2.2.9",
"unsplash-js": "^4.8.0"
}
}

@ -1,69 +0,0 @@
const express = require('express')
const cors = require('cors')
const compression = require('compression')
const morgan = require('morgan')
const bodyParser = require('body-parser')
const puppeteer = require('puppeteer')
const port = parseInt(process.env.PORT, 10) || 4000
const dev = process.env.NODE_ENV !== 'production'
process.on('SIGINT', process.exit)
if (!dev) {
const LOGS_ID = `${process.env.LOGS_SECRET_PREFIX}:${process.env.NOW_URL}`
require('now-logs')(LOGS_ID)
}
function wrap(handler) {
return (req, res) =>
handler(req, res).catch(err => {
// eslint-disable-next-line
console.log('ERR:', err)
res.status(400).end()
})
}
const puppeteerParams = dev
? {}
: {
executablePath: '/usr/bin/chromium-browser',
args: ['--no-sandbox', '--disable-setuid-sandbox']
}
puppeteer.launch(puppeteerParams).then(browser => {
// set up
const server = express()
const imageHandler = require('./handlers/image')(browser)
const unsplashHandler = require('./handlers/unsplash')
const oembedHandler = require('./handlers/oembed')
if (dev) {
server.use(morgan('tiny'))
}
server.use(
cors({
origin(origin, callback) {
return origin === 'https://carbon.now.sh' || !origin
? callback(null, true)
: callback(new Error('Not allowed by CORS'))
}
})
)
server.use(compression())
// api endpoints
server.post('/twitter', bodyParser.json({ limit: '5mb' }), require('./handlers/twitter'))
server.post('/image', bodyParser.json({ limit: '5mb' }), wrap(imageHandler))
server.get('/unsplash/random', wrap(unsplashHandler.randomImages))
server.get('/unsplash/download/:imageId', wrap(unsplashHandler.downloadImage))
server.all('/oembed', oembedHandler)
server.listen(port, '0.0.0.0', err => {
if (err) throw err
// eslint-disable-next-line
console.log(`> Ready on http://localhost:${port}`)
})
})

File diff suppressed because it is too large Load Diff

@ -10,8 +10,8 @@
"export": "next export",
"test": "npm run cy:run --",
"deploy": "now",
"prettier": "prettier --config .prettierrc --write {.,components,api,lib,pages}/*.js {components,api,lib,pages,packages}/**/*.js",
"lint": "eslint components/*.js lib/*.js pages/*.js api/handlers/*.js api/*.js",
"prettier": "prettier --config .prettierrc --write {.,components,lib,pages}/*.js {components,lib,pages,packages}/**/*.js",
"lint": "eslint components/*.js lib/*.js pages/*.js",
"contrib:add": "all-contributors add",
"contrib:build": "all-contributors generate",
"cy:run": "cypress run",

Loading…
Cancel
Save