use esm import instead of cjs require

This commit is contained in:
Dan Phiffer 2024-10-02 09:53:07 -04:00
parent 34b46ed9fc
commit 18299a2c2b
13 changed files with 194 additions and 200 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
node_modules/ node_modules/
*.db *.db
package-lock.json package-lock.json
config.json config.js

124
README.md
View File

@ -8,7 +8,7 @@ As of the `v2.0.0` release of this project, only users who are authenticated wit
## Requirements ## Requirements
This requires Node.js v10.10.0 or above. This requires Node.js v20 or above.
You also need `beanstalkd` running. This is a simple and fast queueing system we use to manage polling RSS feeds. [Here are installation instructions](https://beanstalkd.github.io/download.html). On a production server you'll want to [install it as a background process](https://github.com/beanstalkd/beanstalkd/tree/master/adm). You also need `beanstalkd` running. This is a simple and fast queueing system we use to manage polling RSS feeds. [Here are installation instructions](https://beanstalkd.github.io/download.html). On a production server you'll want to [install it as a background process](https://github.com/beanstalkd/beanstalkd/tree/master/adm).
@ -20,46 +20,46 @@ Clone the repository, then `cd` into its root directory. Install dependencies:
`npm i` `npm i`
Then copy `config.json.template` to `config.json`: Then copy `config.js.template` to `config.js`:
`cp config.json.template config.json` `cp config.js.template config.js`
Update your new `config.json` file: Update your new `config.js` file:
```js ```js
{ export default {
"DOMAIN": "mydomain.com", DOMAIN: "mydomain.com",
"PORT_HTTP": "3000", PORT_HTTP: "3000",
"PORT_HTTPS": "8443", PORT_HTTPS: "8443",
"PRIVKEY_PATH": "/path/to/your/ssl/privkey.pem", PRIVKEY_PATH: "/path/to/your/ssl/privkey.pem",
"CERT_PATH": "/path/to/your/ssl/cert.pem", CERT_PATH: "/path/to/your/ssl/cert.pem",
"OAUTH": { OAUTH: {
"client_id": "abc123def456", client_id: "abc123def456",
"client_secret": "zyx987wvu654", client_secret: "zyx987wvu654",
"redirect_uri": "https://rss.example.social/convert", redirect_uri: "https://rss.example.social/convert",
"domain": "example.social", domain: "example.social",
"domain_human": "Example Online Community", domain_human: "Example Online Community",
"authorize_path": "/oauth/authorize", authorize_path: "/oauth/authorize",
"token_path": "/oauth/token", token_path: "/oauth/token",
"token_verification_path": "/some/path/to/verify/token" token_verification_path: "/some/path/to/verify/token",
} },
} };
``` ```
* `DOMAIN`: your domain! this should be a discoverable domain of some kind like "example.com" or "rss.example.com" - `DOMAIN`: your domain! this should be a discoverable domain of some kind like "example.com" or "rss.example.com"
* `PORT_HTTP`: the http port that Express runs on - `PORT_HTTP`: the http port that Express runs on
* `PORT_HTTPS`: the https port that Express runs on - `PORT_HTTPS`: the https port that Express runs on
* `PRIVKEY_PATH`: point this to your private key you got from Certbot or similar - `PRIVKEY_PATH`: point this to your private key you got from Certbot or similar
* `CERT_PATH`: point this to your cert you got from Certbot or similar - `CERT_PATH`: point this to your cert you got from Certbot or similar
* `OAUTH`: this object contains properties related to OAuth login. See the section below on "Running with OAuth" for more details. - `OAUTH`: this object contains properties related to OAuth login. See the section below on "Running with OAuth" for more details.
* `client_id`: also known as the "client key". A long series of characters. You generate this when you register this application with an OAuth provider. - `client_id`: also known as the "client key". A long series of characters. You generate this when you register this application with an OAuth provider.
* `client_secret`: Another long series of characters that you generate when you register this application with an OAuth provider. - `client_secret`: Another long series of characters that you generate when you register this application with an OAuth provider.
* `redirect_uri`: This is the URI that people get redirected to after they authorize the application on the OAuth server. Must point to the server where THIS service is running, and must point to the `/convert` page. This uri has to match what you put in the application info on the OAuth provider. - `redirect_uri`: This is the URI that people get redirected to after they authorize the application on the OAuth server. Must point to the server where THIS service is running, and must point to the `/convert` page. This uri has to match what you put in the application info on the OAuth provider.
* `domain`: The domain of the OAuth provider. Not necessarily the same as this server (for example, you could host this at rss.mydomain.com and then handle all OAuth through some other server you control, like a Mastodon server). - `domain`: The domain of the OAuth provider. Not necessarily the same as this server (for example, you could host this at rss.mydomain.com and then handle all OAuth through some other server you control, like a Mastodon server).
* `domain_human`: The human-readable name of the OAuth provider. This will appear in various messages, so if you say "Example Online Community" here then the user will see a message like "Click here to log in via Example Online Community". - `domain_human`: The human-readable name of the OAuth provider. This will appear in various messages, so if you say "Example Online Community" here then the user will see a message like "Click here to log in via Example Online Community".
* `authorize_path`: This will generally be `/oauth/authorize/` but you can change it here if your OAuth provider uses a nonstandard authorization path. - `authorize_path`: This will generally be `/oauth/authorize/` but you can change it here if your OAuth provider uses a nonstandard authorization path.
* `token_path`: This will generally be `/oauth/token/` but you can change it here if your OAuth provider uses a nonstandard token path. - `token_path`: This will generally be `/oauth/token/` but you can change it here if your OAuth provider uses a nonstandard token path.
* `token_verification_path`: This should be the path to any URL at the OAuth server that responds with an HTTP status code 200 when you are correctly logged in (and with a non-200 value when you are not). This is the path relative to the `domain` you set, so if your `domain` is `example.social` and you set `token_verification_path` to `/foo/bar/` then the full path that this service will run a GET on to verify you are logged in is `https://example.social/foo/bar`. - `token_verification_path`: This should be the path to any URL at the OAuth server that responds with an HTTP status code 200 when you are correctly logged in (and with a non-200 value when you are not). This is the path relative to the `domain` you set, so if your `domain` is `example.social` and you set `token_verification_path` to `/foo/bar/` then the full path that this service will run a GET on to verify you are logged in is `https://example.social/foo/bar`.
Run the server! Run the server!
@ -75,22 +75,22 @@ There is also a file called `queueFeeds.js` that needs to be run on a cron job o
OAuth is unfortunately a bit underspecified so there are a lot of funky implementations out there. Here I will include an example of using a Mastodon server as the OAuth provider. This is how I have my RSS service set up: I run friend.camp as my Mastodon server, and I use my admin powers on friend.camp to register rss.friend.camp as an application. The steps for this, for Mastodon, are: OAuth is unfortunately a bit underspecified so there are a lot of funky implementations out there. Here I will include an example of using a Mastodon server as the OAuth provider. This is how I have my RSS service set up: I run friend.camp as my Mastodon server, and I use my admin powers on friend.camp to register rss.friend.camp as an application. The steps for this, for Mastodon, are:
* log in as an admin user - log in as an admin user
* go to Preferences - go to Preferences
* select Development - select Development
* select New Application - select New Application
* type in an application name, and the URL where this service is running - type in an application name, and the URL where this service is running
* type in the redirect URI, which will be whatever base domain this service is running at with the `/convert` path appended. So something like `https://rss.example.social/convert` - type in the redirect URI, which will be whatever base domain this service is running at with the `/convert` path appended. So something like `https://rss.example.social/convert`
* uncheck all scopes, and check `read:accounts` (this is the minimum required access, simply so this RSS converter can confirm someone is truly logged in) - uncheck all scopes, and check `read:accounts` (this is the minimum required access, simply so this RSS converter can confirm someone is truly logged in)
* once you're done, save - once you're done, save
* you will now have access to a "client key" and "client secret" for this app. - you will now have access to a "client key" and "client secret" for this app.
* open `config.js` in an editor - open `config.js` in an editor
* fill in `client_id` with the client key, and `client_secret` with the client secret. - fill in `client_id` with the client key, and `client_secret` with the client secret.
* set the `redirect_uri` to be identical to the one you put in Mastodon. It should look like `https://rss.example.social/convert` (the `/convert` part is important, this software won't work if you point to a different path) - set the `redirect_uri` to be identical to the one you put in Mastodon. It should look like `https://rss.example.social/convert` (the `/convert` part is important, this software won't work if you point to a different path)
* set `domain` to the domain of your Mastodon server, and `domain_human` to its human-friendly name - set `domain` to the domain of your Mastodon server, and `domain_human` to its human-friendly name
* leave `authorize_path` and `token_path` on their defaults - leave `authorize_path` and `token_path` on their defaults
* set `token_verification_path` to `/api/v1/accounts/verify_credentials` - set `token_verification_path` to `/api/v1/accounts/verify_credentials`
* cross your fingers and start up this server - cross your fingers and start up this server
## Local testing ## Local testing
@ -112,22 +112,22 @@ There are two tables in the database: `accounts` and `feeds`.
This table keeps track of all the data needed for the accounts. Columns: This table keeps track of all the data needed for the accounts. Columns:
* `name` `TEXT PRIMARY KEY`: the account name, in the form `thename@example.com` - `name` `TEXT PRIMARY KEY`: the account name, in the form `thename@example.com`
* `privkey` `TEXT`: the RSA private key for the account - `privkey` `TEXT`: the RSA private key for the account
* `pubkey` `TEXT`: the RSA public key for the account - `pubkey` `TEXT`: the RSA public key for the account
* `webfinger` `TEXT`: the entire contents of the webfinger JSON served for this account - `webfinger` `TEXT`: the entire contents of the webfinger JSON served for this account
* `actor` `TEXT`: the entire contents of the actor JSON served for this account - `actor` `TEXT`: the entire contents of the actor JSON served for this account
* `apikey` `TEXT`: the API key associated with this account - `apikey` `TEXT`: the API key associated with this account
* `followers` `TEXT`: a JSON-formatted array of the URL for the Actor JSON of all followers, in the form `["https://remote.server/users/somePerson", "https://another.remote.server/ourUsers/anotherPerson"]` - `followers` `TEXT`: a JSON-formatted array of the URL for the Actor JSON of all followers, in the form `["https://remote.server/users/somePerson", "https://another.remote.server/ourUsers/anotherPerson"]`
* `messages` `TEXT`: not yet used but will eventually store all messages so we can render them on a "profile" page - `messages` `TEXT`: not yet used but will eventually store all messages so we can render them on a "profile" page
### `feeds` ### `feeds`
This table keeps track of all the data needed for the feeds. Columns: This table keeps track of all the data needed for the feeds. Columns:
* `feed` `TEXT PRIMARY KEY`: the URI of the RSS feed - `feed` `TEXT PRIMARY KEY`: the URI of the RSS feed
* `username` `TEXT`: the username associated with the RSS feed - `username` `TEXT`: the username associated with the RSS feed
* `content` `TEXT`: the most recent copy fetched of the RSS feed's contents - `content` `TEXT`: the most recent copy fetched of the RSS feed's contents
## License ## License

View File

@ -1,7 +1,7 @@
{ export default {
"DOMAIN": "", "DOMAIN": "",
"PORT_HTTP": "3000", "PORT_HTTP": "3000",
"PORT_HTTPS": "8443", "PORT_HTTPS": "8443",
"PRIVKEY_PATH": "", "PRIVKEY_PATH": "",
"CERT_PATH": "" "CERT_PATH": ""
} };

View File

@ -1,14 +1,17 @@
const config = require('./config.json'); import express from 'express';
import bodyParser from 'body-parser';
import cors from 'cors';
import http from 'http';
import Database from 'better-sqlite3';
import fs from 'fs';
import config from './config.js';
import routes from './routes/index.js';
const { DOMAIN, PRIVKEY_PATH, CERT_PATH, PORT_HTTP, PORT_HTTPS, OAUTH } = config; const { DOMAIN, PRIVKEY_PATH, CERT_PATH, PORT_HTTP, PORT_HTTPS, OAUTH } = config;
const express = require('express');
const app = express(); const app = express();
const Database = require('better-sqlite3');
const db = new Database('bot-node.db'); const db = new Database('bot-node.db');
const fs = require('fs');
const routes = require('./routes'),
bodyParser = require('body-parser'),
cors = require('cors'),
http = require('http');
let sslOptions; let sslOptions;
try { try {

View File

@ -3,18 +3,18 @@
"version": "2.0.0", "version": "2.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module",
"dependencies": { "dependencies": {
"better-sqlite3": "^5.0.1", "better-sqlite3": "^11.3.0",
"body-parser": "^1.18.3", "body-parser": "^1.20.3",
"cheerio": "^1.0.0-rc.2", "cheerio": "^1.0.0",
"cors": "^2.8.4", "cors": "^2.8.5",
"express": "^4.16.3", "express": "^4.21.0",
"generate-rsa-keypair": "^0.1.2", "generate-rsa-keypair": "^0.2.1",
"jackd": "^1.2.4", "jackd": "^2.2.2",
"parse-favicon": "^2.0.0", "parse-favicon": "^7.0.1",
"pug": "^2.0.3", "pug": "^3.0.3",
"request": "^2.87.0", "rss-parser": "^3.13.0"
"rss-parser": "^3.6.3"
}, },
"engines": { "engines": {
"node": ">=10.10.0" "node": ">=10.10.0"

View File

@ -1,8 +1,8 @@
const Database = require('better-sqlite3'); import Database from 'better-sqlite3';
const db = new Database('bot-node.db'); import Jackd from 'jackd';
const Jackd = require('jackd');
const beanstalkd = new Jackd();
const beanstalkd = new Jackd();
const db = new Database('bot-node.db');
async function foo() { async function foo() {
@ -15,7 +15,7 @@ async function foo() {
await beanstalkd.connect() await beanstalkd.connect()
for (feed of feeds) { for (let feed of feeds) {
await beanstalkd.put(feed.feed) await beanstalkd.put(feed.feed)
} }
@ -23,4 +23,4 @@ async function foo() {
} }
foo() foo();

View File

@ -1,15 +1,16 @@
'use strict'; import express from 'express';
const express = require('express'), import cors from 'cors';
router = express.Router(), import crypto from 'crypto';
cors = require('cors'), import Parser from 'rss-parser';
crypto = require('crypto'), import { parseFavicon } from 'parse-favicon';
request = require('request'), import generateRSAKeypair from 'generate-rsa-keypair';
Parser = require('rss-parser'), import config from '../config.js';
parseFavicon = require('parse-favicon').parseFavicon,
generateRSAKeypair = require('generate-rsa-keypair'),
oauth = require('../config.json').OAUTH;
router.get('/request-token', cors(), (req, res) => { const router = express.Router();
const oauth = config.OAUTH;
export default router;
router.get('/request-token', cors(), async (req, res) => {
if (!oauth) { if (!oauth) {
return res.status(501).json({message: `OAuth is not enabled on this server.`}); return res.status(501).json({message: `OAuth is not enabled on this server.`});
} }
@ -25,36 +26,37 @@ router.get('/request-token', cors(), (req, res) => {
params.client_secret = oauth.client_secret; params.client_secret = oauth.client_secret;
params.redirect_uri = oauth.redirect_uri; params.redirect_uri = oauth.redirect_uri;
params.grant_type = 'authorization_code'; params.grant_type = 'authorization_code';
request.post(`https://${oauth.domain}${oauth.token_path}`, {form: params}, (err,httpResponse,body) => { let response = await fetch(`https://${oauth.domain}${oauth.token_path}`, {
body = JSON.parse(body); method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams(params)
});
let body = await response.json();
if (body.access_token) { if (body.access_token) {
return res.json({ access_token: body.access_token, domain: oauth.domain}); return res.json({ access_token: body.access_token, domain: oauth.domain});
} } else {
else {
return res.status(401).json(body); return res.status(401).json(body);
} }
}); });
});
// if oauth is enabled, this function checks to see if we've been sent an access token and validates it with the server // if oauth is enabled, this function checks to see if we've been sent an access token and validates it with the server
// otherwise we simply skip verification // otherwise we simply skip verification
function isAuthenticated(req, res, next) { function isAuthenticated(req, res, next) {
if (oauth) { if (oauth) {
request.get({ fetch(`https://${oauth.domain}${oauth.token_verification_path}`, {
url: `https://${oauth.domain}${oauth.token_verification_path}`,
headers: { headers: {
'Authorization': `Bearer ${req.query.token}` 'Authorization': `Bearer ${req.query.token}`
},
}, (err, resp, body) => {
if (resp.statusCode === 200) {
return next();
} }
else { }).then(response => {
if (response.status === 200) {
return next();
} else {
res.redirect('/'); res.redirect('/');
} }
}); });
} } else {
else {
return next(); return next();
} }
} }
@ -85,6 +87,7 @@ router.get('/convert', isAuthenticated, function (req, res) {
res.status(400).json({err: err.message}); res.status(400).json({err: err.message});
} }
else { else {
console.log(feedData);
res.status(200).json(feedData); res.status(200).json(feedData);
let displayName = feedData.title; let displayName = feedData.title;
let description = feedData.description; let description = feedData.description;
@ -98,9 +101,9 @@ router.get('/convert', isAuthenticated, function (req, res) {
let actorRecord = createActor(account, domain, pair.public, displayName, imageUrl, description); let actorRecord = createActor(account, domain, pair.public, displayName, imageUrl, description);
let webfingerRecord = createWebfinger(account, domain); let webfingerRecord = createWebfinger(account, domain);
const apikey = crypto.randomBytes(16).toString('hex'); const apikey = crypto.randomBytes(16).toString('hex');
db.prepare('insert or replace into accounts(name, actor, apikey, pubkey, privkey, webfinger) values(?, ?, ?, ?, ?, ?)').run( `${account}@${domain}`, JSON.stringify(actorRecord), apikey, pair.public, pair.private, JSON.stringify(webfingerRecord)); let result = db.prepare('insert or replace into accounts(name, actor, apikey, pubkey, privkey, webfinger) values(?, ?, ?, ?, ?, ?)').run( `${account}@${domain}`, JSON.stringify(actorRecord), apikey, pair.public, pair.private, JSON.stringify(webfingerRecord));
let content = JSON.stringify(feedData); let content = JSON.stringify(feedData);
db.prepare('insert or replace into feeds(feed, username, content) values(?, ?, ?)').run( feed, username, content); result = db.prepare('insert or replace into feeds(feed, username, content) values(?, ?, ?)').run( feed, username, content);
}); });
} }
}); });
@ -120,8 +123,8 @@ function getImage(feed, feedData, cb) {
// otherwise parse the HTML for the favicon // otherwise parse the HTML for the favicon
else { else {
let favUrl = new URL(feed); let favUrl = new URL(feed);
request(favUrl.origin, (err, resp, body) => { fetch(favUrl.origin).then(response => response.body).then(body => {
parseFavicon(body, {baseURI: favUrl.origin}).then(result => { const result = parseFavicon(body, {baseURI: favUrl.origin});
if (result && result.length) { if (result && result.length) {
return cb(result[0].url); return cb(result[0].url);
} }
@ -129,7 +132,6 @@ function getImage(feed, feedData, cb) {
return cb(null); return cb(null);
} }
}); });
});
} }
} }
@ -178,5 +180,3 @@ function createWebfinger(name, domain) {
] ]
}; };
} }
module.exports = router;

View File

@ -1,9 +1,9 @@
'use strict'; import express from 'express';
const express = require('express'), import crypto from 'crypto';
crypto = require('crypto'), import fs from 'fs';
request = require('request'),
fs = require('fs'), const router = express.Router();
router = express.Router(); export default router;
function signAndSend(message, name, domain, req, res, targetDomain) { function signAndSend(message, name, domain, req, res, targetDomain) {
// get the URI of the actor object and append 'inbox' to it // get the URI of the actor object and append 'inbox' to it
@ -29,8 +29,7 @@ function signAndSend(message, name, domain, req, res, targetDomain) {
const signature_b64 = signature.toString('base64'); const signature_b64 = signature.toString('base64');
const algorithm = 'rsa-sha256'; const algorithm = 'rsa-sha256';
let header = `keyId="https://${domain}/u/${name}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`; let header = `keyId="https://${domain}/u/${name}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`;
request({ fetch(inbox, {
url: inbox,
headers: { headers: {
'Host': targetDomain, 'Host': targetDomain,
'Date': d.toUTCString(), 'Date': d.toUTCString(),
@ -40,9 +39,7 @@ function signAndSend(message, name, domain, req, res, targetDomain) {
'Accept': 'application/activity+json' 'Accept': 'application/activity+json'
}, },
method: 'POST', method: 'POST',
json: true,
body: message body: message
}, function (error, response, body){
}); });
res.json('done'); res.json('done');
} }
@ -102,5 +99,3 @@ router.post('/', function (req, res) {
} }
} }
}); });
module.exports = router;

View File

@ -1,9 +1,13 @@
'use strict'; import api from './api.js';
import inbox from './inbox.js';
import message from './message.js';
import user from './user.js';
import webfinger from './webfinger.js';
module.exports = { export default {
api: require('./api'), api,
inbox: require('./inbox'), inbox,
message: require('./message'), message,
user: require('./user'), user,
webfinger: require('./webfinger'), webfinger,
}; };

View File

@ -1,8 +1,7 @@
'use strict'; import express from 'express';
const express = require('express'),
router = express.Router(), const router = express.Router();
Parser = require('rss-parser'), export default router;
parser = new Parser();
router.get('/:guid', function (req, res) { router.get('/:guid', function (req, res) {
let guid = req.params.guid; let guid = req.params.guid;
@ -40,5 +39,3 @@ router.get('/:guid', function (req, res) {
} }
} }
}); });
module.exports = router;

View File

@ -1,6 +1,7 @@
'use strict'; import express from 'express';
const express = require('express'),
router = express.Router(); const router = express.Router();
export default router;
router.get('/:name', function (req, res) { router.get('/:name', function (req, res) {
let name = req.params.name; let name = req.params.name;
@ -78,5 +79,3 @@ router.get('/:name/followers', function (req, res) {
//res.json(JSON.parse(result.actor)); //res.json(JSON.parse(result.actor));
} }
}); });
module.exports = router;

View File

@ -1,6 +1,7 @@
'use strict'; import express from 'express';
const express = require('express'),
router = express.Router(); const router = express.Router();
export default router;
router.get('/', function (req, res) { router.get('/', function (req, res) {
let resource = req.query.resource; let resource = req.query.resource;
@ -19,5 +20,3 @@ router.get('/', function (req, res) {
} }
} }
}); });
module.exports = router;

View File

@ -1,13 +1,13 @@
const config = require('./config.json'); import Database from 'better-sqlite3';
const { DOMAIN, PRIVKEY_PATH, CERT_PATH, PORT_HTTP, PORT_HTTPS } = config; import Parser from 'rss-parser';
const Database = require('better-sqlite3'); import crypto from 'crypto';
const db = new Database('bot-node.db'), import Jackd from 'jackd';
Parser = require('rss-parser'), import config from './config.js';
request = require('request'),
crypto = require('crypto'),
parser = new Parser({timeout: 2000});
const Jackd = require('jackd'); const { DOMAIN, PRIVKEY_PATH, CERT_PATH, PORT_HTTP, PORT_HTTPS } = config;
const db = new Database('bot-node.db');
const parser = new Parser({timeout: 2000});
const beanstalkd = new Jackd(); const beanstalkd = new Jackd();
beanstalkd.connect() beanstalkd.connect()
@ -15,10 +15,10 @@ beanstalkd.connect()
async function processQueue() { async function processQueue() {
while (true) { while (true) {
try { try {
const { id, payload } = await beanstalkd.reserve() const job = await beanstalkd.reserve()
/* ... process job here ... */ /* ... process job here ... */
await beanstalkd.delete(id) await beanstalkd.delete(job.id);
await doFeed(payload) await doFeed(job.payload.toString());
} catch (err) { } catch (err) {
// Log error somehow // Log error somehow
console.error(err) console.error(err)
@ -26,7 +26,7 @@ async function processQueue() {
} }
} }
processQueue() processQueue();
function doFeed(feedUrl) { function doFeed(feedUrl) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -175,8 +175,7 @@ function signAndSend(message, name, domain, req, res, targetDomain, inbox) {
const signature_b64 = signature.toString('base64'); const signature_b64 = signature.toString('base64');
const algorithm = 'rsa-sha256'; const algorithm = 'rsa-sha256';
let header = `keyId="https://${domain}/u/${name}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`; let header = `keyId="https://${domain}/u/${name}",algorithm="${algorithm}",headers="(request-target) host date digest",signature="${signature_b64}"`;
request({ fetch(inbox, {
url: inbox,
headers: { headers: {
'Host': targetDomain, 'Host': targetDomain,
'Date': d.toUTCString(), 'Date': d.toUTCString(),
@ -186,9 +185,7 @@ function signAndSend(message, name, domain, req, res, targetDomain, inbox) {
'Accept': 'application/activity+json' 'Accept': 'application/activity+json'
}, },
method: 'POST', method: 'POST',
json: true,
body: message body: message
}, function (error, response, body){
}); });
} }
} }