Open app and pass parameters with deep linking using Electron (macOS) - macos

I want to open app and pass parameters with deep linking using Electron (macOS).
Project 'electron-deep-linking-mac-win' is on GitHub.
Edited package.json, following ‘electron-builder’ quick-setup-guide to produce mac installer:
{
"name": "electron-deep-linking-osx",
"version": "1.0.0",
"description": "A minimal Electron application with Deep Linking (OSX)",
"main": "main.js",
"scripts": {
"start": "electron .",
"pack": "build --dir",
"dist": "build"
},
"repository": "https://github.com/oikonomopo/electron-deep-linking-osx",
"keywords": [
"Electron",
"osx",
"deep-linking"
],
"author": "GitHub",
"license": "CC0-1.0",
"devDependencies": {
"electron": "1.6.6",
"electron-builder": "17.1.2"
},
"build": {
"appId": "your.id",
"mac": {
"category": "your.app.category.type"
},
"protocols": {
"name": "myApp",
"schemes": ["myApp"]
}
}
}
Edited main.js, appended code to register myapp url scheme protocol, listen 'open-url' events and log the arguments:
const electron = require('electron')
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
// Module with utilities for working with file and directory paths.
const path = require('path')
// Module with utilities for URL resolution and parsing.
const url = require('url')
// Module to display native system dialogs for opening and saving files, alerting, etc.
const dialog = electron.dialog
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
// Open the DevTools.
mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// 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.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
// The setAsDefaultProtocolClient only works on packaged versions of the application
app.setAsDefaultProtocolClient('myApp')
// Protocol handler for osx
app.on('open-url', function (event, url) {
event.preventDefault();
log("open-url event: " + url)
dialog.showErrorBox('open-url', `You arrived from: ${url}`)
})
// Log both at terminal and at browser
function log(s) {
console.log(s)
if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.executeJavaScript(`console.log("${s}")`)
}
}
Steps to come to life:-)
# Clone this repository
git clone https://github.com/oikonomopo/electron-deep-linking-mac-win.git
# Go into the repository
cd electron-deep-linking-mac-win
# Install dependencies
npm install
# Run the app
npm start
# Produce installer
npm run dist
After running the installer (electron-deep-linking-mac-win/dist/electron-quick-start-1.0.0.dmg), i try to open electron-deep-linking-os app with deep linking, entering myapp://param at Safari address bar.
If app is opened, it activates and i can see the dialog and the log open-url event: myapp://param!
If app is closed, it opens, dialog is shown with proper url but isn't logged to dev console!
Why with dialog module url is showing properly, but isn't logged to dev console?
How to log it?
Looking for solution using only electron-builder (which uses electron-packager)!

Solved for both macOS and win32 ( Updated project 'electron-deep-linking-mac-win' on GitHub).
package.json:
{
"name": "electron-deeplinking-macos-win32",
"version": "0.0.1",
"description": "Minimal Electron application with deep inking (macOS/win32)",
"main": "main.js",
"scripts": {
"start": "electron .",
"pack": "build --dir",
"dist": "build"
},
"repository": "https://github.com/oikonomopo/electron-deep-linking-osx",
"author": "oikonomopo",
"license": "CC0-1.0",
"devDependencies": {
"electron": "1.6.6",
"electron-builder": "17.1.2"
},
"build": {
"appId": "oikonomopo.electron-deeplinking-macos-win32",
"protocols": {
"name": "electron-deep-linking",
"schemes": ["myapp"]
},
"mac": {
"category": "public.app-category.Reference"
},
"win": {
}
}
}
main.js:
const {app, BrowserWindow} = require('electron')
// Module with utilities for working with file and directory paths.
const path = require('path')
// Module with utilities for URL resolution and parsing.
const url = require('url')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
// Deep linked url
let deeplinkingUrl
// Force Single Instance Application
const shouldQuit = app.makeSingleInstance((argv, workingDirectory) => {
// Someone tried to run a second instance, we should focus our window.
// Protocol handler for win32
// argv: An array of the second instance’s (command line / deep linked) arguments
if (process.platform == 'win32') {
// Keep only command line / deep linked arguments
deeplinkingUrl = argv.slice(1)
}
logEverywhere("app.makeSingleInstance# " + deeplinkingUrl)
if (win) {
if (win.isMinimized()) win.restore()
win.focus()
}
})
if (shouldQuit) {
app.quit()
return
}
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
// Open the DevTools.
mainWindow.webContents.openDevTools()
// Protocol handler for win32
if (process.platform == 'win32') {
// Keep only command line / deep linked arguments
deeplinkingUrl = process.argv.slice(1)
}
logEverywhere("createWindow# " + deeplinkingUrl)
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
}
// 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.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
}
})
// Define custom protocol handler.
// Deep linking works on packaged versions of the application!
app.setAsDefaultProtocolClient('myapp')
// Protocol handler for osx
app.on('open-url', function (event, url) {
event.preventDefault()
deeplinkingUrl = url
logEverywhere("open-url# " + deeplinkingUrl)
})
// Log both at dev console and at running node console instance
function logEverywhere(s) {
console.log(s)
if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.executeJavaScript(`console.log("${s}")`)
}
}
main.js code description:
At let deeplinkingUrl we keep the provided url.
On macOS you need to listen to the app.open-url event, while on Windows the url should be available in process.argv (in the main process).
On macOS platform this is captured at 'open-url' event, we set it with deeplinkingUrl = url! (See // Protocol handler for osx)
On win32 platform this is saved at process.argv together with other arguments. To get only the provided url, deeplinkingUrl = argv.slice(1). (See // Protocol handler for win32)
At app.makeSingleInstance method we check in which platform we are and we set deeplinkingUrl accordingly! If we are on win32 platform, the url is located at argv variable from callback, else on macOS should have already been set at 'open-url' event! (See // Force Single Instance Application)

You should be setting up the open-url event in the will-finish-launching callback as per the docs. I had similar weird behaviour with open-file until it was setup within the will-finish-launching callback.
You notice they've done it this way in the example you link to.
Although it mentions this under will-finish-launching, it should really mention this under the open-url and open-file docs too as its quite easy to miss.
app.on('will-finish-launching', () => {
// Protocol handler for osx
app.on('open-url', (event, url) => {
event.preventDefault();
log("open-url event: " + url)
})
});

Related

Cypress: Is cypress-plugin-snapshots supported in Cypress 10?

I am getting this error when I am trying to run any of my specs file. It was working before. Am I missing a step here? How do I mark the path as external?
I removed parts of images that contain sensitive information.
e2e.js
require('cypress-plugin-tab')
import './commands'
const dayjs = require('dayjs')
Cypress.dayjs = dayjs
import 'cypress-plugin-snapshots/commands';
(Edited)
I tried to reinstall the package again but I am now currently getting this error. When I tried to npm install --force crypto I am back with this error plus I have to do npm install path also. It's like an endless loop.
Edited againThis is the view from the terminal. How do I use "platform: 'node'" ??
(Edited again)
I took the default Cypress project, installed and ran cypress-plugin-snapshots but sadly it worked ok.
Here are the files
cypress.config.js
const { defineConfig } = require("cypress");
const { initPlugin } = require('cypress-plugin-snapshots/plugin');
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
initPlugin(on, config); // this is cypress/plugins/index.js in Cypress v9
return config;
},
excludeSpecPattern: [ // this is "ignoreTestFiles" in Cypress v9
"**/__snapshots__/*",
"**/__image_snapshots__/*"
],
"env": { // just pasted everything from the docs here
"cypress-plugin-snapshots": {
"autoCleanUp": false, // Automatically remove snapshots that are not used by test
"autopassNewSnapshots": true, // Automatically save & pass new/non-existing snapshots
"diffLines": 3, // How many lines to include in the diff modal
"excludeFields": [], // Array of fieldnames that should be excluded from snapshot
"ignoreExtraArrayItems": false, // Ignore if there are extra array items in result
"ignoreExtraFields": false, // Ignore extra fields that are not in `snapshot`
"normalizeJson": true, // Alphabetically sort keys in JSON
"prettier": true, // Enable `prettier` for formatting HTML before comparison
"imageConfig": {
"createDiffImage": true, // Should a "diff image" be created, can be disabled for performance
"resizeDevicePixelRatio": true,// Resize image to base resolution when Cypress is running on high DPI screen, `cypress run` always runs on base resolution
"threshold": 0.01, // Amount in pixels or percentage before snapshot image is invalid
"thresholdType": "percent" // Can be either "pixels" or "percent"
},
"screenshotConfig": { // See https://docs.cypress.io/api/commands/screenshot.html#Arguments
"blackout": [],
"capture": "fullPage",
"clip": null,
"disableTimersAndAnimations": true,
"log": false,
"scale": false,
"timeout": 30000
},
"serverEnabled": true, // Enable "update snapshot" server and button in diff modal
"serverHost": "localhost", // Hostname for "update snapshot server"
"serverPort": 2121, // Port number for "update snapshot server"
"updateSnapshots": false, // Automatically update snapshots, useful if you have lots of changes
"backgroundBlend": "difference" // background-blend-mode for diff image, useful to switch to "overlay"
}
}
},
});
cypress/support/e2e/js
import './commands'
import 'cypress-plugin-snapshots/commands';
Test
describe('empty spec', () => {
it('passes', () => {
cy.visit('https://example.cypress.io')
.then(() => {
cy.get('div').toMatchSnapshot(); // 1st run - logs "update snapshot"
// 2nd run - logs "snapshots match"
});
})
})

Websocket connection failing from Firefox extension

I have a Firefox extension which connects to a websocket server and sends a message. I packaged it with web-ext build, renamed the .zip to a .xpi. Last month I installed it in Firefox after setting xpinstall.signatures.required to false in about:config. I added the SSL key to the Firefox certificate manager. It worked for a month. Yesterday there was probably a Firefox update and now the extension is blocked becaue it is not signed. After some research, I found that the regular Firefox has not allowed unsigned extensions for a long time. It makes me wonder what version I had until yesterday; I am working on a Ubuntu 20.04 system I set up 2 months ago.
When the extension is loaded manually with about:debugging, the extension works as it did before (websocket creation is successful and a message is sent).
I read online that Firefox Developer edition allows unsigned extensions. But after following the exact same steps, I cannot get the extension to work with a .xpi or by temporarily loading the extension. It is the same error in both circumstances:
Firefox can’t establish a connection to the server at wss://localhost:9501/.
The extension has a manifest and a background script
manifest.json:
{
"description": "weblogging app",
"manifest_version": 2,
"name": "weblogger",
"version": "1.0",
"browser_specific_settings": {
"gecko": {
"id": "browser_logger#example.org",
"strict_min_version": "50.0"
}
},
"background": {
"scripts": ["background.js"]
},
"permissions": []
}
background.js:
var websocketArguments = 'wss://localhost:9501';
var connected = new Boolean(false);
var webSocket;
createWebsocket();
function onError(error)
{
console.log(`Error: ${error}`);
}
function createWebsocket()
{
webSocket = new WebSocket(websocketArguments);
webSocket.onerror = onWebSocketError;
webSocket.onopen = onWebSocketOpen;
}
function onWebSocketError(event)
{
console.log("WebSocket error observed:", event);
};
function onWebSocketOpen(event)
{
console.log("WebSocket open: ", webSocket.readyState);
webSocket.send("hello there");
connected = true;
};
Whatever the reason I could not get the extension to work when unsigned, it was just easier to get it signed.
https://addons.mozilla.org/en-CA/developers/

Cannot use import statement outside a module when running android app with firebase

Using nativescript-vue and can't make my app run after upgrading to latest version of nativescript.
"nativescript": {
"id": "xxx",
"tns-ios": {
"version": "6.5.1"
},
"tns-android": {
"version": "6.5.1"
}
},
App.js:
import Vue from "nativescript-vue";
import App from "./components/App";
const firebase = require('nativescript-plugin-firebase')
const {LocalNotifications} = require("nativescript-local-notifications")
firebase.init({
showNotifications: true,
showNotificationsWhenInForeground: true,
onMessageReceivedCallback: (message) => {
console.log('[Firebase] onMessageReceivedCallback:', {message});
LocalNotifications.schedule(
[{
id: 1,
title: message.data.title,
body: message.data.body,
silhouetteIcon: 'res://ic_app_icon',
thumbnail: "res://icon",
forceShowWhenInForeground: true,
notificationLed: true,
channel: i18n.t('messages_about_orders')
}])
.catch(error => console.log("doSchedule error: " + error));
}
}).then(
function () {
console.log("firebase.init done");
},
function (error) {
console.log("firebase.init error: " + error);
}
);
I run tns run android and right after I get Webpack build done in the console, I get these messages and a crash:
nativescript-plugin-firebase: /Users/butaminas/Local Sites/getfast.co.nz-courier-app/platforms/android/.pluginfirebaseinfo not found, forcing prepare!
nativescript-plugin-firebase: running release build or change in environment detected, forcing prepare!
Cannot use import statement outside a module
What could be the problem here? I have another, similar app and it is all working fine.
I figured it out. It was the problem with hooks folder in the project root directory.
I don't know why this happened but after removing the plugin (the hooks directory was not removed) I removed the hooks folder manually and then installed the plugin again - the problem disappeared.

Parse iOS Universal Links with Nativescript Angular?

Following the apple documentation and Branch's documentation here, I have set up a working universal link in my Nativescript Angular (iOS) app. But, how do I parse the link when the app opens?
For example, when someone opens the app from the link, I want to have my app read the link so it can go to the correct page of the app.
There is some helpful code in this answer, but I keep getting errors with it. This could be bc the code is written in vanilla JS and I am not translating it into Angular correctly. The use of "_extends" and "routeUrL" both cause errors for me.
And the Nativescript url-handler plugin does not seem to work without further code.
So, after setting up the universal link, and installing the nativescript url-handler plugin, I have entered the following in app.module.ts:
const Application = require("tns-core-modules/application");
import { handleOpenURL, AppURL } from 'nativescript-urlhandler';
declare var NSUserActivityTypeBrowsingWeb
if (Application.ios) {
const MyDelegate = (function (_super) {
_extends(MyDelegate, _super);
function MyDelegate() {
_super.apply(this, arguments);
}
MyDelegate.prototype.applicationContinueUserActivityRestorationHandler = function (application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
this.routeUrl(userActivity.webpageURL);
}
return true;
};
MyDelegate.ObjCProtocols = [UIApplicationDelegate];
return MyDelegate;
})(UIResponder);
Application.ios.delegate = MyDelegate;
}
...
export class AppModule {
ngOnInit(){
handleOpenURL((appURL: AppURL) => {
console.log('Got the following appURL = ' + appURL);
});
}
}
The trouble seems to be mostly with "_extends" and "_super.apply". For example, I get this error:
'NativeScript encountered a fatal error: TypeError: undefined is not an object (evaluating '_extends')
EDIT: Note that the nativescript-urlhandler plugin is no longer being updated. Does anyone know how to parse universal links with Nativescript?
I have figured out a method to get this working:
The general idea is to use the iOS App Delegate method: applicationContinueUserActivityRestorationHandler.
The syntax in the Nativescript documentation on app delegates did not work for me. You can view that documentation here.
This appears to work:
--once you have a universal link set up, following documentation like here, and now you want your app to read ("handle") the details of the link that was tapped to open the app:
EDIT: This code sample puts everything in one spot in app.module.ts. However, most of the time its better to move things out of app.module and into separate services. There is sample code for doing that in the discussion here. So the below has working code, but keep in mind it is better to put this code in a separate service.
app.module.ts
declare var UIResponder, UIApplicationDelegate
if (app.ios) {
app.ios.delegate = UIResponder.extend({
applicationContinueUserActivityRestorationHandler: function(application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
let tappedUniversalLink = userActivity.webpageURL
console.log('the universal link url was = ' + tappedUniversalLink)
}
return true;
}
},
{
name: "CustomAppDelegate",
protocols: [UIApplicationDelegate]
});
}
NOTE: to get the NSUserActivity/Application Delegate stuff to work with typescript, I also needed to download the tns-platforms-declarations plugin, and configure the app. So:
$ npm i tns-platforms-declarations
and
references.d.ts
/// <reference path="./node_modules/tns-platform-declarations/ios.d.ts" />
The above code works for me to be able to read the details of the tapped universal link when the link opens the app.
From there, you can determine what you want to do with that information. For example, if you want to navigate to a specific page of your app depending on the details of the universal link, then I have found this to work:
app.module.ts
import { ios, resumeEvent, on as applicationOn, run as applicationRun, ApplicationEventData } from "tns-core-modules/application";
import { Router } from "#angular/router";
let univeralLinkUrl = ''
let hasLinkBeenTapped = false
if (app.ios) {
//code from above, to get value of the universal link
applicationContinueUserActivityRestorationHandler: function(application, userActivity) {
if (userActivity.activityType === NSUserActivityTypeBrowsingWeb) {
hasLinkBeenTapped = true
universalLinkUrl = userActivity.webpageURL
}
return true;
},
{
name: "CustomAppDelegate",
protocols: [UIApplicationDelegate]
});
}
#ngModule({...})
export class AppModule {
constructor(private router: Router) {
applicationOn(resumeEvent, (args) => {
if (hasLinkBeenTapped === true){
hasLinkBeenTapped = false //set back to false bc if you don't app will save setting of true, and always assume from here out that the universal link has been tapped whenever the app opens
let pageToOpen = //parse universalLinkUrl to get the details of the page you want to go to
this.router.navigate(["pageToOpen"])
} else {
universalLinkUrl = '' //set back to blank
console.log('app is resuming, but universal Link has not been tapped')
}
})
}
}
You can use the nativescript-plugin-universal-links plugin to do just that.
It has support for dealing with an existing app delegate so if you do have another plugin that implements an app delegate, both of them will work.
Here's the usage example from the docs:
import { Component, OnInit } from "#angular/core";
import { registerUniversalLinkCallback } from "nativescript-plugin-universal-links";
#Component({
selector: "my-app",
template: "<page-router-outlet></page-router-outlet>"
})
export class AppComponent {
constructor() {}
ngOnInit() {
registerUniversalLinkCallback(ul => {
// use the router to navigate to the screen
});
}
}
And the callback will receive a ul (universal link) param that looks like this
{
"href": "https://www.example.com/blog?title=welcome",
"origin": "https://www.example.com",
"pathname": "/blog",
"query": {
"title": "welcome"
}
}
Disclaimer: I'm the author of the plugin.

Service-worker doesn't install only adds to home screen

I'm trying to install the pwa onto my mobile device. I only am able to add to home screen. Does anyone have any idea why this is happening?
To make your PWA installable you need to meet up the following requirments:
A web manifest,with the correct fields filled in.
The website to be served from a secure(HTTPS) domain.
An icon to represent the app on the device.
A service worker registered with a fetch eventhandler,to make the app work offline(this is only required by chrome for Android currently).
You must include your manifest file in section of your index.html,like this
<link rel="manifest" href="name.webmanifest">
Your manifest should contain the following fields,most of which are self explanatory.
{
"background_color": "purple",
"description": "Shows random fox pictures. Hey, at least it isn't cats.",
"display": "fullscreen",
"icons": [
{
"src": "icon/fox-icon.png",
"sizes": "192x192",
"type": "image/png"
}
],
"name": "Awesome fox pictures",
"short_name": "Foxes",
"start_url": "/pwa-examples/a2hs/index.html"
}
Now when the browser finds the manifest file with all requirements fulfilled,it will fire a beforeinstallprompt and accordingly you have to show the A2HS dialog.
NOTE:
Different browsers have different criteria for installation, or to trigger the beforeinstallprompt event.
Starting in Chrome 68 on Android (Stable in July 2018), Chrome will no longer show the add to home screen banner. If the site meets the add to home screen criteria, Chrome will show the mini-infobar.
For A2HS dialog:
Add a button in your document,to allow user to do the installation
<button class="add-button">Add to home screen</button>
Provide some styling
.add-button {
position: absolute;
top: 1px;
left: 1px;
}
Now in the JS file where you register your service worker add the following code
let deferredPrompt;
//reference to your install button
const addBtn = document.querySelector('.add-button');
//We hide the button initially because the PWA will not be available for
//install until it follows the A2HS criteria.
addBtn.style.display = 'none';
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent Chrome 67 and earlier from automatically showing the prompt
e.preventDefault();
// Stash the event so it can be triggered later.
deferredPrompt = e;
// Update UI to notify the user they can add to home screen
addBtn.style.display = 'block';
addBtn.addEventListener('click', (e) => {
// hide our user interface that shows our A2HS button
addBtn.style.display = 'none';
// Show the prompt
deferredPrompt.prompt();
// Wait for the user to respond to the prompt
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('User accepted the A2HS prompt');
} else {
console.log('User dismissed the A2HS prompt');
}
deferredPrompt = null;
});
});
});

Resources