18 Commits

Author SHA1 Message Date
8abcdd1e7d core: Fix unauthenticated sessions.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m38s
Build Tilde Friends / Build-All (push) Successful in 9m52s
2025-12-26 21:53:54 -05:00
97aeff60cc build: Build an aarch64 .AppImage.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m28s
Build Tilde Friends / Build-All (push) Successful in 10m6s
2025-12-26 21:40:11 -05:00
86d6a5c049 update: CodeMirror. 2025-12-26 21:24:52 -05:00
f6f815eec1 ssb: Fix the oblong spinning refresh button. 2025-12-26 21:22:00 -05:00
73a1c1d978 core: Move ssb.getPrivateKey from JS => C.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m25s
Build Tilde Friends / Build-All (push) Successful in 10m35s
2025-12-26 17:27:36 -05:00
5445072d36 core: Move ssb.deleteIdentity from JS => C. 2025-12-26 17:07:07 -05:00
d9a2519e9b core: Move ssb.addItentity() from JS => C. 2025-12-26 16:48:46 -05:00
687a85dbd8 build: Disable -t auto again. Oh well.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m26s
Build Tilde Friends / Build-All (push) Successful in 10m26s
2025-12-26 16:16:33 -05:00
9e25aa1c54 build: Maybe on trixie?
Some checks failed
Build Tilde Friends / Build-Docs (push) Failing after 4m41s
Build Tilde Friends / Build-All (push) Successful in 11m3s
2025-12-26 10:13:08 -05:00
e309f519f2 build: Oops, actually test the thing.
Some checks failed
Build Tilde Friends / Build-Docs (push) Successful in 2m34s
Build Tilde Friends / Build-All (push) Failing after 10m55s
2025-12-25 11:18:46 -05:00
5ccd9f16c3 build: Last try.
Some checks failed
Build Tilde Friends / Build-Docs (push) Successful in 2m31s
Build Tilde Friends / Build-All (push) Has been cancelled
2025-12-25 11:14:10 -05:00
7d596ebd3b build: Maybe like this?
Some checks failed
Build Tilde Friends / Build-Docs (push) Successful in 2m31s
Build Tilde Friends / Build-All (push) Failing after 6s
2025-12-25 11:10:01 -05:00
938f728eb9 build: Just curious, can the CI worker run headless selenium tests?
Some checks failed
Build Tilde Friends / Build-Docs (push) Successful in 2m33s
Build Tilde Friends / Build-All (push) Failing after 12s
2025-12-25 11:03:22 -05:00
6e8a0031a8 bookclub: Handle both about and bookclubUpdate messages. 2025-12-25 10:38:18 -05:00
085f62aadf core: Move ssb.appendMessageWithIdentity() from JS => C, and fix a permission test lifetime issue along the way.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m37s
Build Tilde Friends / Build-All (push) Successful in 9m53s
2025-12-23 17:12:38 -05:00
71d556143b update: CodeMirror.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m30s
Build Tilde Friends / Build-All (push) Successful in 10m24s
2025-12-23 16:12:23 -05:00
081bff9a26 ssb: Load names and profile info for users we see who we're not following.
Some checks failed
Build Tilde Friends / Build-Docs (push) Successful in 2m26s
Build Tilde Friends / Build-All (push) Has been cancelled
2025-12-23 16:09:47 -05:00
f2f4455c82 ssb: Replication fun. Don't request blobs until messages are up to date. Fix multiple issues with blob wants determinating. Fix issues resulting from message added callbacks being merged.
All checks were successful
Build Tilde Friends / Build-Docs (push) Successful in 2m37s
Build Tilde Friends / Build-All (push) Successful in 12m54s
2025-12-23 12:54:01 -05:00
15 changed files with 708 additions and 558 deletions

View File

@@ -1213,7 +1213,26 @@ out/tildefriends-x86_64.AppImage: out/release/tildefriends out/data.zip
@cd out; ./appimagetool --appimage-extract; cd ..
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=x86_64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-x86_64.AppImage.zsync' tildefriends.AppDir tildefriends-x86_64.AppImage; cd ..
appimage: out/tildefriends-x86_64.AppImage ## Build an AppImage.
out/tildefriends-aarch64.AppImage: out/armrelease/tildefriends out/data.zip
@echo "[appimage] $$@"
@rm -rf out/tildefriends_aarch64.AppDir
@mkdir -p out/tildefriends_aarch64.AppDir/usr/bin
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/applications
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/icons/hicolor/scalable/apps
@mkdir -p out/tildefriends_aarch64.AppDir/usr/share/tildefriends
@echo $(APPIMAGETOOL_MD5) > out/appimagetool.md5
@test -x out/appimagetool || curl -q -L -o out/appimagetool $(APPIMAGETOOL_URL) && md5sum -c out/appimagetool.md5 && chmod +x out/appimagetool
@echo "[Desktop Entry]\nName=tildefriends\nExec=/usr/bin/tildefriends\nIcon=/usr/share/icons/hicolor/scalable/apps/tildefriends\nType=Application\nCategories=Network" > out/tildefriends_aarch64.AppDir/tildefriends.desktop
@cp src/ios/tildefriends.svg out/tildefriends_aarch64.AppDir/usr/share/icons/hicolor/scalable/apps/
@cp src/ios/tildefriends.svg out/tildefriends_aarch64.AppDir/
@cp out/armrelease/tildefriends out/tildefriends_aarch64.AppDir/usr/bin/
@cp out/data.zip out/tildefriends_aarch64.AppDir/usr/share/tildefriends/data.zip
@echo "#!/bin/sh\n\$${APPDIR}/usr/bin/tildefriends run -z \$$APPDIR/usr/share/tildefriends/data.zip" > out/tildefriends_aarch64.AppDir/AppRun
@chmod +x out/tildefriends_aarch64.AppDir/AppRun
@cd out; ./appimagetool --appimage-extract; cd ..
@cd out; unset SOURCE_DATE_EPOCH; PATH=$$PATH:squashfs-root/usr/bin ARCH=arm_aarch64 squashfs-root/usr/bin/appimagetool -u 'zsync|https://dev.tildefriends.net/releases/tildefriends-aarch64.AppImage.zsync' tildefriends_aarch64.AppDir tildefriends-aarch64.AppImage; cd ..
appimage: out/tildefriends-x86_64.AppImage out/tildefriends-aarch64.AppImage ## Build AppImages.
.PHONY: appimage
flatpak: out/ ## Build a flatpak.

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "📚",
"previous": "&EO5ifwzemEeSJsN6SJ2VTyE+sqnwU2gikIngQimwnDo=.sha256"
"emoji": "📖",
"previous": "&u7ri5Gi1AK6SbWRmc3S8vN40QrWL90/DKDiDTeDDiPQ=.sha256"
}

View File

@@ -46,12 +46,25 @@ async function main() {
fields.key,
RANK() OVER (PARTITION BY messages.author, messages.content ->> '$.about', fields.key ORDER BY messages.sequence DESC) AS rank,
fields.value
FROM messages, json_each(messages.content) AS fields, json_each(?) AS book, json_each(?) AS following
FROM messages, json_each(messages.content) AS fields, json_each(?1) AS book, json_each(?2) AS following
ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'about'
AND messages.content ->> '$.about' = book.value
AND NOT fields.key IN ('about', 'type')
UNION
SELECT
messages.author,
messages.content ->> '$.updates' AS about,
fields.key,
RANK() OVER (PARTITION BY messages.author, messages.content ->> '$.updates', fields.key ORDER BY messages.sequence DESC) AS rank,
fields.value
FROM messages, json_each(messages.content) AS fields, json_each(?1) AS book, json_each(?2) AS following
ON messages.author = following.value
WHERE
messages.content ->> 'type' = 'bookclubUpdate'
AND messages.content ->> '$.updates' = book.value
AND NOT fields.key IN ('about', 'updates', 'type')
) WHERE rank = 1
GROUP BY author, about
`,

View File

@@ -1,5 +1,5 @@
{
"type": "tildefriends-app",
"emoji": "🦀",
"previous": "&UUj21pyEAxsCGoNK0hk9Ue72kfpwfFKTzZDLP8fJ08g=.sha256"
"previous": "&aZw1V6n7AbOjTcVobViT2BpsIo7x1PFY950HHJpdBfE=.sha256"
}

View File

@@ -236,7 +236,7 @@ class TfElement extends LitElement {
}
}
async fetch_about(following, users) {
async fetch_about(following, users, transient) {
this.loading_about++;
let ids = Object.keys(following).sort();
const k_cache_version = 3;
@@ -320,7 +320,7 @@ class TfElement extends LitElement {
this.loading_about--;
let new_cache = JSON.stringify(cache);
if (new_cache != original_cache) {
if (!transient && new_cache != original_cache) {
let start_time = new Date();
tfrpc.rpc.databaseSet('about', new_cache).then(function () {
console.log('saving about took', (new Date() - start_time) / 1000);
@@ -802,6 +802,18 @@ class TfElement extends LitElement {
}
}
async request_user(event) {
let users = {};
users[event.detail.id] = {};
users = await this.fetch_user_info(users);
if (this.users[event.detail.id]?.seq !== users[event.detail.id]?.seq) {
let self = this;
this.fetch_about(users, users, true).then(function (result) {
self.users = Object.assign({}, self.users, users);
});
}
}
render() {
let self = this;
@@ -854,16 +866,20 @@ class TfElement extends LitElement {
this.is_administrator
? html`
<button
class=${'w3-bar-item w3-button w3-circle w3-ripple w3-right' +
(this.connections?.some((x) => x.flags.one_shot)
? ' w3-spin'
: '')}
class="w3-bar-item w3-button w3-circle w3-right"
@click=${this.refresh}
>
<span
style="display: inline-block"
class=${this.connections?.some((x) => x.flags.one_shot)
? ' w3-spin'
: ''}
>
</span>
</button>
<button
class="w3-bar-item w3-button w3-ripple w3-right"
class="w3-bar-item w3-button w3-right"
@click=${this.toggle_stay_connected}
>
${this.stay_connected ? '🔗' : '⛓️‍💥'}
@@ -914,6 +930,7 @@ class TfElement extends LitElement {
<div
style="width: 100vw; min-height: 100vh; height: 100vh; display: flex; flex-direction: column"
class="w3-theme-dark"
@tf-request-user=${this.request_user}
>
${progress}
<div style="flex: 0 0">${tabs}</div>

View File

@@ -25,6 +25,15 @@ class TfUserElement extends LitElement {
render() {
let user = this.users[this.id];
if (!this.users[this.id]) {
this.dispatchEvent(
new CustomEvent('tf-request-user', {
bubbles: true,
composed: true,
detail: {id: this.id},
})
);
}
let shape =
user?.follow_depth === undefined || user.follow_depth >= 2
? 'w3-circle'

View File

@@ -375,73 +375,7 @@ exports.getProcessBlob = async function getProcessBlob(blobId, key, options) {
Object.keys(ssb).map((key) => [key, ssb[key].bind(ssb)])
);
imports.ssb.createIdentity = () => process.createIdentity();
imports.ssb.addIdentity = function (id) {
if (
process.credentials &&
process.credentials.session &&
process.credentials.session.name
) {
return Promise.resolve(
imports.core.permissionTest('ssb_id_add')
).then(function () {
return ssb.addIdentity(process.credentials.session.name, id);
});
}
};
imports.ssb.deleteIdentity = function (id) {
if (
process.credentials &&
process.credentials.session &&
process.credentials.session.name
) {
return Promise.resolve(
imports.core.permissionTest('ssb_id_delete')
).then(function () {
return ssb.deleteIdentity(process.credentials.session.name, id);
});
}
};
imports.ssb.setActiveIdentity = (id) => process.setActiveIdentity(id);
imports.ssb.getPrivateKey = function (id) {
if (
process.credentials &&
process.credentials.session &&
process.credentials.session.name
) {
return Promise.resolve(
imports.core.permissionTest('ssb_id_export')
).then(function () {
return ssb.getPrivateKey(process.credentials.session.name, id);
});
}
};
imports.ssb.appendMessageWithIdentity = function (id, message) {
if (
process.credentials &&
process.credentials.session &&
process.credentials.session.name
) {
let action;
try {
if (message?.type === 'vote' && message?.vote?.expression) {
action = `React with ${message?.vote?.expression}.`;
} else if (typeof message === 'string') {
action = `Post a private message.`;
} else {
action = `Publish ${'aeiou'.indexOf(message?.type?.toLowerCase()?.substring(0, 1)) != -1 ? 'an' : 'a'} "${message?.type}" message.`;
}
} catch {}
return Promise.resolve(
imports.core.permissionTest('ssb_append', action)
).then(function () {
return ssb.appendMessageWithIdentity(
process.credentials.session.name,
id,
message
);
});
}
};
if (
process.credentials &&
process.credentials.session &&

File diff suppressed because one or more lines are too long

222
deps/codemirror_src/package-lock.json generated vendored
View File

@@ -129,14 +129,14 @@
}
},
"node_modules/@codemirror/language": {
"version": "6.11.3",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz",
"integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==",
"version": "6.12.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz",
"integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.23.0",
"@lezer/common": "^1.1.0",
"@lezer/common": "^1.5.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
@@ -165,9 +165,9 @@
}
},
"node_modules/@codemirror/state": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.3.tgz",
"integrity": "sha512-MerMzJzlXogk2fxWFU1nKp36bY5orBG59HnPiz0G9nLRebWa0zXuv2siH6PLIHBvv5TH8CkQRqjBs0MlxCZu+A==",
"license": "MIT",
"dependencies": {
"@marijn/find-cluster-break": "^1.0.0"
@@ -186,9 +186,9 @@
}
},
"node_modules/@codemirror/view": {
"version": "6.39.4",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.4.tgz",
"integrity": "sha512-xMF6OfEAUVY5Waega4juo1QGACfNkNF+aJLqpd8oUJz96ms2zbfQ9Gh35/tI3y8akEV31FruKfj7hBnIU/nkqA==",
"version": "6.39.7",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.7.tgz",
"integrity": "sha512-3Vif9hnNHJnl2YgOtkR/wzGzhYcQ8gy3LGdUhkLUU8xSBbgsTxrE8he/CMTpeINm5TgxLe2FmzvF6IYQL/BSAg==",
"license": "MIT",
"dependencies": {
"@codemirror/state": "^6.5.0",
@@ -248,9 +248,9 @@
}
},
"node_modules/@lezer/common": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz",
"integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz",
"integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==",
"license": "MIT"
},
"node_modules/@lezer/css": {
@@ -274,9 +274,9 @@
}
},
"node_modules/@lezer/html": {
"version": "1.3.12",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz",
"integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==",
"version": "1.3.13",
"resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz",
"integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.2.0",
@@ -316,12 +316,12 @@
}
},
"node_modules/@lezer/markdown": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.1.tgz",
"integrity": "sha512-72ah+Sml7lD8Wn7lnz9vwYmZBo9aQT+I2gjK/0epI+gjdwUbWw3MJ/ZBGEqG1UfrIauRqH37/c5mVHXeCTGXtA==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.2.tgz",
"integrity": "sha512-iNSdKrIK0FfOjVPVpV0fu7OykdncYpEzf4vkG9szFf60ql/ObZShoVbM9u1tgkogDOmubms1CyoNS2/unOXWNw==",
"license": "MIT",
"dependencies": {
"@lezer/common": "^1.0.0",
"@lezer/common": "^1.5.0",
"@lezer/highlight": "^1.0.0"
}
},
@@ -412,9 +412,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz",
"integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz",
"integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==",
"cpu": [
"arm"
],
@@ -425,9 +425,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz",
"integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz",
"integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==",
"cpu": [
"arm64"
],
@@ -438,9 +438,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz",
"integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz",
"integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==",
"cpu": [
"arm64"
],
@@ -451,9 +451,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz",
"integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz",
"integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==",
"cpu": [
"x64"
],
@@ -464,9 +464,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz",
"integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz",
"integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==",
"cpu": [
"arm64"
],
@@ -477,9 +477,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz",
"integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz",
"integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==",
"cpu": [
"x64"
],
@@ -490,9 +490,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz",
"integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz",
"integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==",
"cpu": [
"arm"
],
@@ -503,9 +503,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz",
"integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz",
"integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==",
"cpu": [
"arm"
],
@@ -516,9 +516,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz",
"integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz",
"integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==",
"cpu": [
"arm64"
],
@@ -529,9 +529,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz",
"integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz",
"integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==",
"cpu": [
"arm64"
],
@@ -542,9 +542,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz",
"integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz",
"integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==",
"cpu": [
"loong64"
],
@@ -555,9 +555,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz",
"integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz",
"integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==",
"cpu": [
"ppc64"
],
@@ -568,9 +568,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz",
"integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz",
"integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==",
"cpu": [
"riscv64"
],
@@ -581,9 +581,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz",
"integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz",
"integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==",
"cpu": [
"riscv64"
],
@@ -594,9 +594,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz",
"integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz",
"integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==",
"cpu": [
"s390x"
],
@@ -607,9 +607,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz",
"integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz",
"integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==",
"cpu": [
"x64"
],
@@ -620,9 +620,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz",
"integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz",
"integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==",
"cpu": [
"x64"
],
@@ -633,9 +633,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz",
"integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz",
"integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==",
"cpu": [
"arm64"
],
@@ -646,9 +646,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz",
"integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz",
"integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==",
"cpu": [
"arm64"
],
@@ -659,9 +659,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz",
"integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz",
"integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==",
"cpu": [
"ia32"
],
@@ -672,9 +672,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz",
"integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz",
"integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==",
"cpu": [
"x64"
],
@@ -685,9 +685,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz",
"integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz",
"integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==",
"cpu": [
"x64"
],
@@ -877,9 +877,9 @@
}
},
"node_modules/rollup": {
"version": "4.53.5",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz",
"integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==",
"version": "4.54.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz",
"integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
"license": "MIT",
"dependencies": {
"@types/estree": "1.0.8"
@@ -892,28 +892,28 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.53.5",
"@rollup/rollup-android-arm64": "4.53.5",
"@rollup/rollup-darwin-arm64": "4.53.5",
"@rollup/rollup-darwin-x64": "4.53.5",
"@rollup/rollup-freebsd-arm64": "4.53.5",
"@rollup/rollup-freebsd-x64": "4.53.5",
"@rollup/rollup-linux-arm-gnueabihf": "4.53.5",
"@rollup/rollup-linux-arm-musleabihf": "4.53.5",
"@rollup/rollup-linux-arm64-gnu": "4.53.5",
"@rollup/rollup-linux-arm64-musl": "4.53.5",
"@rollup/rollup-linux-loong64-gnu": "4.53.5",
"@rollup/rollup-linux-ppc64-gnu": "4.53.5",
"@rollup/rollup-linux-riscv64-gnu": "4.53.5",
"@rollup/rollup-linux-riscv64-musl": "4.53.5",
"@rollup/rollup-linux-s390x-gnu": "4.53.5",
"@rollup/rollup-linux-x64-gnu": "4.53.5",
"@rollup/rollup-linux-x64-musl": "4.53.5",
"@rollup/rollup-openharmony-arm64": "4.53.5",
"@rollup/rollup-win32-arm64-msvc": "4.53.5",
"@rollup/rollup-win32-ia32-msvc": "4.53.5",
"@rollup/rollup-win32-x64-gnu": "4.53.5",
"@rollup/rollup-win32-x64-msvc": "4.53.5",
"@rollup/rollup-android-arm-eabi": "4.54.0",
"@rollup/rollup-android-arm64": "4.54.0",
"@rollup/rollup-darwin-arm64": "4.54.0",
"@rollup/rollup-darwin-x64": "4.54.0",
"@rollup/rollup-freebsd-arm64": "4.54.0",
"@rollup/rollup-freebsd-x64": "4.54.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.54.0",
"@rollup/rollup-linux-arm-musleabihf": "4.54.0",
"@rollup/rollup-linux-arm64-gnu": "4.54.0",
"@rollup/rollup-linux-arm64-musl": "4.54.0",
"@rollup/rollup-linux-loong64-gnu": "4.54.0",
"@rollup/rollup-linux-ppc64-gnu": "4.54.0",
"@rollup/rollup-linux-riscv64-gnu": "4.54.0",
"@rollup/rollup-linux-riscv64-musl": "4.54.0",
"@rollup/rollup-linux-s390x-gnu": "4.54.0",
"@rollup/rollup-linux-x64-gnu": "4.54.0",
"@rollup/rollup-linux-x64-musl": "4.54.0",
"@rollup/rollup-openharmony-arm64": "4.54.0",
"@rollup/rollup-win32-arm64-msvc": "4.54.0",
"@rollup/rollup-win32-ia32-msvc": "4.54.0",
"@rollup/rollup-win32-x64-gnu": "4.54.0",
"@rollup/rollup-win32-x64-msvc": "4.54.0",
"fsevents": "~2.3.2"
}
},

View File

@@ -24,6 +24,8 @@
#include <alloca.h>
#endif
static JSClassID s_permission_test_class_id;
typedef struct _app_path_pair_t
{
const char* app;
@@ -884,38 +886,59 @@ typedef void(permission_test_callback_t)(JSContext* context, bool granted, JSVal
typedef struct _permission_test_t
{
JSContext* context;
bool completed;
permission_test_callback_t* callback;
void* user_data;
} permission_test_t;
static JSValue _tf_ssb_permission_test_resolve(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSClassID class_id = 0;
permission_test_t* work = JS_GetAnyOpaque(data[0], &class_id);
JS_FreeValue(context, data[0]);
permission_test_t* work = JS_GetOpaque(data[0], s_permission_test_class_id);
work->completed = true;
work->callback(context, true, argv[0], work->user_data);
tf_free(work);
return JS_UNDEFINED;
}
static JSValue _tf_ssb_permission_test_reject(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSClassID class_id = 0;
permission_test_t* work = JS_GetAnyOpaque(data[0], &class_id);
JS_FreeValue(context, data[0]);
permission_test_t* work = JS_GetOpaque(data[0], s_permission_test_class_id);
work->completed = true;
work->callback(context, false, argv[0], work->user_data);
tf_free(work);
return JS_UNDEFINED;
}
static void _tf_ssb_permission_test_finalizer(JSRuntime* runtime, JSValue value)
{
permission_test_t* work = JS_GetOpaque(value, s_permission_test_class_id);
if (!work->completed)
{
JSValue arg = JS_ThrowInternalError(work->context, "Permission test incomplete.");
work->callback(work->context, false, arg, work->user_data);
JS_FreeValue(work->context, arg);
}
tf_free(work);
}
static void _tf_ssb_permission_test(JSContext* context, JSValue process, const char* permission, const char* description, permission_test_callback_t* callback, void* user_data)
{
if (!s_permission_test_class_id)
{
JSClassDef def = {
.class_name = "permission_test",
.finalizer = _tf_ssb_permission_test_finalizer,
};
JS_NewClassID(&s_permission_test_class_id);
JS_NewClass(JS_GetRuntime(context), s_permission_test_class_id, &def);
}
permission_test_t* payload = tf_malloc(sizeof(permission_test_t));
*payload = (permission_test_t) {
.context = context,
.callback = callback,
.user_data = user_data,
};
JSValue opaque = JS_NewObject(context);
JSValue opaque = JS_NewObjectClass(context, s_permission_test_class_id);
JS_SetOpaque(opaque, payload);
JSValue imports = JS_GetPropertyStr(context, process, "imports");
JSValue core = JS_GetPropertyStr(context, imports, "core");
@@ -934,10 +957,11 @@ static void _tf_ssb_permission_test(JSContext* context, JSValue process, const c
JS_FreeValue(context, result);
result = JS_Call(context, catch, promise, 1, &reject);
tf_util_report_error(context, result);
JS_FreeValue(context, result);
JS_FreeValue(context, opaque);
JS_FreeValue(context, promise);
JS_FreeValue(context, resolve);
JS_FreeValue(context, reject);
JS_FreeValue(context, result);
JS_FreeValue(context, then);
JS_FreeValue(context, catch);
for (int i = 0; i < tf_countof(args); i++)
@@ -1577,6 +1601,388 @@ static JSValue _tf_ssb_private_message_decrypt(JSContext* context, JSValueConst
return result;
}
typedef struct _append_message_t
{
char id[k_id_base64_len];
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
bool got_private_key;
char previous_id[512];
int32_t previous_sequence;
JSContext* context;
JSValue promise[2];
JSValue message;
char user[];
} append_message_t;
static void _tf_ssb_appendMessage_finish(append_message_t* async, bool success, JSValue result)
{
JSValue error = JS_Call(async->context, success ? async->promise[0] : async->promise[1], JS_UNDEFINED, 1, &result);
tf_util_report_error(async->context, error);
JS_FreeValue(async->context, error);
JS_FreeValue(async->context, async->message);
JS_FreeValue(async->context, async->promise[0]);
JS_FreeValue(async->context, async->promise[1]);
tf_free(async);
}
static void _tf_ssb_appendMessageWithIdentity_callback(const char* id, bool verified, bool is_new, void* user_data)
{
append_message_t* async = user_data;
JSValue result = JS_UNDEFINED;
if (verified)
{
result = is_new ? JS_TRUE : JS_FALSE;
}
_tf_ssb_appendMessage_finish(async, verified, result);
}
static void _tf_ssb_append_message_with_identity_get_key_work(tf_ssb_t* ssb, void* user_data)
{
append_message_t* work = user_data;
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, work->user, work->id, work->private_key, sizeof(work->private_key));
if (!work->got_private_key && tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
{
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, ":admin", work->id, work->private_key, sizeof(work->private_key));
}
tf_ssb_db_get_latest_message_by_author(ssb, work->id, &work->previous_sequence, work->previous_id, sizeof(work->previous_id));
}
static void _tf_ssb_append_message_with_identity_get_key_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
append_message_t* work = user_data;
if (work->got_private_key)
{
JSValue signed_message = tf_ssb_sign_message(ssb, work->id, work->private_key, work->message, work->previous_id, work->previous_sequence);
tf_ssb_verify_strip_and_store_message(ssb, signed_message, _tf_ssb_appendMessageWithIdentity_callback, work);
JS_FreeValue(work->context, signed_message);
}
else
{
_tf_ssb_appendMessage_finish(work, false, JS_ThrowInternalError(work->context, "Unable to get private key for user %s with identity %s.", work->user, work->id));
}
}
static void _tf_ssb_append_message_permission_callback(JSContext* context, bool granted, JSValue value, void* user_data)
{
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
append_message_t* work = user_data;
if (granted)
{
tf_ssb_run_work(ssb, _tf_ssb_append_message_with_identity_get_key_work, _tf_ssb_append_message_with_identity_get_key_after_work, work);
}
else
{
_tf_ssb_appendMessage_finish(work, false, value);
}
}
static JSValue _tf_ssb_appendMessageWithIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSValue process = data[0];
char description[1024] = "Publish a new message.";
const char* type = tf_util_get_property_as_string(context, argv[1], "type");
if (type)
{
if (strcmp(type, "vote") == 0)
{
JSValue vote = JS_GetPropertyStr(context, argv[1], "vote");
const char* expression = tf_util_get_property_as_string(context, vote, "expression");
snprintf(description, sizeof(description), "React with %s.", expression);
JS_FreeCString(context, expression);
JS_FreeValue(context, vote);
}
else
{
snprintf(description, sizeof(description), "Publish a new %s message.", type);
}
}
else if (JS_IsString(argv[1]))
{
tf_string_set(description, sizeof(description), "Publish a new private message.");
}
JS_FreeCString(context, type);
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
if (!user)
{
return JS_ThrowInternalError(context, "Invalid user.");
}
else
{
size_t user_length = strlen(user);
const char* id = JS_ToCString(context, argv[0]);
append_message_t* work = tf_malloc(sizeof(append_message_t) + user_length + 1);
*work = (append_message_t) { .context = context, .message = JS_DupValue(context, argv[1]) };
memcpy(work->user, user, user_length + 1);
tf_string_set(work->id, sizeof(work->id), id);
JS_FreeCString(context, user);
JS_FreeCString(context, id);
JSValue result = JS_NewPromiseCapability(context, work->promise);
_tf_ssb_permission_test(context, process, "ssb_append", description, _tf_ssb_append_message_permission_callback, work);
return result;
}
}
typedef struct _add_identity_t
{
uint8_t key[crypto_sign_SECRETKEYBYTES / 2];
bool added;
JSValue result;
JSValue promise[2];
char user[];
} add_identity_t;
static void _tf_ssb_add_identity_work(tf_ssb_t* ssb, void* user_data)
{
add_identity_t* work = user_data;
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
unsigned char seed[crypto_sign_SEEDBYTES];
uint8_t secret_key[crypto_sign_SECRETKEYBYTES] = { 0 };
memcpy(secret_key, work->key, sizeof(secret_key) / 2);
if (crypto_sign_ed25519_sk_to_seed(seed, secret_key) == 0 && crypto_sign_seed_keypair(public_key, secret_key, seed) == 0)
{
char public_key_b64[512];
tf_base64_encode(public_key, sizeof(public_key), public_key_b64, sizeof(public_key_b64));
snprintf(public_key_b64 + strlen(public_key_b64), sizeof(public_key_b64) - strlen(public_key_b64), ".ed25519");
uint8_t combined[crypto_sign_SECRETKEYBYTES];
memcpy(combined, work->key, sizeof(work->key));
memcpy(combined + sizeof(work->key), public_key, sizeof(public_key));
char combined_b64[512];
tf_base64_encode(combined, sizeof(combined), combined_b64, sizeof(combined_b64));
snprintf(combined_b64 + strlen(combined_b64), sizeof(combined_b64) - strlen(combined_b64), ".ed25519");
work->added = tf_ssb_db_identity_add(ssb, work->user, public_key_b64, combined_b64);
}
}
static void _tf_ssb_add_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
add_identity_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = JS_IsUndefined(work->result) ? (work->added ? JS_TRUE : JS_UNDEFINED) : work->result;
JSValue error = JS_Call(context, work->promise[work->added ? 0 : 1], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->result);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static void _tf_ssb_add_identity_permission_callback(JSContext* context, bool granted, JSValue value, void* user_data)
{
add_identity_t* work = user_data;
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
if (granted)
{
tf_ssb_run_work(ssb, _tf_ssb_add_identity_work, _tf_ssb_add_identity_after_work, work);
}
else
{
work->result = JS_DupValue(context, value);
_tf_ssb_add_identity_after_work(ssb, -1, work);
}
}
static JSValue _tf_ssb_addIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSValue process = data[0];
JSValue result = JS_UNDEFINED;
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
size_t user_length = user ? strlen(user) : 0;
JSValue buffer = JS_UNDEFINED;
size_t length = 0;
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[0]);
if (!array)
{
size_t offset;
size_t element_size;
buffer = tf_util_try_get_typed_array_buffer(context, argv[0], &offset, &length, &element_size);
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &length, buffer);
}
}
if (array)
{
if (length == crypto_sign_SECRETKEYBYTES / 2)
{
add_identity_t* work = tf_malloc(sizeof(add_identity_t) + user_length + 1);
*work = (add_identity_t) { .result = JS_UNDEFINED };
memcpy(work->key, array, sizeof(work->key));
if (user)
{
memcpy(work->user, user, user_length + 1);
}
result = JS_NewPromiseCapability(context, work->promise);
_tf_ssb_permission_test(context, process, "ssb_id_add", "Add an identity.", _tf_ssb_add_identity_permission_callback, work);
}
else
{
result = JS_ThrowInternalError(context, "Unexpected private key size: %d vs. %d\n", (int)length, crypto_sign_SECRETKEYBYTES);
}
}
else
{
result = JS_ThrowInternalError(context, "Expected array argument.");
}
JS_FreeValue(context, buffer);
JS_FreeCString(context, user);
return result;
}
typedef struct _delete_identity_t
{
char id[k_id_base64_len];
bool deleted;
JSValue result;
JSValue promise[2];
char user[];
} delete_identity_t;
static void _tf_ssb_delete_identity_work(tf_ssb_t* ssb, void* user_data)
{
delete_identity_t* work = user_data;
work->deleted = tf_ssb_db_identity_delete(ssb, work->user, work->id);
}
static void _tf_ssb_delete_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
delete_identity_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = work->deleted ? JS_TRUE : JS_FALSE;
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static void _tf_ssb_delete_identity_permission_callback(JSContext* context, bool granted, JSValue value, void* user_data)
{
delete_identity_t* work = user_data;
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
if (granted)
{
tf_ssb_run_work(ssb, _tf_ssb_delete_identity_work, _tf_ssb_delete_identity_after_work, work);
}
else
{
work->result = JS_DupValue(context, value);
_tf_ssb_delete_identity_after_work(ssb, -1, work);
}
}
static JSValue _tf_ssb_deleteIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSValue result = JS_UNDEFINED;
JSValue process = data[0];
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
const char* id = JS_ToCString(context, argv[0]);
if (id && user)
{
size_t user_length = strlen(user);
delete_identity_t* work = tf_malloc(sizeof(delete_identity_t) + user_length + 1);
*work = (delete_identity_t) { .result = JS_UNDEFINED };
tf_string_set(work->id, sizeof(work->id), *id == '@' ? id + 1 : id);
memcpy(work->user, user, user_length + 1);
result = JS_NewPromiseCapability(context, work->promise);
_tf_ssb_permission_test(context, process, "ssb_id_delete", "Delete an identity.", _tf_ssb_delete_identity_permission_callback, work);
}
JS_FreeCString(context, id);
JS_FreeCString(context, user);
return result;
}
typedef struct _get_private_key_t
{
JSContext* context;
JSValue result;
JSValue promise[2];
char id[k_id_base64_len];
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
bool got_private_key;
char user[];
} get_private_key_t;
static void _tf_ssb_get_private_key_work(tf_ssb_t* ssb, void* user_data)
{
get_private_key_t* work = user_data;
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, work->user, work->id, work->private_key, sizeof(work->private_key));
if (!work->got_private_key && tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
{
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, ":admin", work->id, work->private_key, sizeof(work->private_key));
}
}
static void _tf_ssb_get_private_key_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
get_private_key_t* work = user_data;
JSValue result = JS_UNDEFINED;
JSContext* context = work->context;
if (work->got_private_key && JS_IsUndefined(work->result))
{
result = tf_util_new_uint8_array(context, work->private_key, sizeof(work->private_key) / 2);
}
JSValue error = JS_Call(context, work->promise[work->got_private_key ? 0 : 1], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
JS_FreeValue(context, work->result);
tf_free(work);
}
static void _tf_ssb_get_private_key_permission_callback(JSContext* context, bool granted, JSValue value, void* user_data)
{
get_private_key_t* work = user_data;
tf_task_t* task = tf_task_get(context);
tf_ssb_t* ssb = tf_task_get_ssb(task);
if (granted)
{
tf_ssb_run_work(ssb, _tf_ssb_get_private_key_work, _tf_ssb_get_private_key_after_work, work);
}
else
{
work->result = JS_DupValue(context, value);
_tf_ssb_get_private_key_after_work(ssb, -1, work);
}
}
static JSValue _tf_ssb_getPrivateKey(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data)
{
JSValue process = data[0];
const char* user = _tf_ssb_get_process_credentials_session_name(context, process);
size_t user_length = user ? strlen(user) : 0;
const char* id = JS_ToCString(context, argv[0]);
get_private_key_t* work = tf_malloc(sizeof(get_private_key_t) + user_length + 1);
*work = (get_private_key_t) { .context = context, .result = JS_UNDEFINED };
if (user)
{
memcpy(work->user, user, user_length + 1);
}
tf_string_set(work->id, sizeof(work->id), id);
JSValue result = JS_NewPromiseCapability(context, work->promise);
_tf_ssb_permission_test(context, process, "ssb_id_export", "Export a private key.", _tf_ssb_get_private_key_permission_callback, work);
JS_FreeCString(context, user);
JS_FreeCString(context, id);
return result;
}
static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue imports = argv[0];
@@ -1606,6 +2012,10 @@ static JSValue _tf_api_register_imports(JSContext* context, JSValueConst this_va
JS_SetPropertyStr(context, ssb, "getOwnerIdentities", JS_NewCFunctionData(context, _tf_ssb_getOwnerIdentities, 0, 0, 1, &process));
JS_SetPropertyStr(context, ssb, "privateMessageEncrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_encrypt, 3, 0, 1, &process));
JS_SetPropertyStr(context, ssb, "privateMessageDecrypt", JS_NewCFunctionData(context, _tf_ssb_private_message_decrypt, 2, 0, 1, &process));
JS_SetPropertyStr(context, ssb, "appendMessageWithIdentity", JS_NewCFunctionData(context, _tf_ssb_appendMessageWithIdentity, 2, 0, 1, &process));
JS_SetPropertyStr(context, ssb, "addIdentity", JS_NewCFunctionData(context, _tf_ssb_addIdentity, 1, 0, 1, &process));
JS_SetPropertyStr(context, ssb, "deleteIdentity", JS_NewCFunctionData(context, _tf_ssb_deleteIdentity, 1, 0, 1, &process));
JS_SetPropertyStr(context, ssb, "getPrivateKey", JS_NewCFunctionData(context, _tf_ssb_getPrivateKey, 1, 0, 1, &process));
JS_FreeValue(context, ssb);
JSValue credentials = JS_GetPropertyStr(context, process, "credentials");

View File

@@ -671,7 +671,7 @@ static void _httpd_auth_query_after_work(tf_ssb_t* ssb, int status, void* user_d
JSValue out_permissions = JS_NewObject(context);
JS_SetPropertyStr(context, work->credentials, "permissions", out_permissions);
JSValue permissions = !JS_IsUndefined(settings_value) ? JS_GetPropertyStr(context, settings_value, "permissions") : JS_UNDEFINED;
JSValue user_permissions = !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
JSValue user_permissions = name_string && !JS_IsUndefined(permissions) ? JS_GetPropertyStr(context, permissions, name_string) : JS_UNDEFINED;
int length = !JS_IsUndefined(user_permissions) ? tf_util_get_length(context, user_permissions) : 0;
for (int i = 0; i < length; i++)
{
@@ -743,7 +743,6 @@ static void _httpd_auth_query_after_work(tf_ssb_t* ssb, int status, void* user_d
tf_free((void*)cookie);
JS_FreeCString(context, name_string);
// tf_http_request_unref(request);
request->on_message = _httpd_app_on_message;
request->on_close = _httpd_app_on_close;
request->context = context;
@@ -769,10 +768,12 @@ void tf_httpd_endpoint_app_socket(tf_http_request_t* request)
JSValue jwt = tf_httpd_authenticate_jwt(ssb, context, session);
tf_free((void*)session);
JSValue credentials = JS_NewObject(context);
if (!JS_IsUndefined(jwt))
{
JSValue credentials = JS_NewObject(context);
JS_SetPropertyStr(context, credentials, "session", jwt);
}
tf_http_request_ref(request);
app_t* work = tf_malloc(sizeof(app_t));
*work = (app_t) {
@@ -783,4 +784,3 @@ void tf_httpd_endpoint_app_socket(tf_http_request_t* request)
uv_timer_init(tf_ssb_get_loop(ssb), &work->timer);
tf_ssb_run_work(ssb, _httpd_auth_query_work, _httpd_auth_query_after_work, work);
}
}

View File

@@ -2,6 +2,7 @@
#include "log.h"
#include "mem.h"
#include "ssb.ebt.h"
#include "ssb.h"
#include "trace.h"
#include "util.js.h"
@@ -440,11 +441,12 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blob_wants_cache_id_idx ON blob_wants_cache (id)");
_tf_ssb_db_exec(db, "CREATE INDEX IF NOT EXISTS blob_wants_cache_timestamp_id_idx ON blob_wants_cache (timestamp, id)");
_tf_ssb_db_exec(db, "DROP TRIGGER IF EXISTS messages_ai_blob_wants_cache");
_tf_ssb_db_exec(db,
"CREATE TRIGGER IF NOT EXISTS messages_ai_blob_wants_cache AFTER INSERT ON messages_refs BEGIN "
"INSERT INTO blob_wants_cache (source, id, timestamp) "
"SELECT messages.id, new.ref, messages.timestamp FROM messages "
"JOIN blobs ON new.ref = blobs.id "
"LEFT OUTER JOIN blobs ON new.ref = blobs.id "
"WHERE messages.id = new.message AND "
"LENGTH(new.ref) = 52 AND new.ref LIKE '&%.sha256' AND "
"blobs.content IS NULL "
@@ -455,7 +457,7 @@ void tf_ssb_db_init(tf_ssb_t* ssb)
"INSERT INTO blob_wants_cache (source, id, timestamp) "
"SELECT messages.id, new.ref, messages.timestamp FROM messages "
"JOIN blob_wants_cache bwc ON bwc.source = messages.id AND bwc.id = new.blob "
"JOIN blobs ON bwc.id = blobs.id "
"LEFT OUTER JOIN blobs ON bwc.id = blobs.id "
"WHERE blobs.content IS NULL "
"ON CONFLICT (source, id) DO NOTHING; END");
_tf_ssb_db_exec(db,
@@ -534,18 +536,13 @@ static bool _tf_ssb_db_previous_message_exists(sqlite3* db, const char* author,
return exists;
}
static int64_t _tf_ssb_db_store_message_raw(sqlite3* db, const char* id, const char* previous, const char* author, int32_t sequence, double timestamp, const char* content,
size_t content_len, const char* signature, int flags)
static int64_t _tf_ssb_db_store_message_raw(sqlite3* db, sqlite3_stmt* statement, const char* id, const char* previous, const char* author, int32_t sequence, double timestamp,
const char* content, size_t content_len, const char* signature, int flags)
{
int64_t last_row_id = -1;
bool id_mismatch = false;
if (_tf_ssb_db_previous_message_exists(db, author, sequence, previous, &id_mismatch))
{
const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, flags) VALUES (?, ?, ?, ?, ?, jsonb(?), "
"?, ?, ?) ON CONFLICT DO NOTHING";
sqlite3_stmt* statement;
if (sqlite3_prepare_v2(db, query, -1, &statement, NULL) == SQLITE_OK)
{
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
(previous ? sqlite3_bind_text(statement, 2, previous, -1, NULL) : sqlite3_bind_null(statement, 2)) == SQLITE_OK &&
@@ -566,14 +563,9 @@ static int64_t _tf_ssb_db_store_message_raw(sqlite3* db, const char* id, const c
}
else
{
tf_printf("bind failed\n");
}
sqlite3_finalize(statement);
}
else
{
tf_printf("%s: prepare failed: %s\n", __FUNCTION__, sqlite3_errmsg(db));
tf_printf("bind failed: %s\n", sqlite3_errmsg(db));
}
sqlite3_reset(statement);
}
else if (id_mismatch)
{
@@ -665,10 +657,15 @@ static void _tf_ssb_db_store_message_work(tf_ssb_t* ssb, void* user_data)
sqlite3* db = tf_ssb_acquire_db_writer(ssb);
bool in_transaction = _tf_ssb_db_try_exec(db, "BEGIN TRANSACTION") == SQLITE_OK;
const char* query = "INSERT INTO messages (id, previous, author, sequence, timestamp, content, hash, signature, flags) VALUES (?, ?, ?, ?, ?, jsonb(?), "
"?, ?, ?) ON CONFLICT DO NOTHING";
sqlite3_stmt* insert_statement = NULL;
if (sqlite3_prepare_v2(db, query, -1, &insert_statement, NULL) == SQLITE_OK)
{
while (store)
{
int64_t last_row_id = _tf_ssb_db_store_message_raw(db, store->id, *store->previous ? store->previous : NULL, store->author, store->sequence, store->timestamp,
store->content, store->length, store->signature, store->flags);
int64_t last_row_id = _tf_ssb_db_store_message_raw(db, insert_statement, store->id, *store->previous ? store->previous : NULL, store->author, store->sequence,
store->timestamp, store->content, store->length, store->signature, store->flags);
if (last_row_id != -1)
{
store->out_stored = true;
@@ -676,6 +673,12 @@ static void _tf_ssb_db_store_message_work(tf_ssb_t* ssb, void* user_data)
}
store = store->next;
}
sqlite3_finalize(insert_statement);
}
else
{
tf_printf("prepare failed: %s.\n", sqlite3_errmsg(db));
}
if (in_transaction)
{
@@ -760,6 +763,22 @@ static void _tf_ssb_db_store_message_after_work(tf_ssb_t* ssb, int status, void*
store = store->next;
}
tf_ssb_connection_t* connections[256];
int count = tf_ssb_get_connections(ssb, connections, tf_countof(connections));
store = user_data;
while (store)
{
for (int i = 0; i < count; i++)
{
tf_ssb_connection_t* connection = connections[i];
if (tf_ssb_connection_is_connected(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)) && !tf_ssb_connection_is_closing(connection))
{
tf_ssb_ebt_set_messages_received(tf_ssb_connection_get_ebt(connections[i]), store->author, store->sequence);
}
}
store = store->next;
}
if (last_stored)
{
tf_trace_begin(trace, "notify_message_added");

View File

@@ -18,8 +18,6 @@ static const int k_sql_async_timeout_ms = 60 * 1000;
static JSClassID _tf_ssb_classId;
static JSValue _tf_ssb_appendMessageWithIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
typedef struct _create_identity_t
{
char id[k_id_base64_len];
@@ -98,209 +96,6 @@ static JSValue _tf_ssb_createIdentity(JSContext* context, JSValueConst this_val,
return result;
}
typedef struct _add_identity_t
{
uint8_t key[crypto_sign_SECRETKEYBYTES / 2];
bool added;
JSValue promise[2];
char user[];
} add_identity_t;
static void _tf_ssb_add_identity_work(tf_ssb_t* ssb, void* user_data)
{
add_identity_t* work = user_data;
uint8_t public_key[crypto_sign_PUBLICKEYBYTES];
unsigned char seed[crypto_sign_SEEDBYTES];
uint8_t secret_key[crypto_sign_SECRETKEYBYTES] = { 0 };
memcpy(secret_key, work->key, sizeof(secret_key) / 2);
if (crypto_sign_ed25519_sk_to_seed(seed, secret_key) == 0 && crypto_sign_seed_keypair(public_key, secret_key, seed) == 0)
{
char public_key_b64[512];
tf_base64_encode(public_key, sizeof(public_key), public_key_b64, sizeof(public_key_b64));
snprintf(public_key_b64 + strlen(public_key_b64), sizeof(public_key_b64) - strlen(public_key_b64), ".ed25519");
uint8_t combined[crypto_sign_SECRETKEYBYTES];
memcpy(combined, work->key, sizeof(work->key));
memcpy(combined + sizeof(work->key), public_key, sizeof(public_key));
char combined_b64[512];
tf_base64_encode(combined, sizeof(combined), combined_b64, sizeof(combined_b64));
snprintf(combined_b64 + strlen(combined_b64), sizeof(combined_b64) - strlen(combined_b64), ".ed25519");
work->added = tf_ssb_db_identity_add(ssb, work->user, public_key_b64, combined_b64);
}
}
static void _tf_ssb_add_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
add_identity_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = work->added ? JS_TRUE : JS_UNDEFINED;
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static JSValue _tf_ssb_addIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
JSValue result = JS_UNDEFINED;
if (ssb)
{
size_t user_length = 0;
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
JSValue buffer = JS_UNDEFINED;
size_t length = 0;
uint8_t* array = tf_util_try_get_array_buffer(context, &length, argv[1]);
if (!array)
{
size_t offset;
size_t element_size;
buffer = tf_util_try_get_typed_array_buffer(context, argv[1], &offset, &length, &element_size);
if (!JS_IsException(buffer))
{
array = tf_util_try_get_array_buffer(context, &length, buffer);
}
}
if (array)
{
if (length == crypto_sign_SECRETKEYBYTES / 2)
{
add_identity_t* work = tf_malloc(sizeof(add_identity_t) + user_length + 1);
*work = (add_identity_t) { 0 };
memcpy(work->key, array, sizeof(work->key));
memcpy(work->user, user, user_length + 1);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_add_identity_work, _tf_ssb_add_identity_after_work, work);
}
else
{
result = JS_ThrowInternalError(context, "Unexpected private key size: %d vs. %d\n", (int)length, crypto_sign_SECRETKEYBYTES);
}
}
else
{
result = JS_ThrowInternalError(context, "Expected array argument.");
}
JS_FreeValue(context, buffer);
JS_FreeCString(context, user);
}
return result;
}
typedef struct _delete_identity_t
{
char id[k_id_base64_len];
bool deleted;
JSValue promise[2];
char user[];
} delete_identity_t;
static void _tf_ssb_delete_identity_work(tf_ssb_t* ssb, void* user_data)
{
delete_identity_t* work = user_data;
work->deleted = tf_ssb_db_identity_delete(ssb, work->user, work->id);
}
static void _tf_ssb_delete_identity_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
delete_identity_t* work = user_data;
JSContext* context = tf_ssb_get_context(ssb);
JSValue result = work->deleted ? JS_TRUE : JS_FALSE;
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static JSValue _tf_ssb_deleteIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
JSValue result = JS_UNDEFINED;
if (ssb)
{
size_t user_length = 0;
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
const char* id = JS_ToCString(context, argv[1]);
if (id && user)
{
delete_identity_t* work = tf_malloc(sizeof(delete_identity_t) + user_length + 1);
*work = (delete_identity_t) { 0 };
tf_string_set(work->id, sizeof(work->id), *id == '@' ? id + 1 : id);
memcpy(work->user, user, user_length + 1);
result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_delete_identity_work, _tf_ssb_delete_identity_after_work, work);
}
JS_FreeCString(context, id);
JS_FreeCString(context, user);
}
return result;
}
typedef struct _get_private_key_t
{
JSContext* context;
JSValue promise[2];
char id[k_id_base64_len];
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
bool got_private_key;
char user[];
} get_private_key_t;
static void _tf_ssb_get_private_key_work(tf_ssb_t* ssb, void* user_data)
{
get_private_key_t* work = user_data;
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, work->user, work->id, work->private_key, sizeof(work->private_key));
if (!work->got_private_key && tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
{
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, ":admin", work->id, work->private_key, sizeof(work->private_key));
}
}
static void _tf_ssb_get_private_key_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
get_private_key_t* work = user_data;
JSValue result = JS_UNDEFINED;
JSContext* context = work->context;
if (work->got_private_key)
{
result = tf_util_new_uint8_array(context, work->private_key, sizeof(work->private_key) / 2);
}
JSValue error = JS_Call(context, work->promise[0], JS_UNDEFINED, 1, &result);
JS_FreeValue(context, result);
tf_util_report_error(context, error);
JS_FreeValue(context, error);
JS_FreeValue(context, work->promise[0]);
JS_FreeValue(context, work->promise[1]);
tf_free(work);
}
static JSValue _tf_ssb_getPrivateKey(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
size_t user_length = 0;
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
const char* id = JS_ToCString(context, argv[1]);
get_private_key_t* work = tf_malloc(sizeof(get_private_key_t) + user_length + 1);
*work = (get_private_key_t) { .context = context };
memcpy(work->user, user, user_length + 1);
tf_string_set(work->id, sizeof(work->id), id);
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_get_private_key_work, _tf_ssb_get_private_key_after_work, work);
JS_FreeCString(context, user);
JS_FreeCString(context, id);
return result;
}
static JSValue _tf_ssb_getServerIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
JSValue result = JS_UNDEFINED;
@@ -390,92 +185,6 @@ static JSValue _tf_ssb_getIdentityInfo(JSContext* context, JSValueConst this_val
return result;
}
typedef struct _append_message_t
{
char id[k_id_base64_len];
uint8_t private_key[crypto_sign_SECRETKEYBYTES];
bool got_private_key;
char previous_id[512];
int32_t previous_sequence;
JSContext* context;
JSValue promise[2];
JSValue message;
char user[];
} append_message_t;
static void _tf_ssb_appendMessage_finish(append_message_t* async, bool success, JSValue result)
{
JSValue error = JS_Call(async->context, success ? async->promise[0] : async->promise[1], JS_UNDEFINED, 1, &result);
tf_util_report_error(async->context, error);
JS_FreeValue(async->context, error);
JS_FreeValue(async->context, async->message);
JS_FreeValue(async->context, async->promise[0]);
JS_FreeValue(async->context, async->promise[1]);
tf_free(async);
}
static void _tf_ssb_appendMessageWithIdentity_callback(const char* id, bool verified, bool is_new, void* user_data)
{
append_message_t* async = user_data;
JSValue result = JS_UNDEFINED;
if (verified)
{
result = is_new ? JS_TRUE : JS_FALSE;
}
_tf_ssb_appendMessage_finish(async, verified, result);
}
static void _tf_ssb_append_message_with_identity_get_key_work(tf_ssb_t* ssb, void* user_data)
{
append_message_t* work = user_data;
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, work->user, work->id, work->private_key, sizeof(work->private_key));
if (!work->got_private_key && tf_ssb_db_user_has_permission(ssb, NULL, work->user, "administration"))
{
work->got_private_key = tf_ssb_db_identity_get_private_key(ssb, ":admin", work->id, work->private_key, sizeof(work->private_key));
}
tf_ssb_db_get_latest_message_by_author(ssb, work->id, &work->previous_sequence, work->previous_id, sizeof(work->previous_id));
}
static void _tf_ssb_append_message_with_identity_get_key_after_work(tf_ssb_t* ssb, int status, void* user_data)
{
append_message_t* work = user_data;
if (work->got_private_key)
{
JSValue signed_message = tf_ssb_sign_message(ssb, work->id, work->private_key, work->message, work->previous_id, work->previous_sequence);
tf_ssb_verify_strip_and_store_message(ssb, signed_message, _tf_ssb_appendMessageWithIdentity_callback, work);
JS_FreeValue(work->context, signed_message);
}
else
{
_tf_ssb_appendMessage_finish(work, false, JS_ThrowInternalError(work->context, "Unable to get private key for user %s with identity %s.", work->user, work->id));
}
}
static JSValue _tf_ssb_appendMessageWithIdentity(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
{
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
if (!ssb)
{
return JS_ThrowInternalError(context, "No SSB instance.");
}
size_t user_length = 0;
const char* user = JS_ToCStringLen(context, &user_length, argv[0]);
const char* id = JS_ToCString(context, argv[1]);
append_message_t* work = tf_malloc(sizeof(append_message_t) + user_length + 1);
*work = (append_message_t) { .context = context, .message = JS_DupValue(context, argv[2]) };
memcpy(work->user, user, user_length + 1);
tf_string_set(work->id, sizeof(work->id), id);
JS_FreeCString(context, id);
JS_FreeCString(context, user);
JSValue result = JS_NewPromiseCapability(context, work->promise);
tf_ssb_run_work(ssb, _tf_ssb_append_message_with_identity_get_key_work, _tf_ssb_append_message_with_identity_get_key_after_work, work);
return result;
}
typedef struct _blob_get_t
{
JSContext* context;
@@ -1791,12 +1500,7 @@ void tf_ssb_register(JSContext* context, tf_ssb_t* ssb)
/* Requires an identity. */
JS_SetPropertyStr(context, object, "createIdentity", JS_NewCFunction(context, _tf_ssb_createIdentity, "createIdentity", 1));
JS_SetPropertyStr(context, object, "addIdentity", JS_NewCFunction(context, _tf_ssb_addIdentity, "addIdentity", 2));
JS_SetPropertyStr(context, object, "deleteIdentity", JS_NewCFunction(context, _tf_ssb_deleteIdentity, "deleteIdentity", 2));
JS_SetPropertyStr(context, object, "getPrivateKey", JS_NewCFunction(context, _tf_ssb_getPrivateKey, "getPrivateKey", 2));
JS_SetPropertyStr(context, object, "setUserPermission", JS_NewCFunction(context, _tf_ssb_set_user_permission, "setUserPermission", 5));
/* Write. */
JS_SetPropertyStr(context, object, "appendMessageWithIdentity", JS_NewCFunction(context, _tf_ssb_appendMessageWithIdentity, "appendMessageWithIdentity", 3));
/* Does not require an identity. */
JS_SetPropertyStr(context, object, "getServerIdentity", JS_NewCFunction(context, _tf_ssb_getServerIdentity, "getServerIdentity", 0));

View File

@@ -190,9 +190,24 @@ static void _tf_ssb_rpc_blobs_has(tf_ssb_connection_t* connection, uint8_t flags
JS_FreeValue(context, ids);
}
static bool _tf_ssb_rpc_are_messages_pending_in(tf_ssb_connection_t* connection)
{
int in_pending = 0;
int in_total = 0;
int out_pending = 0;
int out_total = 0;
if (connection)
{
tf_ssb_ebt_get_progress(tf_ssb_connection_get_ebt(connection), &in_pending, &in_total, &out_pending, &out_total);
}
return in_pending != 0;
}
static void _tf_ssb_rpc_blob_wants_added_callback(tf_ssb_t* ssb, const char* id, void* user_data)
{
tf_ssb_connection_t* connection = user_data;
if (!_tf_ssb_rpc_are_messages_pending_in(connection))
{
tf_ssb_blob_wants_t* blob_wants = tf_ssb_connection_get_blob_wants_state(connection);
JSContext* context = tf_ssb_get_context(ssb);
JSValue message = JS_NewObject(context);
@@ -200,6 +215,7 @@ static void _tf_ssb_rpc_blob_wants_added_callback(tf_ssb_t* ssb, const char* id,
tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
}
}
typedef struct _blob_wants_work_t
{
@@ -235,7 +251,8 @@ static void _tf_ssb_request_blob_wants_work(tf_ssb_connection_t* connection, voi
if (sqlite3_bind_text(statement, 1, blob_wants->last_id, -1, NULL) == SQLITE_OK && sqlite3_bind_int64(statement, 2, timestamp) == SQLITE_OK &&
sqlite3_bind_int(statement, 3, tf_countof(work->out_id)) == SQLITE_OK)
{
while (sqlite3_step(statement) == SQLITE_ROW)
int r = SQLITE_OK;
while ((r = sqlite3_step(statement)) == SQLITE_ROW)
{
tf_string_set(work->out_id[work->out_id_count], sizeof(work->out_id[work->out_id_count]), (const char*)sqlite3_column_text(statement, 0));
work->out_id_count++;
@@ -264,8 +281,11 @@ static void _tf_ssb_request_blob_wants_after_work(tf_ssb_connection_t* connectio
JS_SetPropertyStr(context, message, work->out_id[i], JS_NewInt32(context, -1));
send_failed = !tf_ssb_connection_rpc_send_json(connection, k_ssb_rpc_flag_stream, -blob_wants->request_number, NULL, message, NULL, NULL, NULL);
JS_FreeValue(context, message);
if (!send_failed)
{
blob_wants->wants_sent++;
}
}
if (work->out_id_count)
{
tf_string_set(blob_wants->last_id, sizeof(blob_wants->last_id), work->out_id[work->out_id_count - 1]);
@@ -275,11 +295,14 @@ static void _tf_ssb_request_blob_wants_after_work(tf_ssb_connection_t* connectio
}
static void _tf_ssb_rpc_request_more_blobs(tf_ssb_connection_t* connection)
{
if (tf_ssb_connection_get_blob_wants_state(connection)->wants_sent == 0 && !_tf_ssb_rpc_are_messages_pending_in(connection))
{
blob_wants_work_t* work = tf_malloc(sizeof(blob_wants_work_t));
memset(work, 0, sizeof(*work));
tf_ssb_connection_run_work(connection, _tf_ssb_request_blob_wants_work, _tf_ssb_request_blob_wants_after_work, work);
}
}
static void _tf_ssb_rpc_blobs_createWants(
tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
@@ -718,10 +741,12 @@ static void _tf_ssb_rpc_connection_blobs_createWants_callback(
}
int64_t size = 0;
JS_ToInt64(context, &size, key_value);
if (--blob_wants->wants_sent == 0)
/* We don't have the context here to disambiguate responses to incoming requests. */
if (blob_wants->wants_sent > 0)
{
_tf_ssb_rpc_request_more_blobs(connection);
--blob_wants->wants_sent;
}
_tf_ssb_rpc_request_more_blobs(connection);
if (size < 0)
{
blob_create_wants_work_t* work = tf_malloc(sizeof(blob_create_wants_work_t));
@@ -1940,8 +1965,8 @@ static void _tf_ssb_rpc_message_added_callback(tf_ssb_t* ssb, const char* author
tf_ssb_connection_t* connection = connections[i];
if (tf_ssb_connection_is_connected(connection) && !tf_ssb_is_shutting_down(tf_ssb_connection_get_ssb(connection)) && !tf_ssb_connection_is_closing(connection))
{
tf_ssb_ebt_set_messages_received(tf_ssb_connection_get_ebt(connections[i]), author, sequence);
_tf_ssb_rpc_ebt_schedule_send_clock(connections[i]);
_tf_ssb_rpc_request_more_blobs(connection);
}
}
}

View File

@@ -67,7 +67,7 @@ success = False
try:
options = webdriver.FirefoxOptions()
service = Service(log_output = 'out/geckodriver.log')
#options.add_argument('--headless')
options.add_argument('--headless')
driver = webdriver.Firefox(options = options, service = service)
wait = WebDriverWait(driver, 10)