Fix open sockets that weren't closing during tests

Problem: The test suite isn't closing the database because `app.close()`
only affects the HTTP server. This means that tests don't exit cleanly
and sockets remain open and all sorts of really fun stuff that we don't
want while writing tests.

Solution: Refactor `src/ssb.js` so that we can exit cleanly and have
less rope to hang ourselves with. Add a small lifecycle test that can
help us ensure that the bare minimum lifecycle events are working
correctly, plus now the previous tests are passing on my machine too.
This commit is contained in:
Christian Bundy 2020-04-06 12:14:58 -07:00
parent 12dfd04536
commit cb1be6bc8b
6 changed files with 325 additions and 173 deletions

176
package-lock.json generated
View File

@ -779,9 +779,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -1018,6 +1018,11 @@
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"base64-js": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
},
"base64-url": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/base64-url/-/base64-url-2.3.3.tgz",
@ -1169,6 +1174,15 @@
"pkg-up": "^3.1.0"
}
},
"buffer": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz",
"integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"buffer-equal": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz",
@ -2370,10 +2384,12 @@
},
"dependencies": {
"abstract-leveldown": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.2.tgz",
"integrity": "sha512-/a+Iwj0rn//CX0EJOasNyZJd2o8xur8Ce9C57Sznti/Ilt/cb6Qd8/k98A4ZOklXgTG+iAYYUs1OTG0s1eH+zQ==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz",
"integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==",
"requires": {
"buffer": "^5.5.0",
"immediate": "^3.2.3",
"level-concat-iterator": "~2.0.0",
"level-supports": "~1.0.0",
"xtend": "~4.0.0"
@ -2642,10 +2658,12 @@
},
"dependencies": {
"abstract-leveldown": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.2.tgz",
"integrity": "sha512-/a+Iwj0rn//CX0EJOasNyZJd2o8xur8Ce9C57Sznti/Ilt/cb6Qd8/k98A4ZOklXgTG+iAYYUs1OTG0s1eH+zQ==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz",
"integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==",
"requires": {
"buffer": "^5.5.0",
"immediate": "^3.2.3",
"level-concat-iterator": "~2.0.0",
"level-supports": "~1.0.0",
"xtend": "~4.0.0"
@ -3418,9 +3436,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -4074,6 +4092,11 @@
"promisize": "^1.1.2"
}
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"ignore": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz",
@ -5278,9 +5301,9 @@
}
},
"leveldown": {
"version": "5.5.1",
"resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.5.1.tgz",
"integrity": "sha512-GoC455/ncfg4yLLItr192HuXpA+CcQ2q9GncXJhewvvlpsBBEegChn5tMPP+kGvJt7u2LuXAd8fY2moQxFD+sQ==",
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz",
"integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==",
"requires": {
"abstract-leveldown": "~6.2.1",
"napi-macros": "~2.0.0",
@ -5288,10 +5311,12 @@
},
"dependencies": {
"abstract-leveldown": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.2.tgz",
"integrity": "sha512-/a+Iwj0rn//CX0EJOasNyZJd2o8xur8Ce9C57Sznti/Ilt/cb6Qd8/k98A4ZOklXgTG+iAYYUs1OTG0s1eH+zQ==",
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz",
"integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==",
"requires": {
"buffer": "^5.5.0",
"immediate": "^3.2.3",
"level-concat-iterator": "~2.0.0",
"level-supports": "~1.0.0",
"xtend": "~4.0.0"
@ -5473,9 +5498,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -5804,9 +5829,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -5912,9 +5937,9 @@
}
},
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -8381,9 +8406,9 @@
}
},
"ssb-conn-db": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/ssb-conn-db/-/ssb-conn-db-0.3.1.tgz",
"integrity": "sha512-1cCbfaVDow50FNx+0V1hGIJsSy1RIn10J16D/gBlWRxB1ddGr5v6TCtFyc6Y+J/6Gnx+BPTKwqd9vTJ1ow6RCA==",
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/ssb-conn-db/-/ssb-conn-db-0.3.2.tgz",
"integrity": "sha512-3dgUU1sfM9cwPRYh2m4A08ebDr/kOKBQ7NMRLz/K0aK12wAjDEGSNCownzIqK/dLLdPXEJHU41kMLquR8Vdn8A==",
"requires": {
"atomic-file": "^1.1.5",
"debug": "~4.1.1",
@ -8464,9 +8489,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -8526,9 +8551,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -8557,18 +8582,37 @@
}
},
"ssb-markdown": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/ssb-markdown/-/ssb-markdown-6.0.6.tgz",
"integrity": "sha512-rPJryeplNPAfJHBTqtg/GQVggjepFTgzPzmgnwD28MFHb2kAnbGo+M+oLsUb8UGkqE2jmV8fdmtiajYJYLOB5g==",
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/ssb-markdown/-/ssb-markdown-6.0.7.tgz",
"integrity": "sha512-uM+wfgh+piiFtTGhLJvdduhPdaH2E15Da0dOlg8C+9hPHb3ePz5nqfdioHah2CkIMRvjhVWHmUMbGQks/RxtIg==",
"requires": {
"emoji-regex": "^8.0.0",
"hashtag-regex": "^2.1.0",
"highlight.js": "^9.15.8",
"markdown-it": "^8.4.2",
"highlight.js": "^9.18.1",
"markdown-it": "^10.0.0",
"markdown-it-emoji": "^1.4.0",
"markdown-it-hashtag": "^0.4.0",
"node-emoji": "^1.10.0",
"ssb-ref": "^2.13.9"
},
"dependencies": {
"entities": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz",
"integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw=="
},
"markdown-it": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-10.0.0.tgz",
"integrity": "sha512-YWOP1j7UbDNz+TumYP1kpwnP0aEa711cJjrAQrzd0UXlbJfc5aAq0F/PZHjiioqDC1NKgvIMX+o+9Bk7yuM2dg==",
"requires": {
"argparse": "^1.0.7",
"entities": "~2.0.0",
"linkify-it": "^2.0.0",
"mdurl": "^1.0.1",
"uc.micro": "^1.0.5"
}
}
}
},
"ssb-marked": {
@ -8644,9 +8688,9 @@
}
},
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -8673,9 +8717,9 @@
},
"dependencies": {
"mkdirp": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz",
"integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"requires": {
"minimist": "^1.2.5"
}
@ -9049,22 +9093,42 @@
"function-bind": "^1.1.1"
}
},
"string.prototype.trimleft": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz",
"integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==",
"string.prototype.trimend": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.0.tgz",
"integrity": "sha512-EEJnGqa/xNfIg05SxiPSqRS7S9qwDhYts1TSLR1BQfYUfPe1stofgGKvwERK9+9yf+PpfBMlpBaCHucXGPQfUA==",
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
"es-abstract": "^1.17.5"
}
},
"string.prototype.trimleft": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.2.tgz",
"integrity": "sha512-gCA0tza1JBvqr3bfAIFJGqfdRTyPae82+KTnm3coDXkZN9wnuW3HjGgN386D7hfv5CHQYCI022/rJPVlqXyHSw==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5",
"string.prototype.trimstart": "^1.0.0"
}
},
"string.prototype.trimright": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz",
"integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==",
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.2.tgz",
"integrity": "sha512-ZNRQ7sY3KroTaYjRS6EbNiiHrOkjihL9aQE/8gfQ4DtAC/aEBRHFJa44OmoWxGGqXuJlfKkZW4WcXErGr+9ZFg==",
"requires": {
"define-properties": "^1.1.3",
"function-bind": "^1.1.1"
"es-abstract": "^1.17.5",
"string.prototype.trimend": "^1.0.0"
}
},
"string.prototype.trimstart": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.0.tgz",
"integrity": "sha512-iCP8g01NFYiiBOnwG1Xc3WZLyoo+RuBymwIlWncShXDDJYWN6DbnM3odslBJdgCdRlq94B5s63NWAZlcn2CS4w==",
"requires": {
"define-properties": "^1.1.3",
"es-abstract": "^1.17.5"
}
},
"string_decoder": {

View File

@ -16,7 +16,7 @@
"dev": "nodemon --inspect src/index.js --debug --no-open",
"fix": "common-good fix",
"start": "node src/index.js",
"test": "tap && common-good check --dependency-check-suffix '-i changelog-version -i mkdirp -i nodemon -i stylelint-config-recommended'",
"test": "tap --timeout 120 && common-good check --dependency-check-suffix '-i changelog-version -i mkdirp -i nodemon -i stylelint-config-recommended'",
"preversion": "npm test",
"version": "mv docs/CHANGELOG.md ./ && changelog-version && mv CHANGELOG.md docs/ && git add CHANGELOG.md"
},

View File

@ -66,6 +66,8 @@ these settings the default. See the readme for details.`);
const oasisCheckPath = "/.well-known/oasis";
let isClosingAfterTest = false;
process.on("uncaughtException", function (err) {
// This isn't `err.code` because TypeScript doesn't like that.
if (err["code"] === "EADDRINUSE") {
@ -103,7 +105,13 @@ Alternatively, you can set the default port in ${defaultConfigFile} with:
}
});
});
} else if (
isClosingAfterTest &&
err["message"] === "TypeError: Cannot read property 'set' of null"
) {
// We're closing during a test. Ignore.
} else {
console.log(err);
throw err;
}
});
@ -801,9 +809,15 @@ const middleware = [
];
const app = http({ host, port, middleware });
app.on("close", () => {
// HACK: This lets us close the database once tests finish.
// If we close the database after each test it throws lots of really fun "parent
// stream closing" errors everywhere and breaks the tests. :/
app._close = () => {
console.log("closing");
isClosingAfterTest = true;
cooler.close();
});
};
module.exports = app;

View File

@ -34,122 +34,153 @@ const log = (formatter, ...args) => {
debug.enabled = isDebugEnabled;
};
const rawConnect = (options) =>
/**
* @param [options] {object} - options to pass to SSB-Client
* @returns Promise
*/
const connect = (options) =>
new Promise((resolve, reject) => {
ssbClient(null, options)
.then((api) => {
if (api.tangle === undefined) {
// HACK: SSB-Tangle isn't available in Patchwork, but we want that
// compatibility. This code automatically injects SSB-Tangle into our
// stack so that we don't get weird errors when using Patchwork.
//
// See: https://github.com/fraction/oasis/issues/21
api.tangle = ssbTangle.init(api);
const onSuccess = (api) => {
if (api.tangle === undefined) {
// HACK: SSB-Tangle isn't available in Patchwork, but we want that
// compatibility. This code automatically injects SSB-Tangle into our
// stack so that we don't get weird errors when using Patchwork.
//
// See: https://github.com/fraction/oasis/issues/21
api.tangle = ssbTangle.init(api);
// MuxRPC supports promises but the raw plugin does not.
api.tangle.branch = promisify(api.tangle.branch);
}
// MuxRPC supports promises but the raw plugin does not.
api.tangle.branch = promisify(api.tangle.branch);
}
resolve(api);
})
.catch(reject);
resolve(api);
};
ssbClient(null, options).then(onSuccess).catch(reject);
});
let handle;
let closing = false;
let serverHandle;
const createConnection = (customConfig) => {
handle = new Promise((resolve) => {
rawConnect({ remote })
/**
* Attempts connection over Unix socket, falling back to TCP socket if that
* fails. If the TCP socket fails, the promise is rejected.
* @returns Promise
*/
const attemptConnection = () =>
new Promise((resolve, reject) => {
connect({ remote })
.then((ssb) => {
log("Connected to existing Scuttlebutt service over Unix socket");
resolve(ssb);
})
.catch((e) => {
if (closing) return;
log("Unix socket failed");
if (e.message !== "could not connect to sbot") {
throw e;
}
rawConnect()
connect()
.then((ssb) => {
log("Connected to existing Scuttlebutt service over TCP socket");
resolve(ssb);
})
.catch((e) => {
if (closing) return;
log("TCP socket failed");
if (e.message !== "could not connect to sbot") {
throw e;
}
log("Connection attempts to existing Scuttlebutt services failed");
log("Starting Scuttlebutt service");
// Start with the default SSB-Config object.
const server = flotilla(ssbConfig);
// Adjust with `customConfig`, which declares further preferences.
server(customConfig);
const inProgress = {};
const maxHops = lodash.get(
ssbConfig,
"friends.hops",
lodash.get(ssbConfig, "friends.hops", 0)
);
const add = (address) => {
inProgress[address] = true;
return () => {
inProgress[address] = false;
};
};
const connectOrRetry = () => {
rawConnect()
.then((ssb) => {
log("Connected to new Scuttlebutt service");
ssb.friends.hops().then((hops) => {
pull(
ssb.conn.stagedPeers(),
pull.drain((x) => {
x.filter(([address, data]) => {
const notInProgress = inProgress[address] !== true;
const key = data.key;
const haveHops = typeof hops[key] === "number";
const hopValue = haveHops ? hops[key] : Infinity;
// Negative hops means blocked
const isNotBlocked = hopValue >= 0;
const withinHops =
isNotBlocked && hopValue <= maxHops;
return notInProgress && withinHops;
}).forEach(([address, data]) => {
const done = add(address);
debug(
`Connecting to staged peer at ${
hops[data.key]
}/${maxHops} hops: ${address}`
);
ssb.conn
.connect(address, data)
.then(done)
.catch(done);
});
})
);
});
resolve(ssb);
})
.catch((e) => {
if (e.message !== "could not connect to sbot") {
log(e);
}
connectOrRetry();
});
};
connectOrRetry();
reject(new Error("Both connection options failed"));
});
});
});
return handle;
const ensureConnection = (customConfig) =>
new Promise((resolve) => {
console.log("ensureConnection()");
attemptConnection()
.then((ssb) => {
resolve(ssb);
})
.catch(() => {
log("Connection attempts to existing Scuttlebutt services failed");
log("Starting Scuttlebutt service");
// Start with the default SSB-Config object.
const server = flotilla(ssbConfig);
// Adjust with `customConfig`, which declares further preferences.
serverHandle = server(customConfig);
// Give the server a moment to start. This is a race condition. :/
setTimeout(() => {
attemptConnection()
.then((ssb) => {
autoStagePeers({ ssb, config: customConfig });
resolve(ssb);
})
.catch((e) => {
console.log(e);
throw new Error(
"Started SSB service but couldn't establish connection"
);
});
}, 100);
});
});
const autoStagePeers = ({ ssb, config }) => {
// TODO: This does not start when Oasis is started in --offline mode, which
// is great, but if you start Oasis in --offline mode and select 'Start
// networking' then this doesn't come into play.
//
// The right place to fix this is in the scheduler, and this entire function
// should be replaced by: https://github.com/staltz/ssb-conn/pull/17
if (config.conn.autostart !== true) {
return;
}
const inProgress = {};
const maxHops = lodash.get(
ssbConfig,
"friends.hops",
lodash.get(ssbConfig, "friends.hops", 0)
);
const add = (address) => {
inProgress[address] = true;
return () => {
inProgress[address] = false;
};
};
ssb.friends.hops().then((hops) => {
pull(
ssb.conn.stagedPeers(),
pull.drain((x) => {
x.filter(([address, data]) => {
const notInProgress = inProgress[address] !== true;
const key = data.key;
const haveHops = typeof hops[key] === "number";
const hopValue = haveHops ? hops[key] : Infinity;
// Negative hops means blocked
const isNotBlocked = hopValue >= 0;
const withinHops = isNotBlocked && hopValue <= maxHops;
return notInProgress && withinHops;
}).forEach(([address, data]) => {
const done = add(address);
debug(
`Connecting to staged peer at ${
hops[data.key]
}/${maxHops} hops: ${address}`
);
ssb.conn.connect(address, data).then(done).catch(done);
});
})
);
});
};
module.exports = ({ offline }) => {
@ -160,13 +191,13 @@ module.exports = ({ offline }) => {
);
}
const config = {
const customConfig = {
conn: {
autostart: !offline,
},
};
createConnection(config);
let clientHandle;
/**
* This is "cooler", a tiny interface for opening or reusing an instance of
@ -176,29 +207,37 @@ module.exports = ({ offline }) => {
open() {
// This has interesting behavior that may be unexpected.
//
// If `handle` is already an active [non-closed] connection, return that.
// If `clientHandle` is already an active [non-closed] connection, return that.
//
// If the connection is closed, we need to restart it. It's important to
// note that if we're depending on an external service (like Patchwork) and
// that app is closed, then Oasis will seamlessly start its own SSB service.
return new Promise((resolve) => {
handle.then((ssb) => {
if (ssb.closed) {
createConnection();
}
resolve(handle);
});
return new Promise((resolve, reject) => {
if (clientHandle && clientHandle.closed === false) {
resolve(clientHandle);
} else {
ensureConnection(customConfig).then((ssb) => {
clientHandle = ssb;
if (closing) {
ssb.close();
reject(new Error("Closing Oasis"));
} else {
resolve(ssb);
}
});
}
});
},
close() {
return new Promise((resolve) => {
handle.then((ssb) => {
if (ssb.closed === false) {
ssb.close();
}
resolve();
});
});
console.log("ssb.close()");
closing = true;
if (clientHandle && clientHandle.closed === false) {
clientHandle.close();
}
if (serverHandle) {
console.log("closing server");
serverHandle.close();
}
},
};
};

View File

@ -1,5 +1,5 @@
// HACK: Prevent Oasis from opening the web browser.
process.argv.push("--no-open");
process.argv.push("--no-open", "--offline");
const app = require("../src");
const supertest = require("supertest");
@ -26,11 +26,30 @@ const paths = [
"/settings/readme",
];
tap.plan(paths.length);
tap.setTimeout(0);
paths.map((path) => {
tap.comment(path);
supertest(app).get(path).expect(200).end(tap.error);
// console.log('starting test')
// tap.test(path, (t) => {
// console.log('soon')
// });
paths.forEach((path) => {
tap.test(path, (t) => {
t.plan(1);
supertest(app)
.get(path)
.expect(200)
.end((err) => {
console.log(`got ${path}`);
t.error(err);
});
});
});
tap.teardown(() => app.close());
console.log("end");
// HACK: This closes the database.
tap.teardown(() => {
console.log("Tearing down.");
app.close();
app._close();
});

16
test/lifecycle.js Normal file
View File

@ -0,0 +1,16 @@
// HACK: Prevent Oasis from opening the web browser.
process.argv.push("--no-open", "--offline");
// This test just ensures that Oasis can open and close cleanly.
const app = require("../src");
const tap = require("tap");
tap.plan(1);
tap.ok(app, "app exists");
setImmediate(() => {
app.close();
app._close();
});