Skip to content

Commit

Permalink
feat(infra): electron app can hot update renderer layer (RSSNext#1209)
Browse files Browse the repository at this point in the history
* ci

Signed-off-by: Innei <i@innei.in>

* feat: hotupdate renderer impl

* fix: skip dev

Signed-off-by: Innei <i@innei.in>

* update logci

Signed-off-by: Innei <i@innei.in>

* update logci

Signed-off-by: Innei <i@innei.in>

* feat: tracker

Signed-off-by: Innei <tukon479@gmail.com>

* log

Signed-off-by: Innei <tukon479@gmail.com>

* fix: hash

Signed-off-by: Innei <tukon479@gmail.com>

* fix: build

Signed-off-by: Innei <tukon479@gmail.com>

* fix: deps

Signed-off-by: Innei <tukon479@gmail.com>

* Update nightly.yml

* update

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* update minimunm

Signed-off-by: Innei <tukon479@gmail.com>

* calc main hash

Signed-off-by: Innei <tukon479@gmail.com>

* add main Hash logic

Signed-off-by: Innei <tukon479@gmail.com>

* remove

Signed-off-by: Innei <tukon479@gmail.com>

* fix

Signed-off-by: Innei <tukon479@gmail.com>

* log

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* test

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* fix: update

Signed-off-by: Innei <tukon479@gmail.com>

* debug

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* fix

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* fix: only one

Signed-off-by: Innei <tukon479@gmail.com>

* try fix cookie

Signed-off-by: Innei <tukon479@gmail.com>

* update

Signed-off-by: Innei <tukon479@gmail.com>

* fix cookie

Signed-off-by: Innei <tukon479@gmail.com>

* fix: domain

Signed-off-by: Innei <tukon479@gmail.com>

* fix

Signed-off-by: Innei <tukon479@gmail.com>

* fix: remove test code

Signed-off-by: Innei <tukon479@gmail.com>

* fix

Signed-off-by: Innei <tukon479@gmail.com>

---------

Signed-off-by: Innei <i@innei.in>
Signed-off-by: Innei <tukon479@gmail.com>
  • Loading branch information
Innei authored Nov 25, 2024
1 parent 59709f0 commit ca4751a
Show file tree
Hide file tree
Showing 37 changed files with 1,025 additions and 95 deletions.
75 changes: 75 additions & 0 deletions .github/workflows/build-render.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Build Electron Render

on:
push:
tags:
- "v*"

env:
VITE_WEB_URL: ${{ vars.VITE_WEB_URL }}
VITE_API_URL: ${{ vars.VITE_API_URL }}
VITE_IMGPROXY_URL: ${{ vars.VITE_IMGPROXY_URL }}
VITE_SENTRY_DSN: ${{ vars.VITE_SENTRY_DSN }}
VITE_OPENPANEL_CLIENT_ID: ${{ vars.VITE_OPENPANEL_CLIENT_ID }}
VITE_OPENPANEL_API_URL: ${{ vars.VITE_OPENPANEL_API_URL }}
VITE_FIREBASE_CONFIG: ${{ vars.VITE_FIREBASE_CONFIG }}
NODE_OPTIONS: --max-old-space-size=8192

jobs:
build-render:
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]

permissions:
id-token: write
contents: write
attestations: write

steps:
- name: Check out Git repository
uses: actions/checkout@v4
with:
lfs: true

- name: Cache pnpm modules
uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: ${{ matrix.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ matrix.os }}-
- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: "pnpm"

- name: Install dependencies
run: pnpm i
- name: Build
run: pnpm build:render
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}

- name: Setup Version
id: version
uses: ./.github/actions/setup-version

- name: Create Release Draft
uses: softprops/action-gh-release@v2
with:
name: v${{ steps.version.outputs.APP_VERSION }}
draft: false
prerelease: true
tag_name: v${{ steps.version.outputs.APP_VERSION }}
files: |
dist/manifest.yml
dist/*.tar.gz
3 changes: 2 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ jobs:
- name: Install dependencies
run: pnpm i

- name: Update main hash
run: pnpm update:main-hash
- name: Build
if: matrix.os != 'macos-latest'
run: npm exec turbo run //#build
Expand Down
24 changes: 17 additions & 7 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,25 +72,20 @@ jobs:
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/build_pp.provisionprofile
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
# import certificate and provisioning profile from secrets
echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH
# create temporary keychain
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
# import certificate to keychain
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH
# apply provisioning profile
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles
- name: Install dependencies
run: pnpm i

Expand All @@ -115,6 +110,9 @@ jobs:
fi
echo "Updated version to $NIGHTLY_VERSION"
- name: Update main hash
run: pnpm update:main-hash

- name: Build
if: matrix.os != 'macos-latest'
run: pnpm build
Expand All @@ -130,6 +128,10 @@ jobs:
KEYCHAIN_PATH: ${{ runner.temp }}/app-signing.keychain-db
run: pnpm build:macos

- name: Build (Render)
run: pnpm build:render
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
Expand All @@ -140,6 +142,9 @@ jobs:
out/make/**/*.AppImage
out/make/**/*.yml
out/make/**/*.dmg
dist/manifest.yml
dist/*.tar.gz
retention-days: 7

- name: Generate artifact attestation
Expand All @@ -152,23 +157,28 @@ jobs:
out/make/**/*.exe
out/make/**/*.AppImage
out/make/**/*.yml
dist/manifest.yml
dist/*.tar.gz
- name: Create Nightly Release
uses: softprops/action-gh-release@v2
with:
name: Nightly ${{ env.NIGHTLY_VERSION }}
draft: false
prerelease: true
tag_name: nightly-${{ env.NIGHTLY_VERSION }}
tag_name: ${{ env.NIGHTLY_VERSION }}
files: |
out/make/**/*.dmg
out/make/**/*.zip
out/make/**/*.exe
out/make/**/*.AppImage
out/make/**/*.yml
dist/manifest.yml
dist/*.tar.gz
body: |
This is an automated nightly release for testing purposes.
Version: 0.0.0-nightly.${{ env.NIGHTLY_VERSION }}
Version: ${{ env.NIGHTLY_VERSION }}
**Warning:** This build may be unstable and is not recommended for production use.
env:
Expand Down
5 changes: 5 additions & 0 deletions apps/main/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
import "../../types/vite"
import "../../types/authjs"

declare global {
const GIT_COMMIT_HASH: string
}
export {}
6 changes: 6 additions & 0 deletions apps/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,29 @@
"@eneris/push-receiver": "4.3.0",
"@follow/shared": "workspace:*",
"@mozilla/readability": "^0.5.0",
"@openpanel/web": "1.0.1",
"@sentry/electron": "5.7.0",
"builder-util-runtime": "9.2.10",
"electron-context-menu": "4.0.4",
"electron-log": "5.2.2",
"electron-squirrel-startup": "1.0.1",
"electron-updater": "^6.3.9",
"es-toolkit": "1.26.1",
"fast-folder-size": "2.3.0",
"font-list": "1.5.1",
"i18next": "^24.0.0",
"js-yaml": "4.1.0",
"linkedom": "^0.18.5",
"lowdb": "7.0.1",
"msedge-tts": "1.3.4",
"node-machine-id": "1.1.12",
"ofetch": "1.4.1",
"semver": "7.6.3",
"tar": "7.4.3",
"vscode-languagedetection": "npm:@vscode/vscode-languagedetection@^1.0.22"
},
"devDependencies": {
"@types/js-yaml": "4.0.9",
"@types/node": "^22.9.3",
"electron": "33.2.0",
"electron-devtools-installer": "3.2.0",
Expand Down
10 changes: 9 additions & 1 deletion apps/main/src/constants/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
// 5min
import path from "node:path"

import { app } from "electron"

export const UNREAD_BACKGROUND_POLLING_INTERVAL = 1000 * 60 * 5

export const HOTUPDATE_RENDER_ENTRY_DIR = path.resolve(app.getPath("userData"), "render")

export const GITHUB_OWNER = process.env.GITHUB_OWNER || "RSSNext"
export const GITHUB_REPO = process.env.GITHUB_REPO || "follow"

// https://github.com/electron/electron/issues/25081
export const START_IN_TRAY_ARGS = "--start-in-tray"
3 changes: 3 additions & 0 deletions apps/main/src/constants/system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { machineIdSync } from "node-machine-id"

export const DEVICE_ID = machineIdSync()
39 changes: 36 additions & 3 deletions apps/main/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { APP_PROTOCOL } from "@follow/shared/constants"
import { env } from "@follow/shared/env"
import { imageRefererMatches, selfRefererMatches } from "@follow/shared/image"
import { app, BrowserWindow, session } from "electron"
import type { Cookie } from "electron/main"
import squirrelStartup from "electron-squirrel-startup"

import { DEVICE_ID } from "./constants/system"
import { isDev, isMacOS } from "./env"
import { initializeAppStage0, initializeAppStage1 } from "./init"
import { updateProxy } from "./lib/proxy"
Expand All @@ -14,6 +16,7 @@ import { store } from "./lib/store"
import { registerAppTray } from "./lib/tray"
import { setAuthSessionToken, updateNotificationsToken } from "./lib/user"
import { registerUpdater } from "./updater"
import { cleanupOldRender } from "./updater/hot-updater"
import {
createMainWindow,
getMainWindow,
Expand All @@ -23,6 +26,9 @@ import {

if (isDev) console.info("[main] env loaded:", env)

const apiURL = process.env["VITE_API_URL"] || import.meta.env.VITE_API_URL

console.info("[main] device id:", DEVICE_ID)
if (squirrelStartup) {
app.quit()
}
Expand Down Expand Up @@ -56,7 +62,7 @@ function bootstrap() {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
app.whenReady().then(async () => {
// Default open or close DevTools by F12 in development
// and ignore CommandOrControl + R in production.
// see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
Expand All @@ -69,6 +75,28 @@ function bootstrap() {

mainWindow = createMainWindow()

// restore cookies
const cookies = store.get("cookies") as Cookie[]
if (cookies) {
Promise.all(
cookies.map((cookie) => {
const setCookieDetails: Electron.CookiesSetDetails = {
url: apiURL,
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
path: cookie.path,
secure: cookie.secure,
httpOnly: cookie.httpOnly,
expirationDate: cookie.expirationDate,
sameSite: cookie.sameSite as "unspecified" | "no_restriction" | "lax" | "strict",
}

return mainWindow.webContents.session.cookies.set(setCookieDetails)
}),
)
}

updateProxy()
registerUpdater()
registerAppTray()
Expand Down Expand Up @@ -135,7 +163,7 @@ function bootstrap() {
}
})

app.on("before-quit", () => {
app.on("before-quit", async () => {
// store window pos when before app quit
const window = getMainWindow()
if (!window || window.isDestroyed()) return
Expand All @@ -147,6 +175,12 @@ function bootstrap() {
x: bounds.x,
y: bounds.y,
})
await session.defaultSession.cookies.flushStore()

const cookies = await session.defaultSession.cookies.get({})
store.set("cookies", cookies)

await cleanupOldRender()
})

const handleOpen = async (url: string) => {
Expand All @@ -158,7 +192,6 @@ function bootstrap() {
const token = urlObj.searchParams.get("token")
const userId = urlObj.searchParams.get("userId")

const apiURL = process.env["VITE_API_URL"] || import.meta.env.VITE_API_URL
if (token && apiURL) {
setAuthSessionToken(token)
mainWindow.webContents.session.cookies.set({
Expand Down
1 change: 0 additions & 1 deletion apps/main/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export const initializeAppStage1 = () => {
// code. You can also put them in separate files and require them here.

registerMenuAndContextMenu()

registerPushNotifications()
clearCacheCronJob()
checkAndCleanCodeCache()
Expand Down
20 changes: 20 additions & 0 deletions apps/main/src/lib/op.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { env } from "@follow/shared/env"
import { OpenPanel } from "@openpanel/web"
import { app } from "electron"

import { DEVICE_ID } from "~/constants/system"

export const op = new OpenPanel({
clientId: env.VITE_OPENPANEL_CLIENT_ID ?? "",
trackScreenViews: false,
trackOutgoingLinks: false,
trackAttributes: false,
trackHashChanges: false,
apiUrl: env.VITE_OPENPANEL_API_URL,
})

op.setGlobalProperties({
device_id: DEVICE_ID,
app_version: app.getVersion(),
build: "electron",
})
12 changes: 12 additions & 0 deletions apps/main/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
import type { BrowserWindow } from "electron"

export const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
// To solve the vibrancy losing issue when leaving full screen mode
// @see https://github.com/toeverything/AFFiNE/blob/280e24934a27557529479a70ab38c4f5fc65cb00/packages/frontend/electron/src/main/windows-manager/main-window.ts:L157
export function refreshBound(window: BrowserWindow, timeout = 0) {
setTimeout(() => {
// FIXME: workaround for theme bug in full screen mode
const size = window?.getSize()
window?.setSize(size[0] + 1, size[1] + 1)
window?.setSize(size[0], size[1])
}, timeout)
}
4 changes: 2 additions & 2 deletions apps/main/src/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { isDev, isMacOS } from "./env"
import { clearAllDataAndConfirm } from "./lib/cleaner"
import { t } from "./lib/i18n"
import { revealLogFile } from "./logger"
import { checkForUpdates, quitAndInstall } from "./updater"
import { checkForAppUpdates, quitAndInstall } from "./updater"
import { createSettingWindow, createWindow, getMainWindow } from "./window"

export const registerAppMenu = () => {
Expand Down Expand Up @@ -184,7 +184,7 @@ export const registerAppMenu = () => {
label: t("menu.checkForUpdates"),
click: async () => {
getMainWindow()?.show()
await checkForUpdates()
await checkForAppUpdates()
},
},
],
Expand Down
Loading

0 comments on commit ca4751a

Please sign in to comment.