-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[menu-bar][electron] Implement local server (#178)
* [menu-bar][electron] Implement local server * Fix CORS * Fix typescript checks * Add changelog entry
- Loading branch information
1 parent
56aae30
commit 2842cdb
Showing
7 changed files
with
183 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
import { app as electronApp } from 'electron'; | ||
import express, { Express } from 'express'; | ||
|
||
const PORTS = [35783, 47909, 44171, 50799]; | ||
const WHITELISTED_DOMAINS = ['expo.dev', 'expo.test', 'exp.host']; | ||
|
||
export class LocalServer { | ||
app: Express; | ||
|
||
constructor() { | ||
this.app = express(); | ||
this.setupMiddlewares(); | ||
this.setupRoutes(); | ||
} | ||
|
||
setupMiddlewares() { | ||
this.app.use((req, res, next) => { | ||
const origin = req.get('origin'); | ||
const referer = req.get('referer'); | ||
if (!origin || !referer || !WHITELISTED_DOMAINS.includes(this.extractRootDomain(origin))) { | ||
res.sendStatus(403); | ||
return; | ||
} | ||
|
||
res.set('Access-Control-Allow-Origin', origin); | ||
next(); | ||
}); | ||
} | ||
|
||
setupRoutes() { | ||
this.app.get('/orbit/status', (_, res) => { | ||
res.json({ ok: true, version: electronApp.getVersion() }); | ||
}); | ||
|
||
this.app.get('/orbit/open', (req, res) => { | ||
const urlParam = req.query.url as string | undefined; | ||
if (!urlParam || !WHITELISTED_DOMAINS.includes(this.extractRootDomain(urlParam))) { | ||
res.sendStatus(400); | ||
return; | ||
} | ||
|
||
const deeplinkURL = urlParam | ||
.replace('https://', 'expo-orbit://') | ||
.replace('exp://', 'expo-orbit://'); | ||
|
||
electronApp.emit('open-url', null, deeplinkURL); | ||
res.json({ ok: true }); | ||
}); | ||
} | ||
|
||
start(port: number = PORTS[0]) { | ||
this.app | ||
.listen(port, () => { | ||
console.log(`Local server running on port ${port}`); | ||
}) | ||
.on('error', (err) => { | ||
console.error(`Failed to start server on port ${port}: ${err.message}`); | ||
const nextPort = PORTS[PORTS.indexOf(port) + 1]; | ||
if (nextPort) { | ||
this.start(nextPort); | ||
} else { | ||
console.error(`Server start error: ${err.message}`); | ||
} | ||
}); | ||
} | ||
|
||
extractRootDomain(urlString: string) { | ||
try { | ||
const originUrl = new URL(decodeURIComponent(urlString)); | ||
let hostName = originUrl.hostname; | ||
|
||
if (!hostName) { | ||
// Orbit deeplink may include specific routes in the URL e.g. /update, /snack, /download, etc. | ||
const urlStringFromParams = originUrl.searchParams.get('url'); | ||
const urlFromParams = new URL(decodeURIComponent(urlStringFromParams)); | ||
hostName = urlFromParams.hostname; | ||
} | ||
|
||
if (!hostName.includes('.')) { | ||
hostName = originUrl.pathname.split('/').filter(Boolean)[1]; | ||
} | ||
const components = hostName.split('.'); | ||
return components.slice(-2).join('.'); | ||
} catch (error) { | ||
console.error('Error extracting root domain:', error); | ||
return ''; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters