Skip to content

Commit

Permalink
2.1 - subscription payment support
Browse files Browse the repository at this point in the history
  • Loading branch information
Glench committed Mar 24, 2021
1 parent b23f26a commit 389b8c6
Show file tree
Hide file tree
Showing 13 changed files with 747 additions and 597 deletions.
242 changes: 125 additions & 117 deletions ExtPay.dev.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export default function ExtPay(extension_id) {
const HOST = `http://localhost:3000`
const EXTENSION_URL = `${HOST}/extension/${extension_id}`

function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function get(key) {
try {
return await browser.storage.sync.get(key)
Expand All @@ -36,84 +39,85 @@ export default function ExtPay(extension_id) {
}
}

browser.runtime.onInstalled && browser.runtime.onInstalled.addListener(async function(install_details) {

const ext_info = await browser.management.getSelf()
if (ext_info.installType == 'development') {
if (!ext_info.hostPermissions.includes(HOST+'/*')) {
var permissions = ext_info.permissions.concat(ext_info.hostPermissions)
throw `ExtPay Setup Error: please include "${HOST}/*" in manifest.json["permissions"] or else ExtensionPay won't work correctly.
// ----- start configuration checks
browser.management && browser.management.getSelf().then(async (ext_info) => {
if (!ext_info.hostPermissions.includes(HOST+'/*')) {
var permissions = ext_info.permissions.concat(ext_info.hostPermissions)
throw `ExtPay Setup Error: please include "${HOST}/*" in manifest.json["permissions"] or else ExtensionPay won't work correctly.
You can copy and paste this to your manifest.json file to fix this error:
"permissions": [
${permissions.map(x => `"${x}"`).join(',\n ')}${permissions.length > 0 ? ',' : ''}
"${HOST}/*"
]
`
}
"permissions": [
${permissions.map(x => `"${x}"`).join(',\n ')}${permissions.length > 0 ? ',' : ''}
"${HOST}/*"
]
`
}

if (!ext_info.permissions.includes('storage')) {
var permissions = ext_info.hostPermissions.concat(ext_info.permissions)
throw `ExtPay Setup Error: please include the "storage" permission in manifest.json["permissions"] or else ExtensionPay won't work correctly.
console.log(ext_info)
if (!ext_info.permissions.includes('storage')) {
var permissions = ext_info.hostPermissions.concat(ext_info.permissions)
throw `ExtPay Setup Error: please include the "storage" permission in manifest.json["permissions"] or else ExtensionPay won't work correctly.
You can copy and paste this to your manifest.json file to fix this error:
"permissions": [
${permissions.map(x => `"${x}"`).join(',\n')}${permissions.length > 0 ? ',' : ''}
"storage"
]
`
}
"permissions": [
${permissions.map(x => `" ${x}"`).join(',\n')}${permissions.length > 0 ? ',' : ''}
"storage"
]
`
}

const content_script_template = `"content_scripts": [
{
"matches": ["${HOST}/*"],
"js": ["ExtPay.js"],
"run_at": "document_start"
}]`
var manifest_resp;
try {
manifest_resp = await fetch('manifest.json')
} catch(e) {
throw 'ExtPay setup error: cannot locate manifest.json in top-level. If this is a problem for you, please contact me so I can add a feature for you.'
}
const manifest = await manifest_resp.json()
if (!manifest.content_scripts) {
throw `ExtPay setup error: Please include ExtPay as a content script in your manifest.json. You can copy the example below into your manifest.json or check the docs: https://github.com/Glench/ExtPay#2-configure-your-manifestjson
const content_script_template = `"content_scripts": [
{
"matches": ["${HOST}/*"],
"js": ["ExtPay.js"],
"run_at": "document_start"
}]`
const manifest = browser.runtime.getManifest();
if (!manifest.content_scripts) {
throw `ExtPay setup error: Please include ExtPay as a content script in your manifest.json. You can copy the example below into your manifest.json or check the docs: https://github.com/Glench/ExtPay#2-configure-your-manifestjson
${content_script_template}`
}
const extpay_content_script_entry = manifest.content_scripts.find(obj => {
// removing port number because firefox ignores content scripts with port number
return obj.matches.includes(HOST.replace(':3000', '')+'/*')
})
if (!extpay_content_script_entry) {
throw `ExtPay setup error: Please include ExtPay as a content script in your manifest.json matching "${HOST}/*". You can copy the example below into your manifest.json or check the docs: https://github.com/Glench/ExtPay#2-configure-your-manifestjson
${content_script_template}`
}
const extpay_content_script = manifest.content_scripts.find(obj => {
// removing port number because firefox ignores content scripts with port number
return obj.matches.includes(HOST.replace(':3000', '')+'/*')
})
if (!extpay_content_script) {
throw `ExtPay setup error: Please include ExtPay as a content script in your manifest.json matching "${HOST}/*". You can copy the example below into your manifest.json or check the docs: https://github.com/Glench/ExtPay#2-add-extension-permissions-to-your-manifestjson
${content_script_template}`
} else {
if (!extpay_content_script.run_at || extpay_content_script.run_at !== 'document_start') {
throw `ExtPay setup error: Please make sure the ExtPay content script in your manifest.json runs at document start. You can copy the example below into your manifest.json or check the docs: https://github.com/Glench/ExtPay#2-add-extension-permissions-to-your-manifestjson
${content_script_template}`
}
${content_script_template}`
} else {
if (!extpay_content_script_entry.run_at || extpay_content_script_entry.run_at !== 'document_start') {
throw `ExtPay setup error: Please make sure the ExtPay content script in your manifest.json runs at document start. You can copy the example below into your manifest.json or check the docs: https://github.com/Glench/ExtPay#2-configure-your-manifestjson
${content_script_template}`
}
}
})
// ----- end configuration checks

if (install_details.reason !== 'install' && install_details.reason !== 'update') {
return
}
// run on "install"
get(['extensionpay_installed_at', 'extensionpay_user']).then(async (storage) => {
if (storage.extensionpay_installed_at) return;

const storage = await get(['extensionpay_api_key']);
if (storage.extensionpay_api_key) return;
// Migration code: before v2.1 installedAt came from the server
// so use that stored datetime instead of making a new one.
const user = storage.extensionpay_user;
const date = user ? user.installedAt : (new Date()).toISOString();
await set({'extensionpay_installed_at': date})
})

var paid_callbacks = [];

async function create_key() {
const ext_info = await browser.management.getSelf();
var body = {};
if (ext_info.installType == 'development') {
body.development = true;
}

// TODO: what to do if this request fails?
const resp = await fetch(`${EXTENSION_URL}/api/new-key`, {
method: 'POST',
headers: {
Expand All @@ -123,32 +127,75 @@ You can copy and paste this to your manifest.json file to fix this error:
body: JSON.stringify(body),
})
if (!resp.ok) {
console.error('ExtPay: Error generating API key (server response): ', resp.status, `Are you sure you registered your extension? Check at this URL and make sure the ID matches '${extension_id}':`, `${HOST}/home`)
throw 'ExtPay: Error generating key. Server response: ', resp.status, `Are you sure you registered your extension on extensionpay.com? Check at this URL and make sure the ID matches '${extension_id}':`, `${HOST}/home`
return;
}
const api_key = await resp.json();
await set({extensionpay_api_key: api_key})
fetch_user()
}) // installed
return api_key;
}

var paid_callbacks = [];
async function get_key() {
const storage = await get(['extensionpay_api_key']);
if (storage.extensionpay_api_key) {
return storage.extensionpay_api_key;
}
return null;
}

function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
const datetime_re = /^\d\d\d\d-\d\d-\d\dT/;

async function fetch_user() {
var storage = await get(['extensionpay_user', 'extensionpay_installed_at'])
const api_key = await get_key()
if (!api_key) {
return {
paid: false,
paidAt: null,
installedAt: new Date(storage.extensionpay_installed_at),
}
}

const resp = await fetch(`${EXTENSION_URL}/api/user?api_key=${api_key}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
}
})
// TODO: think harder about error states and what users will want (bad connection, server error, id not found)
if (!resp.ok) throw 'ExtPay error while fetching user: '+resp

const user_data = await resp.json();

const parsed_user = {}
for (var [key, value] of Object.entries(user_data)) {
if (value && value.match && value.match(datetime_re)) {
value = new Date()
}
parsed_user[key] = value
}
parsed_user.installedAt = new Date(storage.extensionpay_installed_at);


if (parsed_user.paidAt) {
if (!storage.extensionpay_user || (storage.extensionpay_user && !storage.extensionpay_user.paidAt)) {
paid_callbacks.forEach(cb => cb(parsed_user))
}
}
await set({extensionpay_user: user_data})

return parsed_user;
}

async function open_payment_page() {
var storage = await get(['extensionpay_api_key'])
// wait 10 seconds for api key to be returned if creator is running this in background.js immediately after extpay initialization
for (var i=0; i < 20; ++i) {
if (storage.extensionpay_api_key) break;
await timeout(500)
storage = await get(['extensionpay_api_key', 'extensionpay_user'])
var api_key = await get_key();
if (!api_key) {
api_key = await create_key();
}
if (!storage.extensionpay_api_key) throw 'ExtPay Error: timed out registering user.'
if (browser.windows) {
try {
browser.windows.create({
url: `${EXTENSION_URL}?api_key=${storage.extensionpay_api_key}`,
url: `${EXTENSION_URL}?api_key=${api_key}`,
type: "popup",
focused: true,
width: 500,
Expand All @@ -158,7 +205,7 @@ You can copy and paste this to your manifest.json file to fix this error:
} catch(e) {
// firefox doesn't support 'focused'
browser.windows.create({
url: `${EXTENSION_URL}?api_key=${storage.extensionpay_api_key}`,
url: `${EXTENSION_URL}?api_key=${api_key}`,
type: "popup",
width: 500,
height: 800,
Expand All @@ -167,57 +214,19 @@ You can copy and paste this to your manifest.json file to fix this error:
}
} else {
// https://developer.mozilla.org/en-US/docs/Web/API/Window/open
window.open(`${EXTENSION_URL}?api_key=${storage.extensionpay_api_key}`, null, "toolbar=no,location=no,directories=no,status=no,menubar=no,width=500,height=800,left=450")
window.open(`${EXTENSION_URL}?api_key=${api_key}`, null, "toolbar=no,location=no,directories=no,status=no,menubar=no,width=500,height=800,left=450")
}
}


async function fetch_user() {
var storage = await get(['extensionpay_api_key', 'extensionpay_user'])
// wait 10 seconds for api key to be returned if creator is running this in background.js immediately after extpay initialization
for (var i=0; i < 20; ++i) {
if (storage.extensionpay_api_key) break;
await timeout(500)
storage = await get(['extensionpay_api_key', 'extensionpay_user'])
}
if (!storage.extensionpay_api_key) throw 'ExtPay Error: timed out registering user.'

const resp = await fetch(`${EXTENSION_URL}/api/user?api_key=${storage.extensionpay_api_key}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
}
})
// TODO: think harder about error states and what users will want (bad connection, server error, id not found)
if (!resp.ok) throw 'ExtPay error while fetching user: '+resp

const user_data = await resp.json();

const parsed_user = {
paid: user_data.paid,
paidAt: user_data.paidAt ? new Date(user_data.paidAt) : null,
installedAt: new Date(user_data.installedAt),
}

if (parsed_user.paid) {
if (!storage.extensionpay_user || (storage.extensionpay_user && !storage.extensionpay_user.paid)) {
paid_callbacks.forEach(cb => cb(parsed_user))
}
}
await set({extensionpay_user: user_data}) // useful for future purposes perhaps

return parsed_user;
}

browser.runtime.onMessage.addListener(async function(message) {
if (message == 'fetch-user') {
// Only called via extensionpay.com/extension/[extension-id]/paid -> content_script when user successfully pays.
// It's possible attackers could trigger this but it wouldn't do anything but query.
// It's possible attackers could trigger this but that is basically harmless. It would just query the user.

// keep trying to fetch user in case stripe webhook is late
var user = await fetch_user()
for (var i=0; i < 60; ++i) {
if (user.paid) return
for (var i=0; i < 2*60; ++i) {
if (user.paidAt) return
await timeout(1000)
user = await fetch_user()
}
Expand Down Expand Up @@ -247,4 +256,3 @@ You can copy and paste this to your manifest.json file to fix this error:
}
}


Loading

0 comments on commit 389b8c6

Please sign in to comment.