Browse Source

bug-fix electron

imwald
Silberengel 2 weeks ago
parent
commit
6064d96ae7
  1. 88
      electron/main.cjs
  2. 22
      electron/preload.cjs
  3. 4
      package-lock.json
  4. 5
      package.json
  5. 5
      src/lib/electron-aware-fetch.ts

88
electron/main.cjs

@ -1,9 +1,8 @@ @@ -1,9 +1,8 @@
'use strict'
const { app, BrowserWindow, ipcMain, shell, Menu, session } = require('electron')
const { app, BrowserWindow, ipcMain, shell, Menu, session, net } = require('electron')
const fs = require('fs')
const http = require('http')
const https = require('https')
const path = require('path')
/** True when running from source (`electron .`); false when packaged. */
@ -209,6 +208,16 @@ function relaxCorsForRendererSubresources() { @@ -209,6 +208,16 @@ function relaxCorsForRendererSubresources() {
callback({ cancel: false, responseHeaders: details.responseHeaders })
return
}
// Main-process `net.fetch` (translate / LanguageTool IPC) has no `webContentsId` — do not rewrite
// response headers; the consumer is Node/Chromium fetch in main, not a renderer CORS check.
const url = String(details.url || '')
if (
!details.webContentsId &&
(/\/api\/translate(?:\/|\?|$)/u.test(url) || /\/api\/languagetool(?:\/|\?|$)/u.test(url))
) {
callback({ cancel: false, responseHeaders: details.responseHeaders })
return
}
const raw = details.responseHeaders
if (!raw) {
callback({ cancel: false })
@ -267,51 +276,46 @@ const STRIP_OUTBOUND_REQUEST_HEADERS = new Set([ @@ -267,51 +276,46 @@ const STRIP_OUTBOUND_REQUEST_HEADERS = new Set([
'keep-alive'
])
function requestImwaldBackend(urlString, { method, headers, body }) {
return new Promise((resolve, reject) => {
const u = new URL(urlString)
const useTls = u.protocol === 'https:'
const lib = useTls ? https : http
const port = u.port || (useTls ? 443 : 80)
const safeHeaders = {}
if (headers && typeof headers === 'object') {
for (const [k, v] of Object.entries(headers)) {
if (STRIP_OUTBOUND_REQUEST_HEADERS.has(k.toLowerCase())) continue
if (typeof v === 'string') safeHeaders[k] = v
}
}
const opts = {
hostname: u.hostname,
port,
path: `${u.pathname}${u.search}`,
method: (method || 'GET').toUpperCase(),
headers: safeHeaders
/**
* Use Chromium’s network stack (same TLS / HTTP2 behavior as the browser). Node’s `https` can fail
* where `net.fetch` succeeds (e.g. cert chains, SNI, corporate proxies already trusted by Chromium).
*/
async function requestImwaldBackend(urlString, { method, headers, body }) {
const safeHeaders = {}
if (headers && typeof headers === 'object') {
for (const [k, v] of Object.entries(headers)) {
if (STRIP_OUTBOUND_REQUEST_HEADERS.has(k.toLowerCase())) continue
if (typeof v === 'string') safeHeaders[k] = v
}
const req = lib.request(opts, (res) => {
const chunks = []
res.on('data', (chunk) => chunks.push(chunk))
res.on('end', () => {
const buf = Buffer.concat(chunks)
const outHeaders = {}
for (const [k, v] of Object.entries(res.headers)) {
if (v === undefined) continue
outHeaders[k] = Array.isArray(v) ? v.join(', ') : String(v)
}
resolve({
status: res.statusCode || 0,
statusText: res.statusMessage || '',
headers: outHeaders,
body: buf.toString('utf8')
})
})
})
req.on('error', reject)
if (body) req.write(body, 'utf8')
req.end()
}
const m = (method || 'GET').toUpperCase()
const init = {
method: m,
headers: safeHeaders
}
if (body != null && m !== 'GET' && m !== 'HEAD') {
init.body = body
}
const res = await net.fetch(urlString, init)
const text = await res.text()
const outHeaders = {}
res.headers.forEach((value, key) => {
outHeaders[key] = value
})
return {
status: res.status,
statusText: res.statusText || '',
headers: outHeaders,
body: text
}
}
function registerImwaldBackendRequestIpc() {
try {
ipcMain.removeHandler('imwald:backend-request')
} catch {
/* ignore if channel was never registered */
}
ipcMain.handle('imwald:backend-request', async (_event, payload) => {
const url = payload && typeof payload.url === 'string' ? payload.url : ''
if (!isAllowedImwaldBackendUrl(url)) {

22
electron/preload.cjs

@ -2,12 +2,16 @@ @@ -2,12 +2,16 @@
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('imwaldElectron', {
isElectron: true,
reloadApp: () => ipcRenderer.invoke('imwald:reload-app'),
/**
* Same-origin translate / LanguageTool from the renderer hits CORS when the shell is loopback.
* Main process performs the HTTP(S) request (allowlisted host + path only).
*/
backendRequest: (payload) => ipcRenderer.invoke('imwald:backend-request', payload)
})
try {
contextBridge.exposeInMainWorld('imwaldElectron', {
isElectron: true,
reloadApp: () => ipcRenderer.invoke('imwald:reload-app'),
/**
* Same-origin translate / LanguageTool from the renderer hits CORS when the shell is loopback.
* Main process performs the HTTP(S) request (allowlisted host + path only).
*/
backendRequest: (payload) => ipcRenderer.invoke('imwald:backend-request', payload)
})
} catch (err) {
console.error('[imwald] preload: failed to expose imwaldElectron', err)
}

4
package-lock.json generated

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
{
"name": "imwald",
"version": "23.0.9",
"version": "23.0.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "imwald",
"version": "23.0.9",
"version": "23.0.10",
"license": "MIT",
"dependencies": {
"@asciidoctor/core": "^3.0.4",

5
package.json

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
{
"name": "imwald",
"version": "23.0.9",
"version": "23.0.10",
"description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true,
"type": "module",
@ -171,6 +171,9 @@ @@ -171,6 +171,9 @@
"electron/**/*",
"package.json"
],
"asarUnpack": [
"electron/**/*"
],
"linux": {
"target": [
"AppImage",

5
src/lib/electron-aware-fetch.ts

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
import { isImwaldElectron } from '@/lib/client-platform'
export type ImwaldBackendResponse = {
status: number
statusText: string
@ -16,7 +14,8 @@ export async function electronAwareFetch(input: string, init?: RequestInit): Pro @@ -16,7 +14,8 @@ export async function electronAwareFetch(input: string, init?: RequestInit): Pro
throw new DOMException('Aborted', 'AbortError')
}
const bridge = typeof window !== 'undefined' ? window.imwaldElectron : undefined
if (!isImwaldElectron() || typeof bridge?.backendRequest !== 'function') {
/** Prefer main-process fetch whenever the preload bridge exists (do not rely on `isElectron` alone). */
if (typeof bridge?.backendRequest !== 'function') {
return fetch(input, init)
}

Loading…
Cancel
Save