Electron - How to create deep-linking on linux - linux

I have an ElectronJS project and I use the protocols (deep-link) in this one. It's work on MacOS and Windows but on Linux I can't understand how to create this protocol.
I have looked in the ElectronJS documentation as well as on the web for issues etc. but I can't figure out how to initialize protocol on Linux. All I want is to achieve, as I have succeeded on MacOS and Windows, a protocol to interact with the app in deep-link.
Code that works on MacOS and Windows :
// main.ts
// –– B ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
ProtocolUtils.setDefaultProtocolClient();
// eslint-disable-next-line default-case
switch (process.platform) {
case 'darwin':
ProtocolUtils.setProtocolHandlerOSX();
break;
case 'win32':
ProtocolUtils.setProtocolHandlerWin32();
break;
}
// –– E ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// protocol.ts
export abstract class ProtocolUtils {
/**
* #description Create default protocole for call this app.
* Ex : in your browser => myapp://test
*/
public static setDefaultProtocolClient(): void {
if (!app.isDefaultProtocolClient('myapp')) {
// Define custom protocol handler.
// Deep linking works on packaged versions of the application!
app.setAsDefaultProtocolClient('myapp');
}
}
/**
* #description Create logic (WIN32) for open url from protocol
*/
public static setProtocolHandlerWin32(): void {
// Force Single Instance Application on win32
const gotTheLock = app.requestSingleInstanceLock();
app.on('second-instance', (e: Electron.Event, argv: string[]) => {
// Someone tried to run a second instance, we should focus our window.
if (MainWindow.mainWindow) {
if (MainWindow.mainWindow.isMinimized()) MainWindow.mainWindow.restore();
MainWindow.mainWindow.focus();
} else {
MainWindow.openMainWindow(); // Open main windows
}
app.whenReady().then(() => {
MainWindow.mainWindow.loadURL(this._getDeepLinkUrlForWin32(argv)); // Load URL in WebApp
});
});
if (gotTheLock) {
app.whenReady().then(() => {
MainWindow.openMainWindow(); // Open main windows
MainWindow.mainWindow.loadURL(this._getDeepLinkUrlForWin32()); // Load URL in WebApp
});
} else {
app.quit();
}
}
/**
* #description Create logic (OSX) for open url from protocol
*/
public static setProtocolHandlerOSX(): void {
app.on('open-url', (event: Electron.Event, url: string) => {
event.preventDefault();
app.whenReady().then(() => {
MainWindow.openMainWindow(); // Open main windows
MainWindow.mainWindow.loadURL(this._getUrlToLoad(url)); // Load URL in WebApp
});
});
}
/**
* #description Format url to load in mainWindow
*/
private static _getUrlToLoad(url: string): string {
// Ex: url = myapp://deep-link/test?params1=paramValue
// Ex: Split for remove myapp:// and get deep-link/test?params1=paramValue
const urlSplitted = url.split('//');
// Generate URL to load in WebApp.
// Ex: file://path/index.html#deep-link/test?params1=paramValue
const urlToLoad = format({
pathname: Env.BUILDED_WEBAPP_INDEX_PATH,
protocol: 'file:',
slashes: true,
hash: `#${urlSplitted[1]}`,
});
return urlToLoad;
}
/**
* #description Resolve deep link url for windows from process argv
*/
private static _getDeepLinkUrlForWin32(argv?: string[]): string {
let url: string;
const newArgv: string[] = !isNil(argv) ? argv : process.argv;
// Protocol handler for win32
// argv: An array of the second instance’s (command line / deep linked) arguments
if (process.platform === 'win32') {
// Get url form precess.argv
newArgv.forEach((arg) => {
if (/myapp:\/\//.test(arg)) {
url = arg;
}
});
if (!isNil(url)) {
return this._getUrlToLoad(url); // Load URL in WebApp
} else if (!isNil(argv) && isNil(url)) {
throw new Error('URL is undefined');
}
}
}
}
I have no worries for macOS and windows, but on linux the protocol does not exist even with the line :
ProtocolUtils.setDefaultProtocolClient(); who is responsible for creating the myapp: // protocol...
When I run this command : xdg-open myapp://deep-link/test?toto=titi An error tells me that this protocol does not exist
If anyone has an example for me to configure on Linux or can just help me ?
Thanks

Ok i have found the solution !
First, we removed electron-forge and replaced it with electron-builder (cf doc).
Then after reading a lot of documentation for deep links on Linux, Examples of documentation :
Desktop file
Specification of desktop entry
Electron builder desktop file
And my solution is :
# electron-builder.yml
appId: com.myapp.myapp
productName: myapp
directories:
output: out
linux:
icon: src/assets/icons/app/icon#256x256.png
category: Utility
mimeTypes: [x-scheme-handler/myapp] # Define MimeType
desktop: # Define desktop elem
exec: myapp %u # Define Exec
target:
- target: deb
arch:
- x64
So i defined the MimeType with the name of my protocol here myapp which could give:
myapp://toto?foo=bar
And in my desktop file define Exec with myapp %u because %u => A single URL. Local files may either be passed as file: URLs or as file path. (cf doc)
And for finish in my main.ts and protocol.utils.ts:
// main.ts
// –– B ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
ProtocolUtils.setDefaultProtocolClient();
switch (process.platform) {
case 'darwin':
ProtocolUtils.setProtocolHandlerOSX();
break;
case 'linux':
case 'win32':
ProtocolUtils.setProtocolHandlerWindowsLinux();
break;
default:
throw new Error('Process platform is undefined');
}
// –– E ––– PROTOCOL HANDLER –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
// protocol.utils.ts
export abstract class ProtocolUtils {
public static setDefaultProtocolClient(): void {
if (!app.isDefaultProtocolClient('myapp')) {
// Define custom protocol handler.
// Deep linking works on packaged versions of the application!
app.setAsDefaultProtocolClient('myapp');
}
}
/**
* #description Create logic (WIN32 and Linux) for open url from protocol
*/
public static setProtocolHandlerWindowsLinux(): void {
// Force Single Instance Application
const gotTheLock = app.requestSingleInstanceLock();
app.on('second-instance', (e: Electron.Event, argv: string[]) => {
// Someone tried to run a second instance, we should focus our window.
if (MainWindow.mainWindow) {
if (MainWindow.mainWindow.isMinimized()) MainWindow.mainWindow.restore();
MainWindow.mainWindow.focus();
} else {
// Open main windows
MainWindow.openMainWindow();
}
app.whenReady().then(() => {
MainWindow.mainWindow.loadURL(this._getDeepLinkUrl(argv));
});
});
if (gotTheLock) {
app.whenReady().then(() => {
// Open main windows
MainWindow.openMainWindow();
MainWindow.mainWindow.loadURL(this._getDeepLinkUrl());
});
} else {
app.quit();
}
}
/**
* #description Create logic (OSX) for open url from protocol
*/
public static setProtocolHandlerOSX(): void {
app.on('open-url', (event: Electron.Event, url: string) => {
event.preventDefault();
app.whenReady().then(() => {
if (!isNil(url)) {
// Open main windows
MainWindow.openMainWindow();
MainWindow.mainWindow.loadURL(this._getUrlToLoad(url));
} else {
this._logInMainWindow({ s: 'URL is undefined', isError: true });
throw new Error('URL is undefined');
}
});
});
}
/**
* #description Format url to load in mainWindow
*/
private static _getUrlToLoad(url: string): string {
// Ex: url = myapp://deep-link/test?params1=paramValue
// Ex: Split for remove myapp:// and get deep-link/test?params1=paramValue
const splittedUrl = url.split('//');
// Generate URL to load in WebApp.
// Ex: file://path/index.html#deep-link/test?params1=paramValue
const urlToLoad = format({
pathname: Env.BUILDED_APP_INDEX_PATH,
protocol: 'file:',
slashes: true,
hash: `#${splittedUrl[1]}`,
});
return urlToLoad;
}
/**
* #description Resolve deep link url for Win32 or Linux from process argv
* #param argv: An array of the second instance’s (command line / deep linked) arguments
*/
private static _getDeepLinkUrl(argv?: string[]): string {
let url: string;
const newArgv: string[] = !isNil(argv) ? argv : process.argv;
// Protocol handler
if (process.platform === 'win32' || process.platform === 'linux') {
// Get url form precess.argv
newArgv.forEach((arg) => {
if (/myapp:\/\//.test(arg)) {
url = arg;
}
});
if (!isNil(url)) {
return this._getUrlToLoad(url);
} else if (!isNil(argv) && isNil(url)) {
this._logInMainWindow({ s: 'URL is undefined', isError: true });
throw new Error('URL is undefined');
}
}
}
And it's WORK :D

Related

google socialite laravel 8 return after authentication a blank screen

when i am tring to authenticate with google account it returns an empty string i don't know why so can u help me find this error i made the all steps requiring to do the authentication it returns callback with an empty string and my client_id and client secret are correct
here is my code google controller
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
use Exception;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
class GoogleController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function redirectToGoogle()
{
return Socialite::driver('google')->redirect();
}
/**
* Create a new controller instance.
*
* #return void
*/
public function handleGoogleCallback()
{
try {
$user = Socialite::driver('google')->user();
$finduser = User::where('social_id', $user->id)->first();
if($finduser){
Auth::login($finduser);
return redirect()->intended('dashboard');
}else{
$newUser = User::create([
'name' => $user->name,
'email' => $user->email,
'social_id'=> $user->id,
'social_type'=> 'google',
'password' => encrypt('123456dummy')
]);
Auth::login($newUser);
return redirect()->intended('dashboard');
}
} catch (Exception $e) {
dd($e->getMessage());
}
}
}
and my routes
<?php
use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return view('welcome');
});
Route::middleware([
'auth:sanctum',
config('jetstream.auth_session'),
'verified'
])->group(function () {
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
});
Route::get('auth/google', [App\Http\Controllers\GoogleController::class, 'redirectToGoogle']);
Route::get('auth/google/callback', [App\Http\Controllers\GoogleController::class, 'handleGoogleCallback']);
my config/services.php
'google' => [
'client_id' => '982061662199-bns94j425f1cgq7p8b0eo4lctjn83e4e.apps.googleusercontent.com',
'client_secret' => 'GOCSPX-9AFn2bhebi2yPUpCU8_LIzfb',
'redirect' => 'http://localhost:8000/auth/google/callback',
],

Node.JS dns.reverse multiple nameservers

I have an issue with my Node.JS application.
For some reason, dns.reverse is only working with the first nameserver specified and ignoring the second one.
I have the following piece of code:
import Resolver from '~/classes/resolver';
Resolver.addNameServer('192.168.2.1', '192.168.1.254').boot();
Resolver.hostnameFromIP('192.168.1.30').then((hostname: string) => {
console.log(hostname);
}).catch((error) => {
console.log(error); // We are going to end up here as the 192.168.2.1 is the first and only nameserver which will be queried
});
And here is Resolver class:
import q, { Deferred } from 'q';
import dns from 'dns';
import { ResolverInterface } from '~/classes/resolver/interfaces';
/**
* Resolver class
*/
class Resolver implements ResolverInterface {
/**
* List of nameservers used for hostname resolution
* #type { string[] }
*/
public nameservers: string[] = [];
/**
* Add nameserver for resolver to use
* #param { string } nameserver
* #return { Resolver }
*/
public addNameServer(...nameserver: string[]) : Resolver {
this.nameservers.push(...nameserver);
return this;
}
/**
* Initialize resolver
* #return { void }
*/
public boot() : void {
if (this.nameservers.length === 0) {
this.initializeWithDefaultNameServers();
}
dns.setServers(this.nameservers);
}
/**
* Resolve hostname from the IP address
* #param { string } ip
* #return { q.Promise<string> }
*/
public hostnameFromIP(ip: string) : q.Promise<string> {
const deferred: Deferred<any> = q.defer();
dns.reverse(ip, (error: NodeJS.ErrnoException | null, hostnames: string[]) => {
const defaultErrorMessage: string = 'Unable to resolve hostname';
if (error) {
return deferred.reject(defaultErrorMessage);
}
let hostname: string = hostnames.length === 0 ? defaultErrorMessage : hostnames[0];
hostname = hostname.replace('.localdomain', '').trim();
deferred.resolve(hostname);
});
return deferred.promise as q.Promise<string>;
}
/**
* Initialize resolver with default nameservers
* #return { void }
* #private
*/
private initializeWithDefaultNameServers() : void {
const nameservers: string[] = [
'192.168.0.1',
'192.168.1.1',
'192.168.2.1',
];
nameservers.forEach((nameserver: string) : void => {
this.nameservers.push(nameserver);
});
}
}
export default new Resolver;
Expected behavior:
Application should go through all specified nameservers to resolve the hostname for specified IP address
Actual behavior:
Depending on which nameserver is first, only that nameserver will be queried for the hostname.
If 192.168.2.1 is first, i can query data for 192.168.2.10, but i cannot do that for 192.168.1.30.
If 192.168.1.254 is first, i can query data for 192.168.1.30, but i cannot do that for 192.168.2.10.
Is there a way to use all specified nameservers while doing reverse hostname lookup using dns.reverse in Node.JS?
Thanks for help to Jorge Fuentes González, here is the version i've ended up using, at least it works for 2 nameservers.
/**
* Resolve hostname from IP address
* #param { string } ip
* #return { Promise<string> }
*/
public async hostnameFromIP(ip: string) : Promise<string> {
return await this.resolveForHostnameFromIP(ip)
.then((hostname: string): string => hostname)
.catch(async (error: string): Promise<string> => {
const indexOf: number = this.nameservers.indexOf(this.currentNameServer);
const newNameserverIndex: number = indexOf + 1;
if (newNameserverIndex <= this.nameservers.length - 1) {
this.currentNameServer = this.nameservers[indexOf + 1];
this.setCurrentNameServerValue();
return await this.hostnameFromIP(ip).then((hostname: string): string => hostname);
}
return error;
});
}
/**
* Helper method to resolve hostname from the IP address
* #param { string } ip
* #return { q.Promise<string> }
*/
private resolveForHostnameFromIP(ip: string) : q.Promise<string> {
const deferred: Deferred<any> = q.defer();
this.resolver.reverse(ip, (error: NodeJS.ErrnoException | null, hostnames: string[]) => {
const defaultErrorMessage: string = 'Unable to resolve hostname';
if (error) {
return deferred.reject(defaultErrorMessage);
} else {
let hostname: string = hostnames.length === 0 ? defaultErrorMessage : hostnames[0];
hostname = hostname.replace('.localdomain', '').trim();
deferred.resolve(hostname);
}
});
return deferred.promise as q.Promise<string>;
}
/**
* Update resolver configuration with current name server
* #return { void }
* #private
*/
private setCurrentNameServerValue() : void {
this.resolver.setServers([this.currentNameServer]);
};
Sorry, got the question wrong so this part is not important. Proper answer below. Btw keeping it here just in case someone has a problem like this:
That's not how DNS server lists work in almost any platform. If you look in your system network settings (Windows, Mac or Linux), surely you will see 2 nameservers (as is the default amount all ISPs provide). That's because there is a main server and a fallback server if the main one fails. You system won't return 2 different IPs when you are browsing Internet. That's weird. What your browser want is just an IP to connect to, not a list of IPs. NodeJS works the same. The rest of nameservers are just fallback ones just in case the main one fails.
Now the proper part:
When a nameserver replies with NOTFOUND, nodejs will not continue to the next nameserver, as is actually a working nameserver. Fallback nameservers are only used when the nameserver fails (with a weird error/response or a timeout. Documentation link below with this info).
To achieve what you want you need to set your nameserver, make your query, wait for the response, set the next nameserver, make your query, etc... with your current code must be an easy task. Looks pretty.
Be careful as you must not change the nameservers when a DNS query is in progress as you can read in the documentation.

URL to code in node.js applications

I see they use this kind of code to call restful URLs.
Let's say we have /users/{userId}/tasks to create task for a user.
To call this they create another class instead of calling request directly as shown below:
MyAPP.prototype.users = function (userId) {
return {
tasks: function (taskId) {
return this.usersTasks(userId, taskId);
}
}
}
MyAPP.prototype.usersTasks = function (userId, taskId) {
return {
create: function (task, cb) {
make request POST call
}
}
}
Then we can call this as myapp.users('123').tasks().create(task, cb);
What is this kind of coding called and is there any way to automatically generate the code from the URL structure itself?
That is a way of making classes, but I suggest you look into ES6 classes
Defining a class :
class MyAPP {
//:called when created
constructor(name) {
this.name = name;
console.log("[created] MyAPP :",name);
//(a in memory database stored in MyAPP : for example purpose)
this.DB = {'user000':{'tasks':{'task000':'do pizza'},{'task001':'code some magik'}}}
}
//: Get specific taskID for userID
getTask(userID, taskID) {
console.log("[get task]",taskID,"[from user]",userID)
return (this.DB[userID][taskID])
}
//: Get all tasks for userID
allTasks(userID) {
console.log("[get all tasks from user]",userID)
return (this.DB[userID].tasks)
}
//: Create a taskID with taskContent for userID
newTask(userID, taskID, taskContent) {
this.DB[userID].tasks[taskID] = taskContent
}
}
Creating a MyAPP instance :
var myapp = new MyAPP('Pizza API'); //creates a MyAPP with a name
And then (maybe I got your question wrong) using express you would make a server and listen for requests (GET, POST, PUT, ...)
app.get("/APIv1/:userID/:actionID", function(req, res) {
switch(req.params.actionID){
case 'all':
res.send(myapp.allTasks(req.params.userID));
break
default :
res.send("The "+myapp.name+" doesn't support that (yet)")
break
}
});

How to define different context menus for different objects in autodesk forge

I want to define different context menus for different objects in forge viewer,this is my code
viewer.addEventListener(Autodesk.Viewing.AGGREGATE_SELECTION_CHANGED_EVENT,function(e){
if(viewer.getSelection().length==0){return;}
var selectId=viewer.getSelection()[0];
viewer.search("Cabinet",function(ids){
if(ids.indexOf(selectId)!=-1){
viewer.registerContextMenuCallback('CabinetMsg', function (menu, status) {
if (status.hasSelected) {
menu.push({
title: "CabinetMsg",
target: function () {
openLayer('CabinetMsg','954','775','CabinetMsg.html')
}
});
}
});
}else{
viewer.registerContextMenuCallback('CabinetMsg', function (menu, status) {
if (status.hasSelected) {
menu.forEach(function(el,index){
if(el.title=="CabinetMsg"){
menu.splice(menu.indexOf(index),1)
}
})
}
});
}
})
});
But push elements to the array is always later than the context menus show. My custom context menu is always show when I select another object. What I can do?
The codes you provided will create 2 new sub items to the context menu. Here is a way for this case, i.e. you have to write your own ViewerObjectContextMenu. In addition, you need do hitTest in ViewerObjectContextMenu.buildMenu to get dbId selected by the mouse right clicking. Here is the example for you:
class MyContextMenu extends Autodesk.Viewing.Extensions.ViewerObjectContextMenu {
constructor( viewer ) {
super( viewer );
}
isCabinet( dbId ) {
// Your logic for determining if selected element is cabinet or not.
return false;
}
buildMenu( event, status ) {
const menu = super.buildMenu( event, status );
const viewport = this.viewer.container.getBoundingClientRect();
const canvasX = event.clientX - viewport.left;
const canvasY = event.clientY - viewport.top;
const result = that.viewer.impl.hitTest(canvasX, canvasY, false);
if( !result || !result.dbId ) return menu;
if( status.hasSelected && this.isCabinet( result.dbId ) ) {
menu.push({
title: 'CabinetMsg',
target: function () {
openLayer( 'CabinetMsg', '954', '775', 'CabinetMsg.html' );
}
});
}
return menu;
}
}
After this, you could write an extension to replace default viewer context menu with your own menu. Here also is the example:
class MyContextMenuExtension extends Autodesk.Viewing.Extension {
constructor( viewer, options ) {
super( viewer, options );
}
load() {
this.viewer.setContextMenu( new MyContextMenu( this.viewer ) );
return true;
}
unload() {
this.viewer.setContextMenu( new Autodesk.Viewing.Extensions.ViewerObjectContextMenu( this.viewer ) );
return true;
}
}
Hope this help.

Dart web audio noteOn internal error

I'm trying to get dart to play an audio, but everytime I try to play the Audio I get the following error:
Internal error: 'dart:_blink': error: line 248: native function 'AudioBufferSourceNode_noteOn_Callback_RESOLVER_STRING_1_double' (2 arguments) cannot be found
Native_AudioBufferSourceNode_noteOn_Callback(mthis, when) native "AudioBufferSourceNode_noteOn_Callback_RESOLVER_STRING_1_double";
This is the my Audio class:
class Audio {
static List<Audio> _loadQueue = new List<Audio>();
String _url;
bool loaded = false;
AudioBufferSourceNode _source;
Audio(this._url) {
if (audioContext == null) {
_loadQueue.add(this);
}
else {
load();
}
}
void load() {
print("Loading sound: " + _url);
HttpRequest req = new HttpRequest();
req.open("GET", _url);
req.responseType = "arraybuffer";
req.onLoad.listen((e) {
_source = audioContext.createBufferSource();
print("Found sound: " + _url + ". Decoding...");
audioContext.decodeAudioData(req.response).then((buffer) {
_source.buffer = buffer;
_source.connectNode(gainNode);
_source.connectNode(audioContext.destination);
loaded = true;
});
});
req.send();
}
void play() {
if (!loaded) { return; }
_source.noteOn(0);
}
void stop() {
if (!loaded) return;
_source.noteOff(0);
}
static void loadAll() {
_loadQueue.forEach((e) {e.load();});
}
}
The audio context and gain node is created in another class like this:
audioContext = new AudioContext();
gainNode = audioContext.createGain();
gainNode.connectNode(audioContext.destination);
Audio.loadAll();
I don't know what the problem is, especially since it says it's internal, and that it's missing arguments, but the noteOn function only takes one argument.
I was facing the same issue using dart 1.6. It seems noteOn has been replaced by start in the latest spec/implementation.
The AudioBufferSourceNode Interface (http://webaudio.github.io/web-audio-api/#the-audiobuffersourcenode-interface)
Porting notes (https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API/Porting_webkitAudioContext_code_to_standards_based_AudioContext#Changes_to_methods_used_to_start.2Fstop_AudioBufferSourceNode_and_OscillatorNode)

Resources