diff --git a/Nostore.xcodeproj/project.pbxproj b/Nostore.xcodeproj/project.pbxproj index b047ea1..8faf6f9 100644 --- a/Nostore.xcodeproj/project.pbxproj +++ b/Nostore.xcodeproj/project.pbxproj @@ -89,6 +89,12 @@ 944A6DED299682EF0032C2E3 /* experimental.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE8299682EF0032C2E3 /* experimental.js */; }; 944A6DEE299682EF0032C2E3 /* experimental.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE9299682EF0032C2E3 /* experimental.build.js */; }; 944A6DEF299682EF0032C2E3 /* experimental.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DE9299682EF0032C2E3 /* experimental.build.js */; }; + 944A6DF3299975A70032C2E3 /* event_log.html in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DF0299975A70032C2E3 /* event_log.html */; }; + 944A6DF4299975A70032C2E3 /* event_log.html in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DF0299975A70032C2E3 /* event_log.html */; }; + 944A6DF5299975A70032C2E3 /* event_log.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DF1299975A70032C2E3 /* event_log.build.js */; }; + 944A6DF6299975A70032C2E3 /* event_log.build.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DF1299975A70032C2E3 /* event_log.build.js */; }; + 944A6DF7299975A70032C2E3 /* event_log.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DF2299975A70032C2E3 /* event_log.js */; }; + 944A6DF8299975A70032C2E3 /* event_log.js in Resources */ = {isa = PBXBuildFile; fileRef = 944A6DF2299975A70032C2E3 /* event_log.js */; }; 948C69D9297F887600FB3574 /* options.html in Resources */ = {isa = PBXBuildFile; fileRef = 948C69D8297F887600FB3574 /* options.html */; }; 948C69DA297F887600FB3574 /* options.html in Resources */ = {isa = PBXBuildFile; fileRef = 948C69D8297F887600FB3574 /* options.html */; }; 948C69DD297F88A200FB3574 /* options.css in Resources */ = {isa = PBXBuildFile; fileRef = 948C69DB297F88A200FB3574 /* options.css */; }; @@ -198,6 +204,9 @@ 944A6DE7299682EF0032C2E3 /* experimental.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = experimental.html; sourceTree = ""; }; 944A6DE8299682EF0032C2E3 /* experimental.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = experimental.js; sourceTree = ""; }; 944A6DE9299682EF0032C2E3 /* experimental.build.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = experimental.build.js; sourceTree = ""; }; + 944A6DF0299975A70032C2E3 /* event_log.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = event_log.html; sourceTree = ""; }; + 944A6DF1299975A70032C2E3 /* event_log.build.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = event_log.build.js; sourceTree = ""; }; + 944A6DF2299975A70032C2E3 /* event_log.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = event_log.js; sourceTree = ""; }; 948C69D8297F887600FB3574 /* options.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = options.html; sourceTree = ""; }; 948C69DB297F88A200FB3574 /* options.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = options.css; sourceTree = ""; }; 948C69DC297F88A200FB3574 /* options.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = options.js; sourceTree = ""; }; @@ -285,6 +294,9 @@ 941B03A2296FA90400CA291E /* Resources */ = { isa = PBXGroup; children = ( + 944A6DF1299975A70032C2E3 /* event_log.build.js */, + 944A6DF0299975A70032C2E3 /* event_log.html */, + 944A6DF2299975A70032C2E3 /* event_log.js */, 944A6DE9299682EF0032C2E3 /* experimental.build.js */, 944A6DE7299682EF0032C2E3 /* experimental.html */, 944A6DE8299682EF0032C2E3 /* experimental.js */, @@ -548,17 +560,20 @@ 948C69E2297F891F00FB3574 /* options.build.js in Resources */, 941B03F8296FA90400CA291E /* popup.css in Resources */, 941B04292977A28700CA291E /* Icon-128.png in Resources */, + 944A6DF3299975A70032C2E3 /* event_log.html in Resources */, 941B04182971138F00CA291E /* nostr.build.js in Resources */, 941B042C2978CD8E00CA291E /* iOS-Icon-1024.png in Resources */, 941B03F6296FA90400CA291E /* popup.html in Resources */, 941B040D296FAD6900CA291E /* nostr.js in Resources */, 944A6DE02991DFC60032C2E3 /* delegation_wizard.build.js in Resources */, + 944A6DF5299975A70032C2E3 /* event_log.build.js in Resources */, 941B03EE296FA90400CA291E /* images in Resources */, 941B03F0296FA90400CA291E /* manifest.json in Resources */, 944A6DD62988BD230032C2E3 /* permission.js in Resources */, 941B04312978CDF900CA291E /* Icon-32.png in Resources */, 941B041A2971139000CA291E /* content.build.js in Resources */, 941B041C2971139000CA291E /* popup.build.js in Resources */, + 944A6DF7299975A70032C2E3 /* event_log.js in Resources */, 944A6DEC299682EF0032C2E3 /* experimental.js in Resources */, 941B03EC296FA90400CA291E /* _locales in Resources */, 941B04222977A25700CA291E /* Icon-512.png in Resources */, @@ -591,17 +606,20 @@ 948C69E3297F891F00FB3574 /* options.build.js in Resources */, 941B03F9296FA90400CA291E /* popup.css in Resources */, 941B042A2977A28700CA291E /* Icon-128.png in Resources */, + 944A6DF4299975A70032C2E3 /* event_log.html in Resources */, 941B04192971138F00CA291E /* nostr.build.js in Resources */, 941B042D2978CD8E00CA291E /* iOS-Icon-1024.png in Resources */, 941B03F7296FA90400CA291E /* popup.html in Resources */, 941B040E296FAD6900CA291E /* nostr.js in Resources */, 944A6DE12991DFC60032C2E3 /* delegation_wizard.build.js in Resources */, + 944A6DF6299975A70032C2E3 /* event_log.build.js in Resources */, 941B03EF296FA90400CA291E /* images in Resources */, 941B03F1296FA90400CA291E /* manifest.json in Resources */, 944A6DD72988BD230032C2E3 /* permission.js in Resources */, 941B04322978CDF900CA291E /* Icon-32.png in Resources */, 941B041B2971139000CA291E /* content.build.js in Resources */, 941B041D2971139000CA291E /* popup.build.js in Resources */, + 944A6DF8299975A70032C2E3 /* event_log.js in Resources */, 944A6DED299682EF0032C2E3 /* experimental.js in Resources */, 941B03ED296FA90400CA291E /* _locales in Resources */, 941B04232977A25700CA291E /* Icon-512.png in Resources */, diff --git a/Nostore.xcodeproj/xcuserdata/ryan.xcuserdatad/xcschemes/xcschememanagement.plist b/Nostore.xcodeproj/xcuserdata/ryan.xcuserdatad/xcschemes/xcschememanagement.plist index 2fa6e1a..1e154e6 100644 --- a/Nostore.xcodeproj/xcuserdata/ryan.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Nostore.xcodeproj/xcuserdata/ryan.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ Nostore (iOS).xcscheme_^#shared#^_ orderHint - 1 + 0 Nostore (macOS).xcscheme_^#shared#^_ orderHint - 0 + 1 nostore (iOS).xcscheme_^#shared#^_ diff --git a/Shared (Extension)/Resources/background.js b/Shared (Extension)/Resources/background.js index e9fc234..4fde090 100644 --- a/Shared (Extension)/Resources/background.js +++ b/Shared (Extension)/Resources/background.js @@ -16,6 +16,7 @@ import { setPermission, feature, } from './utils'; +import { saveEvent } from './db'; const storage = browser.storage.local; const log = msg => console.log('Background: ', msg); @@ -139,7 +140,7 @@ function complete({ payload, origKind, event, remember, host }) { }); break; case 'signEvent': - signEvent_(event).then(e => sendResponse(e)); + signEvent_(event, host).then(e => sendResponse(e)); break; case 'nip04.encrypt': nip04Encrypt(event).then(e => sendResponse(e)); @@ -213,14 +214,17 @@ async function currentProfile() { return profiles[index]; } -async function signEvent_(event) { +async function signEvent_(event, host) { event = JSON.parse(JSON.stringify(event)); let privKey = await getPrivKey(); let pubKey = getPublicKey(privKey); event.pubkey = pubKey; event.id = getEventHash(event); event.sig = signEvent(event, privKey); - console.log(JSON.stringify(event)); + saveEvent({ + event, + metadata: { host, signed_at: Math.round(Date.now() / 1000) }, + }); return event; } diff --git a/Shared (Extension)/Resources/db.js b/Shared (Extension)/Resources/db.js new file mode 100644 index 0000000..c02d8cc --- /dev/null +++ b/Shared (Extension)/Resources/db.js @@ -0,0 +1,69 @@ +import { openDB } from 'idb'; + +async function openEventsDb() { + return await openDB('events', 1, { + upgrade(db) { + const events = db.createObjectStore('events', { + keyPath: 'event.id', + }); + events.createIndex('pubkey', 'event.pubkey'); + events.createIndex('created_at', 'event.created_at'); + events.createIndex('kind', 'event.kind'); + events.createIndex('host', 'metadata.host'); + }, + }); +} + +export async function saveEvent(event) { + let db = await openEventsDb(); + return db.put('events', event); +} + +export async function sortByIndex(index, query, asc, max) { + let db = await openEventsDb(); + let events = []; + let cursor = await db + .transaction('events') + .store.index(index) + .openCursor(query, asc ? 'next' : 'prev'); + while (cursor) { + events.push(cursor.value); + if (events.length >= max) { + break; + } + cursor = await cursor.continue(); + } + return events; +} + +export async function getHosts() { + let db = await openEventsDb(); + let hosts = new Set(); + let cursor = await db.transaction('events').store.openCursor(); + while (cursor) { + hosts.add(cursor.value.metadata.host); + cursor = await cursor.continue(); + } + return [...hosts]; +} + +export async function downloadAllContents() { + let db = await openEventsDb(); + let events = []; + let cursor = await db.transaction('events').store.openCursor(); + while (cursor) { + events.push(cursor.value.event); + cursor = await cursor.continue(); + } + events = events.map(e => JSON.stringify(e)); + events = events.join('\n'); + console.log(events); + + const file = new File([events], 'events.jsonl', { + type: 'application/octet-stream', + }); + + const blob = new Blob([events], { type: 'plain/text' }); + + return blob; +} diff --git a/Shared (Extension)/Resources/event_log.html b/Shared (Extension)/Resources/event_log.html new file mode 100644 index 0000000..d53c538 --- /dev/null +++ b/Shared (Extension)/Resources/event_log.html @@ -0,0 +1,135 @@ + + + + + + + + + + Event Log + + + + + +

+ ← Back +

+ +

Event Log

+ +
+
Filters
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+ + + + + + \ No newline at end of file diff --git a/Shared (Extension)/Resources/event_log.js b/Shared (Extension)/Resources/event_log.js new file mode 100644 index 0000000..4d6773a --- /dev/null +++ b/Shared (Extension)/Resources/event_log.js @@ -0,0 +1,135 @@ +import Alpine from 'alpinejs'; +import { deleteDB } from 'idb'; +import jsonFormatHighlight from 'json-format-highlight'; +import { getPublicKey } from 'nostr-tools'; +import { downloadAllContents, getHosts, sortByIndex } from './db'; +import { getProfiles, KINDS } from './utils'; + +const TOMORROW = new Date(); +TOMORROW.setDate(TOMORROW.getDate() + 1); + +Alpine.data('eventLog', () => ({ + kinds: KINDS, + events: [], + view: 'created_at', + max: 100, + sort: 'asc', + allHosts: [], + host: '', + allProfiles: [], + profile: '', + pubkey: '', + selected: null, + + // date view + fromCreatedAt: '2008-10-31', + toCreatedAt: TOMORROW.toISOString().split('T')[0], + + // kind view + quickKind: '', + fromKind: 0, + toKind: 50000, + + async init() { + await this.reload(); + }, + + async reload() { + let events = await sortByIndex( + this.view, + this.keyRange, + this.sort === 'asc', + this.max + ); + this.events = events; + getHosts().then(hosts => (this.allHosts = hosts)); + const profiles = await getProfiles(); + console.log(profiles); + this.allProfiles = profiles.map(profile => ({ + name: profile.name, + pubkey: getPublicKey(profile.privKey), + })); + }, + + async saveAll() { + const file = await downloadAllContents(); + browser.tabs.create({ + url: URL.createObjectURL(file), + active: true, + }); + }, + + async deleteAll() { + if (confirm('Are you sure you want to delete ALL events?')) { + await deleteDB('events'); + await this.reload(); + } + }, + + quickKindSelect() { + if (this.quickKind === '') return; + const i = parseInt(this.quickKind); + this.fromKind = i; + this.toKind = i; + this.reload(); + }, + + pkFromProfile() { + this.pubkey = this.allProfiles.find( + ({ name }) => name === this.profile + ).pubkey; + this.reload(); + }, + + highlight(event) { + return jsonFormatHighlight(event); + }, + + formatDate(epochSeconds) { + return new Date(epochSeconds * 1000).toUTCString(); + }, + + formatKind(kind) { + const k = KINDS.find(([kNum, _]) => kNum === kind); + return k ? `${k[1]} (${kind})` : `Unknown (${kind})`; + }, + + // Properties + + get fromTime() { + let dt = new Date(this.fromCreatedAt); + return Math.floor(dt.getTime() / 1000); + }, + + get toTime() { + let dt = new Date(this.toCreatedAt); + return Math.floor(dt.getTime() / 1000); + }, + + get profileNames() { + return this.allProfiles.map(p => p.name); + }, + + get keyRange() { + switch (this.view) { + case 'created_at': + return IDBKeyRange.bound(this.fromTime, this.toTime); + + case 'kind': + return IDBKeyRange.bound(this.fromKind, this.toKind); + + case 'host': + if (this.host.length === 0) return null; + return IDBKeyRange.only(this.host); + + case 'pubkey': + if (this.pubkey.length === 0) return null; + return IDBKeyRange.only(this.pubkey); + + default: + return null; + } + }, +})); + +Alpine.start(); diff --git a/Shared (Extension)/Resources/options.html b/Shared (Extension)/Resources/options.html index c98d3c4..7432181 100644 --- a/Shared (Extension)/Resources/options.html +++ b/Shared (Extension)/Resources/options.html @@ -162,6 +162,8 @@ diff --git a/build.js b/build.js index 9289a3a..747d4dc 100755 --- a/build.js +++ b/build.js @@ -23,6 +23,7 @@ require('esbuild') './Shared (Extension)/Resources/experimental.js', 'delegation_wizard.build': './Shared (Extension)/Resources/delegation_wizard.js', + 'event_log.build': './Shared (Extension)/Resources/event_log.js', }, outdir: './Shared (Extension)/Resources', sourcemap: 'inline', diff --git a/package-lock.json b/package-lock.json index 8e0aa53..b299447 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "alpinejs": "^3.10.5", "async-mutex": "^0.4.0", + "idb": "^7.1.1", "json-format-highlight": "^1.0.4", "nostr-tools": "^1.3.0" }, @@ -835,6 +836,11 @@ "node": ">= 0.4.0" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", diff --git a/package.json b/package.json index d861795..1cc659f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "dependencies": { "alpinejs": "^3.10.5", "async-mutex": "^0.4.0", + "idb": "^7.1.1", "json-format-highlight": "^1.0.4", "nostr-tools": "^1.3.0" },