Skip to content

Commit fc5e60d

Browse files
committed
added crop
1 parent e9a8c5c commit fc5e60d

11 files changed

Lines changed: 945 additions & 27 deletions

File tree

.github/workflows/release-android.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
- run: npx cap add android
3636
- run: npm run icons
3737
- run: npx cap sync android
38+
- run: npm run cap:patch:links
3839
- run: ./gradlew assembleDebug
3940
working-directory: android
4041
- run: mkdir -p release && cp android/app/build/outputs/apk/debug/app-debug.apk release/Nostr-Social-Android.apk

electron/main.cjs

Lines changed: 70 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ const http = require('http');
55
const path = require('path');
66

77
let server;
8+
let mainWindow;
9+
let appBaseUrl;
10+
let pendingNostrUrl = '';
811
const secureSessionFile = 'secure-private-key-session.dat';
912

1013
function createStaticServer() {
@@ -41,8 +44,8 @@ function createStaticServer() {
4144
}
4245

4346
async function createWindow() {
44-
const baseUrl = await createStaticServer();
45-
const window = new BrowserWindow({
47+
appBaseUrl = appBaseUrl || (await createStaticServer());
48+
mainWindow = new BrowserWindow({
4649
width: 1180,
4750
height: 840,
4851
minWidth: 420,
@@ -58,11 +61,53 @@ async function createWindow() {
5861
}
5962
});
6063

61-
window.webContents.setWindowOpenHandler(({ url }) => {
64+
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
6265
void shell.openExternal(url);
6366
return { action: 'deny' };
6467
});
65-
await window.loadURL(baseUrl);
68+
mainWindow.on('closed', () => {
69+
mainWindow = undefined;
70+
});
71+
await mainWindow.loadURL(appBaseUrl);
72+
if (pendingNostrUrl) {
73+
const url = pendingNostrUrl;
74+
pendingNostrUrl = '';
75+
openNostrUrl(url);
76+
}
77+
}
78+
79+
function registerNostrProtocol() {
80+
if (process.defaultApp) {
81+
if (process.argv.length >= 2) app.setAsDefaultProtocolClient('nostr', process.execPath, [path.resolve(process.argv[1])]);
82+
} else {
83+
app.setAsDefaultProtocolClient('nostr');
84+
}
85+
}
86+
87+
function findNostrUrl(argv = process.argv) {
88+
return argv.find((argument) => /^nostr:/i.test(argument)) || '';
89+
}
90+
91+
function nostrIdentifierFromUrl(url = '') {
92+
const clean = String(url).trim();
93+
if (!/^nostr:/i.test(clean)) return '';
94+
try {
95+
return decodeURIComponent(clean.replace(/^nostr:(?:\/\/)?/i, '').replace(/^\/+/, '').split(/[?#]/, 1)[0] || '').trim();
96+
} catch {
97+
return clean.replace(/^nostr:(?:\/\/)?/i, '').replace(/^\/+/, '').split(/[?#]/, 1)[0] || '';
98+
}
99+
}
100+
101+
function openNostrUrl(url) {
102+
const identifier = nostrIdentifierFromUrl(url);
103+
if (!identifier) return;
104+
pendingNostrUrl = url;
105+
if (!mainWindow || !appBaseUrl) return;
106+
pendingNostrUrl = '';
107+
if (mainWindow.isMinimized()) mainWindow.restore();
108+
mainWindow.show();
109+
mainWindow.focus();
110+
void mainWindow.loadURL(`${appBaseUrl}/${encodeURIComponent(identifier)}`);
66111
}
67112

68113
function contentType(filePath) {
@@ -122,10 +167,27 @@ function registerSecureSessionHandlers() {
122167
ipcMain.handle('secure-session:clear', () => clearSecureSession());
123168
}
124169

125-
app.whenReady().then(() => {
126-
registerSecureSessionHandlers();
127-
return createWindow();
128-
});
170+
registerNostrProtocol();
171+
openNostrUrl(findNostrUrl());
172+
173+
const gotSingleInstanceLock = app.requestSingleInstanceLock();
174+
if (!gotSingleInstanceLock) {
175+
app.quit();
176+
} else {
177+
app.on('second-instance', (_event, argv) => {
178+
openNostrUrl(findNostrUrl(argv));
179+
});
180+
181+
app.on('open-url', (event, url) => {
182+
event.preventDefault();
183+
openNostrUrl(url);
184+
});
185+
186+
app.whenReady().then(() => {
187+
registerSecureSessionHandlers();
188+
return createWindow();
189+
});
190+
}
129191

130192
app.on('window-all-closed', () => {
131193
if (process.platform !== 'darwin') app.quit();

package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"desktop:linux": "npm run icons && npm run build && electron-builder --linux AppImage --x64 --publish never",
2222
"cap:add:ios": "cap add ios",
2323
"cap:add:android": "cap add android",
24-
"cap:sync": "npm run icons && npm run build && cap sync && npm run icons"
24+
"cap:patch:links": "node scripts/patch-native-links.cjs",
25+
"cap:sync": "npm run icons && npm run build && cap sync && npm run cap:patch:links && npm run icons"
2526
},
2627
"dependencies": {
2728
"@capacitor/android": "^7.4.4",
@@ -56,6 +57,14 @@
5657
"output": "release"
5758
},
5859
"icon": "icon",
60+
"protocols": [
61+
{
62+
"name": "Nostr URI",
63+
"schemes": [
64+
"nostr"
65+
]
66+
}
67+
],
5968
"mac": {
6069
"target": "dmg",
6170
"artifactName": "Nostr-Social-macOS-${arch}.${ext}"

scripts/patch-native-links.cjs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const fs = require('node:fs');
2+
const path = require('node:path');
3+
4+
patchAndroidManifest();
5+
patchIosInfoPlist();
6+
7+
function patchAndroidManifest() {
8+
const manifestPath = path.join('android', 'app', 'src', 'main', 'AndroidManifest.xml');
9+
if (!fs.existsSync(manifestPath)) return;
10+
11+
const source = fs.readFileSync(manifestPath, 'utf8');
12+
if (source.includes('android:scheme="nostr"')) return;
13+
14+
const intentFilter = `
15+
<intent-filter>
16+
<action android:name="android.intent.action.VIEW" />
17+
18+
<category android:name="android.intent.category.DEFAULT" />
19+
<category android:name="android.intent.category.BROWSABLE" />
20+
21+
<data android:scheme="nostr" />
22+
</intent-filter>
23+
`;
24+
25+
const activityPattern = /(<activity\b[^>]*android:name="[^"]*MainActivity"[\s\S]*?)(\n\s*<\/activity>)/;
26+
const next = source.replace(activityPattern, `$1${intentFilter}$2`);
27+
if (next === source) throw new Error(`Could not find MainActivity in ${manifestPath}`);
28+
29+
fs.writeFileSync(manifestPath, next);
30+
console.log(`Patched ${manifestPath} for nostr: links`);
31+
}
32+
33+
function patchIosInfoPlist() {
34+
const plistPath = path.join('ios', 'App', 'App', 'Info.plist');
35+
if (!fs.existsSync(plistPath)) return;
36+
37+
const source = fs.readFileSync(plistPath, 'utf8');
38+
if (source.includes('<string>nostr</string>')) return;
39+
40+
const urlTypes = `
41+
<key>CFBundleURLTypes</key>
42+
<array>
43+
<dict>
44+
<key>CFBundleURLName</key>
45+
<string>com.nostr.social</string>
46+
<key>CFBundleURLSchemes</key>
47+
<array>
48+
<string>nostr</string>
49+
</array>
50+
</dict>
51+
</array>
52+
`;
53+
54+
const next = source.replace(/\n<\/dict>\s*\n<\/plist>\s*$/, `${urlTypes}</dict>\n</plist>\n`);
55+
if (next === source) throw new Error(`Could not patch ${plistPath}`);
56+
57+
fs.writeFileSync(plistPath, next);
58+
console.log(`Patched ${plistPath} for nostr: links`);
59+
}

0 commit comments

Comments
 (0)