├── .gitignore ├── src ├── utils │ ├── fileExt.ts │ └── versioning.ts ├── index.ts ├── legacy │ ├── constants.ts │ ├── validatePlatform.ts │ ├── checkPlatform.ts │ └── handler.ts ├── types.ts ├── getPlatform.ts ├── constants.ts ├── services │ └── github.ts └── handler.ts ├── .prettierrc ├── jestconfig.json ├── wrangler.toml ├── .github └── workflows │ └── deploy.yml ├── tsconfig.json ├── test └── handler.test.ts ├── webpack.config.js ├── LICENSE ├── LICENSE_MIT ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | transpiled 4 | /.idea/ 5 | -------------------------------------------------------------------------------- /src/utils/fileExt.ts: -------------------------------------------------------------------------------- 1 | export function fileExt(filename: string): string { 2 | return filename.split('.').pop() || '' 3 | } 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "semi": false, 4 | "trailingComma": "all", 5 | "tabWidth": 2, 6 | "printWidth": 80 7 | } 8 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { handleRequest } from './handler' 2 | 3 | addEventListener('fetch', (event) => { 4 | event.respondWith(handleRequest(event.request)) 5 | }) 6 | -------------------------------------------------------------------------------- /src/legacy/constants.ts: -------------------------------------------------------------------------------- 1 | export enum LEGACY_AVAILABLE_PLATFORMS { 2 | MacOS = 'darwin', 3 | Win32 = 'win32', 4 | Win64 = 'win64', 5 | Linux = 'linux', 6 | } 7 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type TauriUpdateResponse = { 2 | url: string 3 | version: string 4 | notes?: string 5 | pub_date?: string 6 | signature?: string 7 | } 8 | -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.(t|j)sx?$": "ts-jest" 4 | }, 5 | "testRegex": "/test/.*\\.test\\.ts$", 6 | "collectCoverageFrom": ["src/**/*.{ts,js}"] 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/versioning.ts: -------------------------------------------------------------------------------- 1 | import semverValid from 'semver/functions/valid' 2 | import semverGt from 'semver/functions/gt' 3 | 4 | export function sanitizeVersion(version: string): string | undefined { 5 | // Works with or without v in version 6 | const semanticV = version.split('v').pop() 7 | return semanticV 8 | } 9 | 10 | export { semverGt } 11 | export { semverValid } 12 | -------------------------------------------------------------------------------- /src/legacy/validatePlatform.ts: -------------------------------------------------------------------------------- 1 | import { LEGACY_AVAILABLE_PLATFORMS } from './constants' 2 | 3 | export function validatePlatform(platform: string): string | undefined { 4 | switch (platform) { 5 | case LEGACY_AVAILABLE_PLATFORMS.MacOS: 6 | case LEGACY_AVAILABLE_PLATFORMS.Win32: 7 | case LEGACY_AVAILABLE_PLATFORMS.Win64: 8 | case LEGACY_AVAILABLE_PLATFORMS.Linux: 9 | return platform 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "tmexclude-tauri-update" 2 | type = "javascript" 3 | zone_id = "" 4 | account_id = "" 5 | route = "" 6 | workers_dev = true 7 | compatibility_date = "2022-03-26" 8 | 9 | [vars] 10 | GITHUB_ACCOUNT = 'PhotonQuantum' 11 | GITHUB_REPO = 'tmexclude' 12 | GITHUB_TOKEN = '' # Optional - used with private repos 13 | 14 | [build] 15 | command = "npm install && npm run build" 16 | [build.upload] 17 | format = "service-worker" 18 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | repository_dispatch: 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | name: Deploy 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Publish 15 | uses: cloudflare/wrangler-action@1.3.0 16 | with: 17 | apiToken: ${{ secrets.CF_API_TOKEN }} 18 | env: 19 | CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }} 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "module": "commonjs", 5 | "target": "esnext", 6 | "lib": ["esnext"], 7 | "alwaysStrict": true, 8 | "strict": true, 9 | "preserveConstEnums": true, 10 | "moduleResolution": "node", 11 | "sourceMap": true, 12 | "esModuleInterop": true, 13 | "types": [ 14 | "@cloudflare/workers-types", 15 | "@types/jest", 16 | "@types/service-worker-mock" 17 | ] 18 | }, 19 | "include": ["src"], 20 | "exclude": ["node_modules", "dist", "test"] 21 | } 22 | -------------------------------------------------------------------------------- /test/handler.test.ts: -------------------------------------------------------------------------------- 1 | import { handleRequest } from '../src/handler' 2 | import makeServiceWorkerEnv from 'service-worker-mock' 3 | 4 | declare var global: any 5 | 6 | describe('handle', () => { 7 | beforeEach(() => { 8 | Object.assign(global, makeServiceWorkerEnv()) 9 | jest.resetModules() 10 | }) 11 | 12 | test('handle GET', async () => { 13 | const result = await handleRequest(new Request('/', { method: 'GET' })) 14 | expect(result.status).toEqual(200) 15 | const text = await result.text() 16 | expect(text).toEqual('request method: GET') 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | entry: './src/index.ts', 5 | output: { 6 | filename: 'worker.js', 7 | path: path.join(__dirname, 'dist'), 8 | }, 9 | devtool: 'cheap-module-source-map', 10 | mode: 'development', 11 | resolve: { 12 | extensions: ['.ts', '.tsx', '.js'], 13 | }, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.tsx?$/, 18 | loader: 'ts-loader', 19 | options: { 20 | // transpileOnly is useful to skip typescript checks occasionally: 21 | // transpileOnly: true, 22 | }, 23 | }, 24 | ], 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /src/getPlatform.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ARCH_FILTERS, 3 | AVAILABLE_ARCHITECTURES, 4 | AVAILABLE_PLATFORMS, 5 | PLATFORM_FILTERS, 6 | } from './constants' 7 | import { fileExt } from './utils/fileExt' 8 | 9 | export const testAsset = ( 10 | target: AVAILABLE_PLATFORMS, 11 | arch: AVAILABLE_ARCHITECTURES, 12 | fileName: string, 13 | ): boolean => { 14 | const { matches, extension } = PLATFORM_FILTERS[target] 15 | const arch_matches = ARCH_FILTERS[arch] 16 | const rightArch = 17 | arch_matches && ARCH_FILTERS[arch].some((arch) => fileName.includes(arch)) 18 | 19 | // .app gz files don't have arch in the name 20 | if (!rightArch && target !== AVAILABLE_PLATFORMS.MacOS) { 21 | return false 22 | } 23 | 24 | if (fileExt(fileName) !== extension) { 25 | return false 26 | } 27 | 28 | return matches.some((match) => fileName.includes(match)) 29 | } 30 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export enum AVAILABLE_PLATFORMS { 2 | MacOS = 'darwin', 3 | Windows = 'windows', 4 | Linux = 'linux', 5 | } 6 | 7 | export enum AVAILABLE_ARCHITECTURES { 8 | x64 = 'x86_64', 9 | x86 = 'i686', 10 | arm64 = 'aarch64', 11 | armv7 = 'armv7', 12 | } 13 | 14 | //TODO: check if this is correct 15 | export const ARCH_FILTERS = { 16 | [AVAILABLE_ARCHITECTURES.x64]: ['x64', 'amd64'], 17 | [AVAILABLE_ARCHITECTURES.x86]: ['x86'], 18 | [AVAILABLE_ARCHITECTURES.arm64]: ['arm64'], 19 | [AVAILABLE_ARCHITECTURES.armv7]: ['armv7'], 20 | } as { [key in AVAILABLE_ARCHITECTURES]: string[] } 21 | 22 | export type Filter = { 23 | extension: string 24 | matches: string[] 25 | } 26 | export const PLATFORM_FILTERS = { 27 | [AVAILABLE_PLATFORMS.MacOS]: { 28 | extension: 'gz', 29 | matches: ['.app', 'osx'], 30 | }, 31 | [AVAILABLE_PLATFORMS.Windows]: { 32 | extension: 'zip', 33 | matches: ['x64', 'x32'], 34 | }, 35 | [AVAILABLE_PLATFORMS.Linux]: { 36 | extension: 'gz', 37 | matches: ['AppImage'], 38 | }, 39 | } as { 40 | [key in AVAILABLE_PLATFORMS]: Filter 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 KilleenCode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Cloudflare, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/legacy/checkPlatform.ts: -------------------------------------------------------------------------------- 1 | import { LEGACY_AVAILABLE_PLATFORMS } from './constants' 2 | import { fileExt } from '../utils/fileExt' 3 | 4 | export function checkPlatform( 5 | platform: string, 6 | fileName: string, 7 | ): string | undefined { 8 | const extension = fileExt(fileName) 9 | // OSX we should have our .app tar.gz 10 | if ( 11 | (fileName.includes('.app') || 12 | fileName.includes('darwin') || 13 | fileName.includes('osx')) && 14 | extension === 'gz' && 15 | platform === LEGACY_AVAILABLE_PLATFORMS.MacOS 16 | ) { 17 | return 'darwin' 18 | } 19 | 20 | // Windows 64 bits 21 | if ( 22 | (fileName.includes('x64') || fileName.includes('win64')) && 23 | extension === 'zip' && 24 | platform === LEGACY_AVAILABLE_PLATFORMS.Win64 25 | ) { 26 | return 'win64' 27 | } 28 | 29 | // Windows 32 bits 30 | if ( 31 | (fileName.includes('x32') || fileName.includes('win32')) && 32 | extension === 'zip' && 33 | platform === LEGACY_AVAILABLE_PLATFORMS.Win32 34 | ) { 35 | return 'win32' 36 | } 37 | 38 | // Linux app image 39 | if ( 40 | fileName.includes('AppImage') && 41 | extension === 'gz' && 42 | platform === LEGACY_AVAILABLE_PLATFORMS.Linux 43 | ) { 44 | return 'linux' 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "worker-typescript-template", 3 | "version": "1.0.0", 4 | "description": "Cloudflare worker TypeScript template", 5 | "main": "dist/worker.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "format": "prettier --write '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'", 9 | "lint": "eslint --max-warnings=0 src && prettier --check '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'", 10 | "test": "jest --config jestconfig.json --verbose" 11 | }, 12 | "author": "author", 13 | "license": "MIT OR Apache-2.0", 14 | "eslintConfig": { 15 | "root": true, 16 | "extends": [ 17 | "typescript", 18 | "prettier" 19 | ] 20 | }, 21 | "devDependencies": { 22 | "@cloudflare/workers-types": "^3.0.0", 23 | "@types/jest": "^26.0.23", 24 | "@types/semver": "^7.3.9", 25 | "@types/service-worker-mock": "^2.0.1", 26 | "@typescript-eslint/eslint-plugin": "^4.16.1", 27 | "@typescript-eslint/parser": "^4.16.1", 28 | "eslint": "^7.21.0", 29 | "eslint-config-prettier": "^8.1.0", 30 | "eslint-config-typescript": "^3.0.0", 31 | "jest": "^27.0.1", 32 | "prettier": "^2.3.0", 33 | "service-worker-mock": "^2.0.5", 34 | "ts-jest": "^27.0.1", 35 | "ts-loader": "^9.2.2", 36 | "typescript": "^4.3.2", 37 | "webpack": "^5.38.1", 38 | "webpack-cli": "^4.7.0" 39 | }, 40 | "dependencies": { 41 | "semver": "^7.3.5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/services/github.ts: -------------------------------------------------------------------------------- 1 | export type Asset = { name: string; browser_download_url: string } 2 | export const getReleases = async (request: Request): Promise => { 3 | const reqUrl = new URL( 4 | `https://api.github.com/repos/${GITHUB_ACCOUNT}/${GITHUB_REPO}/releases/latest`, 5 | ) 6 | const headers = new Headers({ 7 | Accept: 'application/vnd.github.preview', 8 | 'User-Agent': request.headers.get('User-Agent') as string, 9 | }) 10 | 11 | if (GITHUB_TOKEN?.length) headers.set('Authorization', `token ${GITHUB_TOKEN}`) 12 | 13 | return await fetch(reqUrl.toString(), { 14 | method: 'GET', 15 | headers, 16 | }) 17 | } 18 | 19 | type Release = { 20 | tag_name: string 21 | assets: Asset[] 22 | body: string 23 | published_at: string 24 | } 25 | 26 | export const getLatestRelease = async (request: Request): Promise => { 27 | const releases = await getReleases(request) 28 | 29 | return (await releases.json()) as Release 30 | } 31 | 32 | export async function findAssetSignature( 33 | fileName: string, 34 | assets: Asset[], 35 | ): Promise { 36 | // check in our assets if we have a file: `fileName.sig` 37 | // by example fileName can be: App-1.0.0.zip 38 | 39 | const foundSignature = assets.find( 40 | (asset) => asset.name.toLowerCase() === `${fileName.toLowerCase()}.sig`, 41 | ) 42 | 43 | if (!foundSignature) { 44 | return undefined 45 | } 46 | 47 | const response = await fetch(foundSignature.browser_download_url) 48 | if (response.status !== 200) { 49 | return undefined 50 | } 51 | const signature = await response.text() 52 | return signature 53 | } 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tauri Update Server: Cloudflare 2 | 3 | [![Deploy to Cloudflare Workers](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/killeencode/tauri-update-cloudflare) 4 | 5 | ## One-Click Deploy 6 | 1. Click the button above, let Cloudflare walk you through: it's easy! 7 | 2. Go to your forked repository, edit `wrangler.toml`: 8 | - Update `GITHUB_ACCOUNT` and `GITHUB_REPO` to point to the Tauri project you're publishing releases from 9 | 10 | Much credit to [@lemarier](https://github.com/lemarier) for the underlying logic at https://github.com/lemarier/updater-deno 11 | 12 | ## Tauri Version Support 13 | ### Tauri >= v1.0.0-rc5: 14 | 15 | use `https://your-update-server.com/v1` route 16 | 17 | For example usage, see [Brancato config](https://github.com/KilleenCode/brancato/blob/main/src-tauri/tauri.conf.json#L55) 18 | 19 | ### Legacy 20 | use `https://your-update-server.com/` 21 | 22 | ## Cloudflare Wrangler 23 | 24 | ### 👩 💻 Developing 25 | 26 | `wrangler dev` 27 | 28 | [`src/index.ts`](./src/index.ts) calls the request handler in [`src/handler.ts`](./src/handler.ts), and will return the [request method](https://developer.mozilla.org/en-US/docs/Web/API/Request/method) for the given request. 29 | 30 | ### 🧪 Testing 31 | 32 | This template comes with jest tests which simply test that the request handler can handle each request method. `npm test` will run your tests. 33 | 34 | ### 👀 Previewing and Publishing 35 | 36 | `wrangler preview` 37 | `wrangler publish` 38 | 39 | For information on how to preview and publish your worker, please see the [Wrangler docs](https://developers.cloudflare.com/workers/tooling/wrangler/commands/#publish). 40 | 41 | 42 | ## Private repos 43 | 44 | In order to work with private repos you need to set `GITHUB_TOKEN` variable to your `wrangler.toml` file. You can create a [personal access token here](https://github.com/settings/tokens/new), create it with the repo permissions. -------------------------------------------------------------------------------- /src/legacy/handler.ts: -------------------------------------------------------------------------------- 1 | import { validatePlatform } from './validatePlatform' 2 | import { checkPlatform } from './checkPlatform' 3 | import { Asset, findAssetSignature, getReleases } from '../services/github' 4 | import { TauriUpdateResponse } from '../types' 5 | import { sanitizeVersion, semverGt, semverValid } from '../utils/versioning' 6 | 7 | export const handleLegacyRequest = async ( 8 | request: Request, 9 | ): Promise => { 10 | const path = new URL(request.url).pathname 11 | const [platform, version] = path.slice(1).split('/') 12 | 13 | const releases = await getReleases(request) 14 | 15 | const release = (await releases.clone().json()) as { 16 | tag_name: string 17 | assets: Asset[] 18 | body: string 19 | published_at: string 20 | } 21 | if (!platform || !validatePlatform(platform) || !version) { 22 | return releases 23 | } 24 | const remoteVersion = sanitizeVersion(release.tag_name.toLowerCase()) 25 | 26 | if (!remoteVersion || !semverValid(remoteVersion)) { 27 | return new Response('Not found', { status: 404 }) 28 | } 29 | 30 | const shouldUpdate = semverGt(remoteVersion, version) 31 | if (!shouldUpdate) { 32 | return new Response(null, { status: 204 }) 33 | } 34 | 35 | for (const asset of release.assets) { 36 | const { name, browser_download_url } = asset 37 | const findPlatform = checkPlatform(platform, name) 38 | if (!findPlatform) { 39 | continue 40 | } 41 | 42 | // try to find signature for this asset 43 | const signature = await findAssetSignature(name, release.assets) 44 | const data: TauriUpdateResponse = { 45 | url: browser_download_url, 46 | version: remoteVersion, 47 | notes: release.body, 48 | pub_date: release.published_at, 49 | signature, 50 | } 51 | return new Response(JSON.stringify(data), { 52 | headers: { 'Content-Type': 'application/json; charset=utf-8' }, 53 | }) 54 | } 55 | 56 | return new Response(JSON.stringify({ remoteVersion, version, shouldUpdate })) 57 | } 58 | -------------------------------------------------------------------------------- /src/handler.ts: -------------------------------------------------------------------------------- 1 | import { testAsset } from './getPlatform' 2 | import semverValid from 'semver/functions/valid' 3 | import semverGt from 'semver/functions/gt' 4 | import { AVAILABLE_ARCHITECTURES, AVAILABLE_PLATFORMS } from './constants' 5 | import { handleLegacyRequest } from './legacy/handler' 6 | import { findAssetSignature, getLatestRelease } from './services/github' 7 | import { TauriUpdateResponse } from './types' 8 | import { sanitizeVersion } from './utils/versioning' 9 | 10 | declare global { 11 | const GITHUB_ACCOUNT: string 12 | const GITHUB_REPO: string 13 | const GITHUB_TOKEN: string 14 | } 15 | 16 | const responses = { 17 | NotFound: () => new Response('Not found', { status: 404 }), 18 | NoContent: () => new Response(null, { status: 204 }), 19 | SendUpdate: (data: TauriUpdateResponse) => 20 | new Response(JSON.stringify(data), { 21 | headers: { 'Content-Type': 'application/json; charset=utf-8' }, 22 | }), 23 | } 24 | 25 | type RequestPathParts = [ 26 | string, 27 | AVAILABLE_PLATFORMS, 28 | AVAILABLE_ARCHITECTURES, 29 | string, 30 | ] 31 | const handleV1Request = async (request: Request) => { 32 | const path = new URL(request.url).pathname 33 | const [, target, arch, appVersion] = path 34 | .slice(1) 35 | .split('/') as RequestPathParts 36 | 37 | if (!target || !arch || !appVersion || !semverValid(appVersion)) { 38 | return responses.NotFound() 39 | } 40 | const release = await getLatestRelease(request) 41 | 42 | const remoteVersion = sanitizeVersion(release.tag_name.toLowerCase()) 43 | if (!remoteVersion || !semverValid(remoteVersion)) { 44 | return responses.NotFound() 45 | } 46 | 47 | const shouldUpdate = semverGt(remoteVersion, appVersion) 48 | if (!shouldUpdate) { 49 | return responses.NoContent() 50 | } 51 | 52 | const match = release.assets.find(({ name }) => { 53 | const test = testAsset(target, arch, name) 54 | 55 | return test 56 | }) 57 | 58 | if (typeof match === 'undefined') { 59 | return responses.NotFound() 60 | } 61 | 62 | const signature = await findAssetSignature(match.name, release.assets) 63 | const data: TauriUpdateResponse = { 64 | url: match.browser_download_url, 65 | version: remoteVersion, 66 | notes: release.body, 67 | pub_date: release.published_at, 68 | signature, 69 | } 70 | 71 | return responses.SendUpdate(data) 72 | } 73 | 74 | export async function handleRequest(request: Request): Promise { 75 | const path = new URL(request.url).pathname 76 | const version = path.slice(1).split('/')[0] 77 | 78 | if (version.includes('v')) { 79 | switch (version) { 80 | case 'v1': 81 | default: 82 | return handleV1Request(request) 83 | } 84 | } 85 | 86 | return handleLegacyRequest(request) 87 | } 88 | --------------------------------------------------------------------------------