Skip to content

Commit

Permalink
Deeplinks/custom schema/SEP7 links for iOS, Android, Electron (#17574)
Browse files Browse the repository at this point in the history
* Rebase on master

* fix up Linux startup

* remove debugging

* rename deeplink => schema handler launch

* add keybase.desktop change

* Feedback from Surya

* Rename Android intent for SEP7

* reduxLaunched suggestion from Danny

* Try out Surya's KEYBASE_STARTUP_URL env var suggestion

* add to unset-environment too

* back to cmdline instead of env var

* remove more KEYBASE_STARTUP_URL traces

* Surya's suggestions

* More Surya feedback
  • Loading branch information
cjb committed Jun 19, 2019
1 parent 24c64da commit 29941eb
Show file tree
Hide file tree
Showing 10 changed files with 107 additions and 16 deletions.
3 changes: 2 additions & 1 deletion packaging/linux/keybase.desktop
@@ -1,8 +1,9 @@
[Desktop Entry]
Name=Keybase
Exec=run_keybase
Exec=run_keybase %u
Icon=keybase
Terminal=false
Type=Application
Categories=Network;
StartupNotify=false
MimeType=x-scheme-handler/web+stellar;
37 changes: 31 additions & 6 deletions packaging/linux/run_keybase
Expand Up @@ -120,12 +120,7 @@ start_background() {

echo Launching Keybase GUI...
gui_log="$logdir/Keybase.app.log"
# Allow distributions to change the location of the gui as long as it's in PATH.
if command -v Keybase &> /dev/null; then
Keybase &>> "$gui_log" &
else
/opt/keybase/Keybase &>> "$gui_log" &
fi
"$KEYBASE" &>> "$gui_log" &
fi
}

Expand Down Expand Up @@ -176,6 +171,30 @@ init() {

# Remove legacy envfiles; now stored in config directory by ctl init
rm -f "$runtime_dir/keybase.env" "$runtime_dir/keybase.kbfs.env" "$runtime_dir/keybase.gui.env"

# Allow distributions to change the location of the gui as long as it's in PATH.
if command -v Keybase &> /dev/null; then
KEYBASE=Keybase
else
KEYBASE=/opt/keybase/Keybase
fi
}

check_for_url_scheme_launch() {
# We set a URL scheme handler on our .desktop file, and since that file
# points to this script, that's what gets called with URL links. We can
# differentiate between a normal run_keybase invocation and a URL launch
# by checking argv.

# If Keybase is already running, then pass it the link and exit.
if [[ $# -eq 1 && "$1" =~ ^web+.* ]]; then
if pgrep -u "$USER" -f 'Keybase$' &> /dev/null; then
"$KEYBASE" "$1" &
exit
fi
fi

# Since we didn't exit, fall through to a normal startup.
}

startup_all() {
Expand Down Expand Up @@ -218,6 +237,7 @@ KEYBASE_NO_GUI="${KEYBASE_NO_GUI:-0}"
KEYBASE_NO_KBFS="${KEYBASE_NO_KBFS:-0}"
KEYBASE_AUTOSTART="${KEYBASE_AUTOSTART:-0}"
KEYBASE_KILL="${KEYBASE_KILL:-0}"

# NOTE: Make sure to update the Linux User Guide doc if you change this!
# http://keybase.io/docs/linux-user-guide
while getopts "afghk" flag; do
Expand All @@ -232,6 +252,11 @@ while getopts "afghk" flag; do
done

init

# Exit early if run caused by a URL scheme invocation and Keybase is already
# running; otherwise fall through to start Keybase.
check_for_url_scheme_launch "$@"

# Always stop any running services. With systemd, we could've decided to just
# `start` services and no-op if they're already running, however:
# 1) We still need to handle the case where services started outside systemd
Expand Down
16 changes: 10 additions & 6 deletions shared/actions/config/index.tsx
Expand Up @@ -382,12 +382,16 @@ const routeToInitialScreen = state => {

const handleAppLink = (_, action: ConfigGen.LinkPayload) => {
const url = new URL(action.payload.link)
const username = Constants.urlToUsername(url)
if (username) {
return [
RouteTreeGen.createSwitchTo({path: [Tabs.peopleTab]}),
ProfileGen.createShowUserProfile({username}),
]
if (action.payload.link.startsWith('web+stellar:')) {
console.warn('Got SEP7 link:', action.payload.link)
} else {
const username = Constants.urlToUsername(url)
if (username) {
return [
RouteTreeGen.createSwitchTo({path: [Tabs.peopleTab]}),
ProfileGen.createShowUserProfile({username}),
]
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions shared/android/app/src/main/AndroidManifest.xml
Expand Up @@ -45,6 +45,12 @@
<data android:mimeType="audio/*" />
<data android:mimeType="application/*" />
</intent-filter>
<intent-filter android:label="Open with Keybase">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="web+stellar" />
</intent-filter>
<!-- TODO: enable
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
Expand Down
1 change: 1 addition & 0 deletions shared/app/index.native.tsx
Expand Up @@ -38,6 +38,7 @@ class Keybase extends Component<any> {

componentDidMount() {
Linking.addEventListener('url', this._handleOpenURL)
Linking.getInitialURL().then(event => event && event.url && this._handleOpenURL(event))
}

componentWillUnmount() {
Expand Down
2 changes: 2 additions & 0 deletions shared/desktop/app/main-window.desktop.tsx
Expand Up @@ -65,6 +65,8 @@ export default function() {
appState.manageWindow(mainWindow.window)

const app = SafeElectron.getApp()
// Register for SEP7 links.
app.setAsDefaultProtocolClient('web+stellar')

const openedAtLogin = app.getLoginItemSettings().wasOpenedAtLogin
// app.getLoginItemSettings().restoreState is Mac only, so consider it always on in Windows
Expand Down
42 changes: 39 additions & 3 deletions shared/desktop/app/node.desktop.tsx
Expand Up @@ -8,9 +8,14 @@ import * as SafeElectron from '../../util/safe-electron.desktop'
import {setupExecuteActionsListener, executeActionsForContext} from '../../util/quit-helper.desktop'
import {allowMultipleInstances} from '../../local-debug.desktop'
import startWinService from './start-win-service.desktop'
import {isWindows, cacheRoot} from '../../constants/platform.desktop'
import {isDarwin, isWindows, cacheRoot} from '../../constants/platform.desktop'
import {sendToMainWindow} from '../remote/util.desktop'
import * as ConfigGen from '../../actions/config-gen'
import logger from '../../logger'

let mainWindow = null
let reduxLaunched = false
let startupURL = null

const installCrashReporter = () => {
if (process.env.KEYBASE_CRASH_REPORT) {
Expand Down Expand Up @@ -56,7 +61,7 @@ const appShouldDieOnStartup = () => {
return false
}

const focusSelfOnAnotherInstanceLaunching = () => {
const focusSelfOnAnotherInstanceLaunching = (_, commandLine) => {
if (!mainWindow) {
return
}
Expand All @@ -65,6 +70,12 @@ const focusSelfOnAnotherInstanceLaunching = () => {
if (isWindows) {
mainWindow.window && mainWindow.window.focus()
}

// The new instance might be due to a URL schema handler launch.
logger.info('Launched with URL', commandLine)
if (commandLine.length > 1 && commandLine[1] && commandLine[1].startsWith('web+stellar:')) {
sendToMainWindow('dispatchAction', {payload: {link: commandLine[1]}, type: ConfigGen.link})
}
}

const changeCommandLineSwitches = () => {
Expand Down Expand Up @@ -139,6 +150,19 @@ const createMainWindow = () => {
SafeElectron.getIpcMain().on('remoteWindowWantsProps', (_, windowComponent, windowParam) => {
mainWindow && mainWindow.window.webContents.send('remoteWindowWantsProps', windowComponent, windowParam)
})

SafeElectron.getIpcMain().on('reduxLaunched', () => {
reduxLaunched = true
if (startupURL) {
// Mac calls open-url for a launch URL before redux is up, so we
// stash a startupURL to be dispatched when we're ready for it.
sendToMainWindow('dispatchAction', {payload: {link: startupURL}, type: ConfigGen.link})
startupURL = null
} else if (!isDarwin && process.argv.length > 1 && process.argv[1].startsWith('web+stellar:')) {
// Windows and Linux instead store a launch URL in argv.
sendToMainWindow('dispatchAction', {payload: {link: process.argv[1]}, type: ConfigGen.link})
}
})
}

const handleInstallCheck = (event, arg) => {
Expand Down Expand Up @@ -174,6 +198,17 @@ const handleQuitting = event => {
executeActionsForContext('beforeQuit')
}

const willFinishLaunching = () => {
SafeElectron.getApp().on('open-url', (event, link) => {
event.preventDefault()
if (!reduxLaunched) {
startupURL = link
} else {
sendToMainWindow('dispatchAction', {payload: {link}, type: ConfigGen.link})
}
})
}

const start = () => {
handleCrashes()
installCrashReporter()
Expand All @@ -185,7 +220,7 @@ const start = () => {

console.log('Version:', SafeElectron.getApp().getVersion())

// Foreground if another instance tries to launch
// Foreground if another instance tries to launch, look for SEP7 link
SafeElectron.getApp().on('second-instance', focusSelfOnAnotherInstanceLaunching)

fixWindowsNotifications()
Expand All @@ -195,6 +230,7 @@ const start = () => {
// Load menubar and get its browser window id so we can tell the main window
setupMenubar()

SafeElectron.getApp().once('will-finish-launching', willFinishLaunching)
SafeElectron.getApp().once('ready', createMainWindow)
SafeElectron.getIpcMain().on('install-check', handleInstallCheck)
SafeElectron.getIpcMain().on('kb-service-check', handleKBServiceCheck)
Expand Down
4 changes: 4 additions & 0 deletions shared/desktop/package.desktop.tsx
Expand Up @@ -56,6 +56,10 @@ const packagerOpts: any = {
icon: null,
ignore: ['.map', '/test($|/)', '/tools($|/)', '/release($|/)', '/node_modules($|/)'],
name: appName,
protocols: [{
name: 'Keybase',
schemes: ['web+stellar'],
}],
}

function main() {
Expand Down
3 changes: 3 additions & 0 deletions shared/desktop/renderer/main.desktop.tsx
Expand Up @@ -88,6 +88,9 @@ function setupApp(store, runSagas) {

// Handle notifications from the service
store.dispatch(NotificationsGen.createListenForNotifications())

// Check for a startup URL
SafeElectron.getIpcRenderer().send('reduxLaunched')
}

const FontLoader = () => (
Expand Down
9 changes: 9 additions & 0 deletions shared/ios/Keybase/Info.plist
Expand Up @@ -2,6 +2,15 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>web+stellar</string>
</array>
</dict>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
Expand Down

0 comments on commit 29941eb

Please sign in to comment.