Initial commit
This commit is contained in:
commit
82c515b2e9
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@ -0,0 +1,13 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
2
worker/.gitignore
vendored
Normal file
2
worker/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
wrangler.toml
|
146
worker/index.js
Normal file
146
worker/index.js
Normal file
@ -0,0 +1,146 @@
|
||||
async function hashString(input)
|
||||
{
|
||||
const data = new TextEncoder().encode(input);
|
||||
const hash = await crypto.subtle.digest('SHA-256', data);
|
||||
return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes Patreon OAuth token
|
||||
*
|
||||
* @param {Object} token
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function refreshToken(token)
|
||||
{
|
||||
const url = new URL('https://www.patreon.com/api/oauth2/token');
|
||||
url.searchParams.set('grant_type', 'refresh_token');
|
||||
url.searchParams.set('refresh_token', token.refresh_token);
|
||||
url.searchParams.set('client_id', PATREON_CLIENT_ID);
|
||||
url.searchParams.set('client_secret', PATREON_CLIENT_SECRET);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current Patreon OAuth access token. Will trigger `refreshToken()` if necessary.
|
||||
*
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
async function getToken()
|
||||
{
|
||||
let token = await PATREON_CONFIG.get('credentials', {
|
||||
type: 'json',
|
||||
});
|
||||
|
||||
if (token.expires_at && token.expires_at > Date.now()) {
|
||||
return token.access_token;
|
||||
}
|
||||
|
||||
token = await refreshToken(token);
|
||||
token.expires_at = Date.now() + (token.expires_in * 1000);
|
||||
|
||||
await PATREON_CONFIG.put('credentials', JSON.stringify(token));
|
||||
|
||||
return token.access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches data from the Patreon API.
|
||||
* Will cache responses for up to 15 minutes, based on final URL (with GET parameters).
|
||||
*
|
||||
* @param {String} path
|
||||
* @param {Object} params
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function fetchApi(path, params)
|
||||
{
|
||||
if (path[0] !== '/') {
|
||||
path = '/' + path;
|
||||
}
|
||||
|
||||
const url = new URL(`https://www.patreon.com/api/oauth2/v2${path}`);
|
||||
|
||||
for (const key in params) {
|
||||
url.searchParams.set(key, params[key]);
|
||||
}
|
||||
|
||||
const cacheKey = `CACHE_${await hashString(url.toString())}`;
|
||||
const cache = await PATREON_CONFIG.get(cacheKey, {
|
||||
type: 'json',
|
||||
});
|
||||
|
||||
if (cache) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
const token = await getToken();
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
await PATREON_CONFIG.put(cacheKey, JSON.stringify(data), {
|
||||
// Cache for 15 minutes
|
||||
expirationTtl: 15 * 60,
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get campaign goals
|
||||
*
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getGoals()
|
||||
{
|
||||
const goals = await fetchApi('campaigns', {
|
||||
'fields[goal]': 'amount_cents,completed_percentage,created_at,description,reached_at,title',
|
||||
'include': 'goals',
|
||||
});
|
||||
|
||||
return goals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for returning a JSON response.
|
||||
*
|
||||
* @param {Object} input
|
||||
* @param {Number} statusCode
|
||||
* @returns {Response}
|
||||
*/
|
||||
function response(input, statusCode = 200)
|
||||
{
|
||||
return new Response(JSON.stringify(input), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
status: statusCode,
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRequest(request) {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === '/goals') {
|
||||
return response(await getGoals());
|
||||
}
|
||||
|
||||
return response(
|
||||
{
|
||||
error: 'Not found',
|
||||
},
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
addEventListener('fetch', function(event) {
|
||||
return event.respondWith(handleRequest(event.request))
|
||||
});
|
17
worker/package.json
Normal file
17
worker/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "patreon-api-proxy",
|
||||
"version": "1.0.0",
|
||||
"description": "Cloudflare Worker that works as a basic API proxy, so we don't have to deal with refreshing tokens in WordPress",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"format": "prettier --write '**/*.{js,css,json,md}'"
|
||||
},
|
||||
"author": "Alex Thomassen <alex@thomassen.xyz>",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"prettier": "^1.18.2"
|
||||
}
|
||||
}
|
||||
|
14
worker/wrangler.sample.toml
Normal file
14
worker/wrangler.sample.toml
Normal file
@ -0,0 +1,14 @@
|
||||
name = "patreon-api-proxy"
|
||||
account_id = ""
|
||||
workers_dev = true
|
||||
compatibility_date = "2022-09-10"
|
||||
main = "index.js"
|
||||
|
||||
[vars]
|
||||
PATREON_CLIENT_ID = ""
|
||||
PATREON_CLIENT_SECRET = ""
|
||||
|
||||
[[kv_namespaces]]
|
||||
binding = "PATREON_TOKENS"
|
||||
id = ""
|
||||
preview_id = ""
|
Loading…
Reference in New Issue
Block a user