Migrate to applesauce v6

Remove applesauce-factory (deleted in v6) and bump applesauce-core,
applesauce-relay, and applesauce-signers to ^6.2.x. Rewrite all
EventFactory usage from the removed constructor API
(`new EventFactory({ signer }).sign(template)`) to the new chainable
builder (`EventFactory.fromKind(k).content(...).sign(signer)`).

Remove `relay.eoseTimeout` overrides in nostr-auth — the eoseTimeout
property was removed in v6; NostrConnectSigner now uses persistent
subscriptions that don't rely on EOSE-based timeouts.

Update photo-gallery test mock to use 64-char hex pubkeys/ids/sigs
because v6's sign() validates event structure via validateEvent().

Also includes safe semver bumps to all other dependencies.
This commit is contained in:
2026-08-06 16:13:24 -05:00
parent 1822c4893f
commit a6576fcda8
7 changed files with 733 additions and 1578 deletions
+6 -10
View File
@@ -6,7 +6,7 @@ import { fn } from '@ember/helper';
import { service } from '@ember/service'; import { service } from '@ember/service';
import { modifier } from 'ember-modifier'; import { modifier } from 'ember-modifier';
import { task } from 'ember-concurrency'; import { task } from 'ember-concurrency';
import { EventFactory } from 'applesauce-factory'; import { EventFactory } from 'applesauce-core';
import config from 'marco/config/environment'; import config from 'marco/config/environment';
import DropdownMenu from './dropdown-menu'; import DropdownMenu from './dropdown-menu';
import PhotoCarousel from './photo-carousel'; import PhotoCarousel from './photo-carousel';
@@ -201,21 +201,17 @@ export default class PhotoGallery extends Component {
const eventId = this.currentPhoto.eventId; const eventId = this.currentPhoto.eventId;
// Publish Nostr kind: 5 deletion event first so we don't end up with dead blossom links on a failure // Publish Nostr kind: 5 deletion event first so we don't end up with dead blossom links on a failure
const factory = new EventFactory({ signer: this.nostrAuth.signer });
const tags = [['e', eventId]]; const tags = [['e', eventId]];
if (this.currentPhoto.placeIdentifier) { if (this.currentPhoto.placeIdentifier) {
tags.push(['i', this.currentPhoto.placeIdentifier]); tags.push(['i', this.currentPhoto.placeIdentifier]);
} }
const template = { const event = await EventFactory.fromKind(5)
kind: 5, .content('Deleted photo')
created_at: Math.floor(Date.now() / 1000), .modifyPublicTags(() => tags)
content: 'Deleted photo', .as(this.nostrAuth.signer)
tags, .sign();
};
const event = await factory.sign(template);
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event); await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
// Remove from local store by adding the kind 5 to it // Remove from local store by adding the kind 5 to it
+5 -13
View File
@@ -155,8 +155,6 @@ export default class PlacePhotoUpload extends Component {
this.isPublishing = true; this.isPublishing = true;
try { try {
const factory = new EventFactory({ signer: this.nostrAuth.signer });
const tags = [['i', `osm:${osmType}:${osmId}`]]; const tags = [['i', `osm:${osmType}:${osmId}`]];
for (const tag of this.selectedTags) { for (const tag of this.selectedTags) {
@@ -198,17 +196,11 @@ export default class PlacePhotoUpload extends Component {
tags.push(imeta); tags.push(imeta);
// NIP-XX draft Place Photo event // NIP-XX draft Place Photo event
const template = { const event = await EventFactory.fromKind(360)
kind: 360, .content('')
content: '', .modifyPublicTags(() => tags)
tags, .as(this.nostrAuth.signer)
}; .sign();
if (!template.created_at) {
template.created_at = Math.floor(Date.now() / 1000);
}
const event = await factory.sign(template);
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event); await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
this.nostrData.store.add(event); this.nostrData.store.add(event);
+7 -10
View File
@@ -35,23 +35,20 @@ export default class BlossomService extends Service {
} }
async _getAuthHeader(action, hash, serverUrl) { async _getAuthHeader(action, hash, serverUrl) {
const factory = new EventFactory({ signer: this.nostrAuth.signer });
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const serverHostname = new URL(serverUrl).hostname; const serverHostname = new URL(serverUrl).hostname;
const authTemplate = { const authEvent = await EventFactory.fromKind(24242)
kind: 24242, .content(action === 'upload' ? 'Upload photo for place' : 'Delete photo')
created_at: now, .modifyPublicTags(() => [
content: action === 'upload' ? 'Upload photo for place' : 'Delete photo',
tags: [
['t', action], ['t', action],
['x', hash], ['x', hash],
['expiration', String(now + 3600)], ['expiration', String(now + 3600)],
['server', serverHostname], ['server', serverHostname],
], ])
}; .created(now)
.as(this.nostrAuth.signer)
const authEvent = await factory.sign(authTemplate); .sign();
const base64 = btoa(JSON.stringify(authEvent)); const base64 = btoa(JSON.stringify(authEvent));
const base64url = base64 const base64url = base64
.replace(/\+/g, '-') .replace(/\+/g, '-')
-6
View File
@@ -150,9 +150,6 @@ export default class NostrAuthService extends Service {
const relay = DEFAULT_CONNECT_RELAY; const relay = DEFAULT_CONNECT_RELAY;
localStorage.setItem(STORAGE_KEY_CONNECT_RELAY, relay); localStorage.setItem(STORAGE_KEY_CONNECT_RELAY, relay);
// Override aggressive 10s EOSE timeout to allow time for QR scanning
this.nostrRelay.pool.relay(relay).eoseTimeout = 180000; // 3 minutes
this._signerInstance = new NostrConnectSigner({ this._signerInstance = new NostrConnectSigner({
pool: this.nostrRelay.pool, pool: this.nostrRelay.pool,
relays: [relay], relays: [relay],
@@ -235,9 +232,6 @@ export default class NostrAuthService extends Service {
const localSigner = this._getLocalSigner(); const localSigner = this._getLocalSigner();
// Override aggressive 10s EOSE timeout to allow time for QR scanning
this.nostrRelay.pool.relay(relay).eoseTimeout = 180000; // 3 minutes
this._signerInstance = new NostrConnectSigner({ this._signerInstance = new NostrConnectSigner({
pool: this.nostrRelay.pool, pool: this.nostrRelay.pool,
relays: [relay], relays: [relay],
+32 -33
View File
@@ -43,31 +43,31 @@
"@babel/eslint-parser": "^7.28.5", "@babel/eslint-parser": "^7.28.5",
"@babel/plugin-transform-runtime": "^7.28.5", "@babel/plugin-transform-runtime": "^7.28.5",
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@ember/test-helpers": "^5.4.1", "@ember/test-helpers": "^5.4.3",
"@ember/test-waiters": "^4.1.1", "@ember/test-waiters": "^4.1.2",
"@embroider/core": "^4.4.2", "@embroider/core": "^4.6.3",
"@embroider/legacy-inspector-support": "^0.1.3", "@embroider/legacy-inspector-support": "^0.1.3",
"@embroider/macros": "^1.19.6", "@embroider/macros": "^1.20.6",
"@embroider/router": "^3.0.6", "@embroider/router": "^3.0.6",
"@embroider/vite": "^1.5.0", "@embroider/vite": "^1.7.9",
"@eslint/js": "^9.39.2", "@eslint/js": "^9.39.2",
"@glimmer/component": "^2.0.0", "@glimmer/component": "^2.1.1",
"@remotestorage/module-places": "~1.3.0", "@remotestorage/module-places": "~1.3.0",
"@rollup/plugin-babel": "^6.1.0", "@rollup/plugin-babel": "^6.1.0",
"@warp-drive/core": "~5.8.0", "@warp-drive/core": "~5.8.2",
"@warp-drive/ember": "~5.8.0", "@warp-drive/ember": "~5.8.2",
"@warp-drive/json-api": "~5.8.0", "@warp-drive/json-api": "~5.8.2",
"@warp-drive/legacy": "~5.8.0", "@warp-drive/legacy": "~5.8.2",
"@warp-drive/utilities": "~5.8.0", "@warp-drive/utilities": "~5.8.2",
"babel-plugin-ember-template-compilation": "^3.0.1", "babel-plugin-ember-template-compilation": "^3.0.1",
"concurrently": "^9.2.1", "concurrently": "^9.2.1",
"decorator-transforms": "^2.3.1", "decorator-transforms": "^2.4.0",
"ember-cli": "^6.10.0", "ember-cli": "^6.10.0",
"ember-cli-deprecation-workflow": "^4.0.0", "ember-cli-deprecation-workflow": "^4.0.1",
"ember-modifier": "^4.2.2", "ember-modifier": "^4.3.0",
"ember-page-title": "^9.0.3", "ember-page-title": "^9.0.3",
"ember-qunit": "^9.0.4", "ember-qunit": "^9.1.0",
"ember-resolver": "^13.1.1", "ember-resolver": "^13.2.0",
"ember-source": "~6.11.0-alpha.6", "ember-source": "~6.11.0-alpha.6",
"ember-template-lint": "^7.9.3", "ember-template-lint": "^7.9.3",
"ember-truth-helpers": "^5.0.0", "ember-truth-helpers": "^5.0.0",
@@ -76,23 +76,23 @@
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-ember": "^12.7.5", "eslint-plugin-ember": "^12.7.5",
"eslint-plugin-n": "^17.23.1", "eslint-plugin-n": "^17.23.1",
"eslint-plugin-qunit": "^8.2.5", "eslint-plugin-qunit": "^8.2.6",
"eslint-plugin-warp-drive": "^5.8.0", "eslint-plugin-warp-drive": "^5.8.2",
"feather-icons": "^4.29.2", "feather-icons": "^4.29.2",
"globals": "^16.5.0", "globals": "^16.5.0",
"latlon-geohash": "^2.0.0", "latlon-geohash": "^2.0.0",
"ol": "^10.7.0", "ol": "^10.10.0",
"ol-mapbox-style": "^13.2.0", "ol-mapbox-style": "^13.4.2",
"prettier": "^3.7.4", "prettier": "^3.9.6",
"prettier-plugin-ember-template-tag": "^2.1.2", "prettier-plugin-ember-template-tag": "^2.1.7",
"qunit": "^2.25.0", "qunit": "^2.26.0",
"qunit-dom": "^3.5.0", "qunit-dom": "^3.6.0",
"remotestorage-widget": "^1.8.1", "remotestorage-widget": "^1.8.1",
"remotestoragejs": "2.0.0-beta.9", "remotestoragejs": "2.0.0-beta.9",
"sinon": "^21.0.1", "sinon": "^21.0.1",
"stylelint": "^16.26.1", "stylelint": "^16.26.1",
"stylelint-config-standard": "^38.0.0", "stylelint-config-standard": "^38.0.0",
"testem": "^3.17.0", "testem": "^3.20.1",
"vite": "^7.3.0" "vite": "^7.3.0"
}, },
"engines": { "engines": {
@@ -102,16 +102,15 @@
"edition": "octane" "edition": "octane"
}, },
"dependencies": { "dependencies": {
"@noble/hashes": "^2.2.0", "@noble/hashes": "^2.3.0",
"@waysidemapping/pinhead": "^15.20.0", "@waysidemapping/pinhead": "^15.25.0",
"applesauce-core": "^5.2.0", "applesauce-core": "^6.2.0",
"applesauce-factory": "^4.0.0", "applesauce-relay": "^6.2.1",
"applesauce-relay": "^5.2.0", "applesauce-signers": "^6.2.2",
"applesauce-signers": "^5.2.0",
"blurhash": "^2.0.5", "blurhash": "^2.0.5",
"ember-concurrency": "^5.2.0", "ember-concurrency": "^5.2.0",
"ember-lifeline": "^7.0.0", "ember-lifeline": "^7.1.0",
"nostr-idb": "^5.0.0", "nostr-idb": "^5.1.0",
"oauth2-pkce": "^2.1.3", "oauth2-pkce": "^2.1.3",
"qrcode": "^1.5.4", "qrcode": "^1.5.4",
"rxjs": "^7.8.2" "rxjs": "^7.8.2"
+669 -1495
View File
File diff suppressed because it is too large Load Diff
@@ -6,6 +6,9 @@ import PhotoGallery from 'marco/components/photo-gallery';
import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr'; import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr';
import sinon from 'sinon'; import sinon from 'sinon';
const USER_A = 'a'.repeat(64);
const USER_B = 'b'.repeat(64);
class MockBlossomService extends Service { class MockBlossomService extends Service {
async delete() { async delete() {
return true; return true;
@@ -34,7 +37,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
this.photos = [ this.photos = [
{ {
eventId: 'event1', eventId: 'event1',
pubkey: 'userA', pubkey: USER_A,
placeIdentifier: 'osm:node:12345', placeIdentifier: 'osm:node:12345',
url: 'https://example.com/a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1.jpg', url: 'https://example.com/a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1.jpg',
thumbUrl: thumbUrl:
@@ -42,7 +45,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}, },
{ {
eventId: 'event2', eventId: 'event2',
pubkey: 'userB', pubkey: USER_B,
placeIdentifier: 'osm:node:12345', placeIdentifier: 'osm:node:12345',
url: 'photo2.jpg', url: 'photo2.jpg',
}, },
@@ -55,7 +58,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it does not show delete button if user is not creator', async function (assert) { test('it does not show delete button if user is not creator', async function (assert) {
this.nostrAuth.pubkey = 'userB'; // Different from photo1's pubkey this.nostrAuth.pubkey = USER_B; // Different from photo1's pubkey
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
await render( await render(
@@ -80,7 +83,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it shows delete button if user is creator and setting is enabled', async function (assert) { test('it shows delete button if user is creator and setting is enabled', async function (assert) {
this.nostrAuth.pubkey = 'userA'; // Matches photo1's pubkey this.nostrAuth.pubkey = USER_A; // Matches photo1's pubkey
this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
@@ -106,7 +109,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it handles cancellation of deletion', async function (assert) { test('it handles cancellation of deletion', async function (assert) {
this.nostrAuth.pubkey = 'userA'; this.nostrAuth.pubkey = USER_A;
this.settings.update('experimentalEnablePhotoDeletion', true); this.settings.update('experimentalEnablePhotoDeletion', true);
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
@@ -133,7 +136,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it performs full deletion flow when confirmed', async function (assert) { test('it performs full deletion flow when confirmed', async function (assert) {
this.nostrAuth.pubkey = 'userA'; this.nostrAuth.pubkey = USER_A;
this.settings.update('experimentalEnablePhotoDeletion', true); this.settings.update('experimentalEnablePhotoDeletion', true);
// Override the mock's getter just for this test // Override the mock's getter just for this test
Object.defineProperty(this.nostrAuth, 'signer', { Object.defineProperty(this.nostrAuth, 'signer', {
@@ -141,11 +144,11 @@ module('Integration | Component | photo-gallery', function (hooks) {
get: () => ({ get: () => ({
signEvent: async (e) => ({ signEvent: async (e) => ({
...e, ...e,
id: 'signed-id', id: 'a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
sig: 'sig', sig: 'b3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
pubkey: 'userA', pubkey: USER_A,
}), }),
getPublicKey: async () => 'userA', getPublicKey: async () => USER_A,
}), }),
}); });
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
@@ -227,7 +230,7 @@ module('Integration | Component | photo-gallery', function (hooks) {
}); });
test('it copies event id to clipboard', async function (assert) { test('it copies event id to clipboard', async function (assert) {
this.nostrAuth.pubkey = 'userA'; this.nostrAuth.pubkey = USER_A;
this.selectedPhoto = this.photos[0]; this.selectedPhoto = this.photos[0];
const clipboardStub = sinon const clipboardStub = sinon