Browser push notifications not responding after some time - google-chrome

I'm having a strange problem, and can't seem to find any solution for this. I subscribe users to my web push service and when they receive the notification I track when the notification displays, if its clicked or closed. But when i send notification with require interaction flag or persistent flags and leave it open around 1 min , it becomes disabled.
What I mean by disabled :
cant click on buttons
cant click on notification it self
only can click on X to close it
And then when the user closes notification or system closes it my service worker dose not register close event.
Also one of the problems i ran into on Firefox is that when users click on notification it only focuses on empty tab in Firefox.
Here is my service worker code :
self.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
data = event.data.json();
const title = data.title;
const options = data.options;
event.waitUntil(
self.registration.showNotification(title, options)
.then((notificationEvent)=>{
let payload = {
campaign_id: data.campaign_id,
subscriber_id:data.subscriber_id,
action:'display'
}
sendData(api_url+'touch',payload,'display');
})
);
});
self.addEventListener('notificationclick',function(event){
if(data.url){
console.log('Notification clicked !');
const clickedNotification = event.notification;
let promiseChain = null;
clickedNotification.close();
let payload = {
campaign_id: data.campaign_id,
subscriber_id:data.subscriber_id,
action:'click'
}
if(event.action === 'button1Link')
{
promiseChain = clients.openWindow(data.button1Link);
}else if(event.action === 'button2Link')
{
promiseChain = clients.openWindow(data.button2Link);
}else if(event.action === 'button3Link')
{
promiseChain = clients.openWindow(data.button3Link);
}else if(event.action === 'closeButton')
{
payload.action = 'close';
}else
{
promiseChain = clients.openWindow(data.url);
}
sendData(api_url+'touch',payload).then(()=>console.log('Stat
sent!'));
event.waitUntil(promiseChain);
}
})
self.addEventListener('notificationclose',function(event){
console.log('Notification closed !');
let payload = {
campaign_id: data.campaign_id,
subscriber_id:data.subscriber_id,
action:'close'
}
event.waitUntil(sendData(api_url+'touch',payload,'close'));
})
function sendData(url,payload)
{
let data = new FormData();
Object.keys(payload).map((field)=>{
data.append(field,payload[field]);
});
return fetch(api_url+'touch', {
method: 'POST',
body: data,
mode:'cors'
});
}
I could really use some advice and help, as I looked all over the web for reasons why is this happening and can't seem to find anything useful.
My payload structure looks like this :
array(7) {
["title"]=>
string(25) "Noise reducing headphones"
["url"]=>
string(76) "https://www.bestbuy.com/site/audio/headphones/abcat0204000.c?id=abcat0204000"
["campaign_id"]=>
string(2) "49"
["button1Link"]=>
string(120) "https://cdn.iconscout.com/public/images/icon/premium/png-512/headphones-phones-music-player-3b0242ebbd8df45e-512x512.png"
["button2Link"]=>
string(120) "https://cdn.iconscout.com/public/images/icon/premium/png-512/headphones-phones-music-player-3b0242ebbd8df45e-512x512.png"
["button3Link"]=>
string(120) "https://cdn.iconscout.com/public/images/icon/premium/png-512/headphones-phones-music-player-3b0242ebbd8df45e-512x512.png"
["options"]=>
array(7) {
["actions"]=>
array(3) {
[0]=>
array(2) {
["action"]=>
string(11) "button1Link"
["title"]=>
string(5) "Order"
}
[1]=>
array(2) {
["action"]=>
string(11) "button2Link"
["title"]=>
string(6) "Cancel"
}
[2]=>
array(2) {
["action"]=>
string(11) "button3Link"
["title"]=>
string(17) "Order on facebook"
}
}
["body"]=>
string(40) "Listen to your music without disturbance"
["image"]=>
string(56) "campaignimg1.jpg"
["icon"]=>
string(53) "image2.png"
["vibrate"]=>
array(1) {
[0]=>
string(0) ""
}
["silent"]=>
bool(false)
["requireInteraction"]=>
bool(false)
}
}

So I figured out what was going wrong with my service worker ...
First rebuilt my promises :
notificationclick listener now looks like this :
self.addEventListener('notificationclick',function(event){
const clickedNotification = event.notification;
if(clickedNotification.data.url){
let openWindowEvent = null;
clickedNotification.close();
let payload = {
campaign_id: clickedNotification.data.campaign_id,
subscriber_id:clickedNotification.data.subscriber_id,
action:'click'
}
if(event.action === 'button1Link')
{
openWindowEvent = clients.openWindow(clickedNotification.data.button1Link);
}else if(event.action === 'button2Link')
{
openWindowEvent = clients.openWindow(clickedNotification.data.button2Link);
}else if(event.action === 'button3Link')
{
openWindowEvent = clients.openWindow(clickedNotification.data.button3Link);
}else if(event.action === 'closeButton')
{
payload.action = 'close';
}else
{
openWindowEvent = clients.openWindow(clickedNotification.data.url);
}
payload = prepareData(payload);
const sendStat = fetch(api_url+'touch', {
method: 'POST',
body: payload,
mode:'cors'
});
const promiseChain = Promise.all([
openWindowEvent,
sendStat
]);
event.waitUntil(promiseChain);
}
})
Channing promises helped keep alive service worker long until all the tasks are done .
Also i rebuilt onclose event listener so now it looks like this :
self.addEventListener('notificationclose',function(event){
const closedNotification = event.notification;
console.log('Notification closed !');
let payload = {
campaign_id: closedNotification.data.campaign_id,
subscriber_id: closedNotification.data.subscriber_id,
action:'close'
}
payload = prepareData(payload);
const promiseChain = fetch(api_url+'touch', {
method: 'POST',
body: payload,
mode:'cors'
});
event.waitUntil(promiseChain);
})
And the main problem that disabled my notifications after some time was accessing data. I made a mistake trying to save data that I sent from server when the 'push' event triggered ... This was ok until browser decides to kill the service worker. Then you lose all the data stored in variables.
Solution:
Access data sent from server using variable 'event' passed in to all the event listeners :
self::addEventListener('notificationclick',function(event){});
From there you can access all the data sent with notification and use it as you want. This solved problem of not opening new tab with sent url and registering close event .

Related

How to pass user entered input on homepage card to a a card created from Drive onItemsSelectedTrigger?

I'm creating a google workspace addon that applies entered text to user selected templates with placeholders. My issue is after I enter the information on the card created by homepageTrigger, if I then select a file/folder on Drive, it creates the new card without any of the entered text. Likewise, if I select an item to enter information then select a new file/folder, the input doesn't carry over. This is a problem in the event that the user accidentally selects the wrong file, enters all the information, then attempts to select the correct file.
My first attempt at a solution was to create an object of currently entered information and pull the value from each key upon reloading.
//formatting for text input = {title : fieldname}
//both title and fieldname are required
let textFields = {
'Date': 'date',
'Name':'name',
'Street Address':'address1',
'City, State Zip':'address2',
'Phone Number':'phoneNum',
'SSN':'ssn',
'E-mail Address':'email',
}
//create object of empty placeholders for template
//pulled from textFields
let enteredInfo = {}
Object.values(textFields).forEach(fieldName => {
enteredInfo[fieldName] = '';
});
The object is created in a global scope, outside of any function and is updated with an onChange function for each text input box using the saveInput function
function saveInput(e) {
for (let [field, value] of Object.entries(e.formInput)) {
if (enteredInfo.hasOwnProperty(field) && field != 'date' && field != '') {
enteredInfo[field] = value
}
}
return CardService.newNavigation().updateCard(buildCard(e));
}
This has the unfortunate effect of having to update the card upon each input, which makes tabbing between input boxes impossible. Unfortunately, while this does work as intended for the current card, when selecting a new drive file, the enteredInfo object displays
enteredInfo[fieldName]:''
The second attempt, which would be the more ideal solution, was to pass the event object with e.formInput(this will be changed before deployment to e.commonEventObject.formInputs as the former is deprecated per documentation), and set the value for each input from that and update the original build card upon selecting an item. The code was as follows:
for (let [title, fieldName] of Object.entries(textFields)) {
if ([title] != 'Date') {
form.addWidget(CardService.newTextInput()
.setFieldName(fieldName)
.setTitle(title)
.setValue(e['formInput'][fieldName])
.setOnChangeAction(save));
}
}
The code together under my cardBuilder.gs page is:
let onDriveHomePageOpen = (e) => buildCard(e);
let onDriveItemsSelected = (e) => itemSelectedCard(e);
//formatting for text input = {title : fieldname}
//both title and fieldname are required
let textFields = {
'Date': 'date',
'Name':'name',
'Street Address':'address1',
'City, State Zip':'address2',
'Phone Number':'phoneNum',
'SSN':'ssn',
'E-mail Address':'email',
}
//create object of empty placeholders for template
//pulled from textFields
let enteredInfo = {}
Object.values(textFields).forEach(fieldName => {
enteredInfo[fieldName] = '';
});
function buildCard(e) {
let header = CardService.newCardHeader()
// .setTitle('Select folder or individual templates')
.setTitle(JSON.stringify(enteredInfo))
let apply = CardService.newAction()
.setFunctionName('applyInfo');
let applyButton = CardService.newTextButton()
.setText('Apply')
.setOnClickAction(apply);
let footer = CardService.newFixedFooter()
.setPrimaryButton(applyButton);
//change timezone to EDT
let currentDate = Date.now() - (5 * 3600000);
let date = CardService.newDatePicker()
.setFieldName('date')
.setValueInMsSinceEpoch(currentDate);
let form = CardService.newCardSection()
.setHeader('Enter Client Information')
.addWidget(date)
let save = CardService.newAction().setFunctionName('saveInput')
for (let [title, fieldName] of Object.entries(textFields)) {
if ([title] != 'Date') {
form.addWidget(CardService.newTextInput()
.setFieldName(fieldName)
.setTitle(title)
.setValue(enteredInfo[fieldName])
.setOnChangeAction(save));
}
}
let card = CardService.newCardBuilder()
.setFixedFooter(footer)
.setHeader(header)
.addSection(form)
.build();
return card;
}
function itemSelectedCard(e) {
let header = CardService.newCardSection();
header.setHeader('Selected Files')
.setCollapsible(true)
.setNumUncollapsibleWidgets(1)
let selectedFiles = []
e.drive.selectedItems.forEach (item => {
selectedFiles.push(item)
});
for (let i = 0; i < selectedFiles.length; i++) {
header.addWidget(CardService.newDecoratedText()
.setText(selectedFiles[i].title)
)}
let apply = CardService.newAction()
.setFunctionName('applyInfo');
let applyButton = CardService.newTextButton()
.setText('Apply')
.setOnClickAction(apply);
let footer = CardService.newFixedFooter()
.setPrimaryButton(applyButton);
//change timezone to EDT
let currentDate = Date.now() - (5 * 3600000);
let date = CardService.newDatePicker()
.setFieldName('date')
.setValueInMsSinceEpoch(currentDate);
let form = CardService.newCardSection()
.setHeader(JSON.stringify(e))
.addWidget(date)
let save = CardService.newAction().setFunctionName('saveInput')
for (let [title, fieldName] of Object.entries(textFields)) {
if ([title] != 'Date') {
form.addWidget(CardService.newTextInput()
.setFieldName(fieldName)
.setTitle(title)
.setValue(enteredInfo[fieldName])
.setOnChangeAction(save));
}
}
let card = CardService.newCardBuilder()
.setFixedFooter(footer)
.addSection(header)
.addSection(form)
.build();
return CardService.newNavigation().updateCard(buildCard(e))
}
Manifest File:
{
"timeZone": "America/New_York",
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"addOns": {
"common": {
"logoUrl": "https://storage.googleapis.com/jplogo/format_ink_highlighter_FILL1_wght200_GRAD0_opsz48.png",
"name": "Doc Merge",
"openLinkUrlPrefixes": [
"https://www.googleapis.com/auth/script.storage",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/drive.addons.metadata.readonly"
]
},
"drive": {
"homepageTrigger": {
"runFunction": "onDriveHomePageOpen",
"enabled": true
},
"onItemsSelectedTrigger": {
"runFunction": "onDriveItemsSelected"
}
},
"sheets": {
"homepageTrigger": {
"runFunction": "onSpreadsheet"
}
}
}
}
I'm a long time lurker but first time poster so I apologize if any of my SO formatting is off. I'd be happy to correct any issues. Thank you for any help you can provide!

Alexa Skill: Playing Audio results in "Sorry don't know that"

I am coding my very first alexa skill and am very excited! I am trying to make a skill so that when I say "ramranch", alexa will play Sunil Syal. I started this skill using the space fact picker blueprint. I have deleted that code and put in my own. The aws is connected and is working, however when I say "ramranch" in the testing console, Alexa responds with "Sorry, I don't know that". Their is no debug or error message.
/* eslint-disable func-names */
/* eslint-disable no-console */
const Alexa = require('ask-sdk');
const RamRanch = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'LaunchRequest'
|| (request.type === 'IntentRequest'
&& request.intent.name === 'handle');
},
handle(handlerInput) {
this.emit(':tell', "Let's enjoy a world's most beautiful composition, composed by the great, Sunil Syal, <audio src='https://my-apis.000webhostapp.com/audio/Romantic%20Solitude-Instrumental%20(Flute).mp3'/> Wow, That is amazing. Click the link on top right corner to listen to full song.");
}
}
const HelpHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest'
&& request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
return handlerInput.responseBuilder
.speak(HELP_MESSAGE)
.reprompt(HELP_REPROMPT)
.getResponse();
},
};
const ExitHandler = {
canHandle(handlerInput) {
const request = handlerInput.requestEnvelope.request;
return request.type === 'IntentRequest'
&& (request.intent.name === 'AMAZON.CancelIntent'
|| request.intent.name === 'AMAZON.StopIntent');
There is a strong likelihood that when you modified the blueprint skill you may have deleted the node modules. Here is a slightly modified version of your index.js code. Begin a node project npm init. next npm install ask-sdk. your project folder will now have node_modules, package-lock.json, and package.json add this index.js file.
const Alexa = require('ask-sdk-core');
const LaunchRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
},
handle(handlerInput) {
const speechText = 'Welcome to the Alexa Skills Kit, you can say what about ramranch';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard('Ramranch', speechText)
.getResponse();
}
};
const RamranchIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'ramranchIntent';
},
handle(handlerInput) {
const speechText = 'Lets enjoy a worlds most beautiful composition, composed by the great, Sunil Syal, <audio src="https://my-apis.000webhostapp.com/audio/Romantic%20Solitude-Instrumental%20(Flute).mp3"/> Wow, That is amazing. Click the link on top right corner to listen to full song.';
return handlerInput.responseBuilder
.speak(speechText)
.withSimpleCard('Ramranch', speechText)
.getResponse();
}
};
const HelpIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
},
handle(handlerInput) {
const speechText = 'You can say what about Ramranch!';
return handlerInput.responseBuilder
.speak(speechText)
.reprompt(speechText)
.withSimpleCard('Ramranch', speechText)
.getResponse();
}
};
const CancelAndStopIntentHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'IntentRequest'
&& (handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent'
|| handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent');
},
handle(handlerInput) {
const speechText = 'Goodbye!';
return handlerInput.responseBuilder
.speak(speechText)
.withSimpleCard('Ramranch', speechText)
.getResponse();
}
};
const SessionEndedRequestHandler = {
canHandle(handlerInput) {
return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
},
handle(handlerInput) {
//any cleanup logic goes here
return handlerInput.responseBuilder.getResponse();
}
};
const ErrorHandler = {
canHandle() {
return true;
},
handle(handlerInput, error) {
console.log(`Error handled: ${error.message}`);
return handlerInput.responseBuilder
.speak('Sorry, I can\'t understand the command. Please say again.')
.reprompt('Sorry, I can\'t understand the command. Please say again.')
.getResponse();
},
};
exports.handler = Alexa.SkillBuilders.custom()
.addRequestHandlers(
LaunchRequestHandler,
RamranchIntentHandler,
HelpIntentHandler,
CancelAndStopIntentHandler,
SessionEndedRequestHandler)
.addErrorHandlers(ErrorHandler)
.lambda();
Be sure to modify your Alexa skill developer model to include a ramranchIntent as I have not combined launch and primary intents as in your code. Lastly, zip this with the other previously mentioned files into your lambda function in AWS and add some intent phrases for triggering Ramranch, in my tests I used this skill JSON
{
"interactionModel": {
"languageModel": {
"invocationName": "ramranch player",
"intents": [
{
"name": "AMAZON.FallbackIntent",
"samples": []
},
{
"name": "AMAZON.CancelIntent",
"samples": []
},
{
"name": "AMAZON.HelpIntent",
"samples": []
},
{
"name": "AMAZON.StopIntent",
"samples": []
},
{
"name": "AMAZON.NavigateHomeIntent",
"samples": []
},
{
"name": "ramranchIntent",
"slots": [],
"samples": [
"what about ramranch",
"lets hear ramranch"
]
}
],
"types": []
}
}
}

How can i make delay in typescript?

first of all in using angular. Im having issue with login component which i want to show a spinner for some secs after the user enter the details and get response from server... right now everything happen fast and im not able to see the spinner for like 7 secs like i want to.. how can i delay the app so i can see it? here is my code:
component:
showSpinner: boolean = false;
showMySpinner() {
this.showSpinner = true;
setTimeout(() => {
this.showSpinner = false;
}, 7000);
}
constructor(private dataService: DataService, private auth: AuthService,
public matService: MatService) { }
loginUser(username, password, type): void {
switch (type.value) {
case "ADMIN": {
this.dataService.getLoginResponse(username.value, password.value,
type.value).subscribe(res => {
**this.showMySpinner();** HERE I USE THE FUNCTION BUT IT GOES RIGHT UNDER IT AND DONT LET IT SHOW
this.auth.updateUserType(type.value);
sessionStorage.setItem("type", "ADMIN");
sessionStorage.setItem("username", username.value);
this.matService.openSnackBar(this.loginSuccess, "success");
}, error => this.matService.openSnackBar(this.loginFailed, "error"));
break;
}
html:
<mat-spinner [style.display]="showSpinner ? 'block' : 'none'"></mat-spinner>
thanks
You can use rxjs delay.
You may add delay to getLoginResponse() method in DataService itself. And toggle the spinner accordingly in component file.
data.service.ts
import {delay} from 'rxjs/operators'
getLoginResponse() {
return this.http.get(url).pipe(delay(7000));
}
component.ts
switch (type.value) {
case "ADMIN": {
this.showSpinner = true; // start spinner just before asynchronous request is sent
this.dataService.getLoginResponse(username.value, password.value,
type.value).subscribe(res => {
this.showSpinner = false; // stop spinner
this.auth.updateUserType(type.value);
sessionStorage.setItem("type", "ADMIN");
sessionStorage.setItem("username", username.value);
this.matService.openSnackBar(this.loginSuccess, "success");
}, error => this.matService.openSnackBar(this.loginFailed, "error"));
break;
}
DEMO
You need to initiate your spinner before your call.
this.showSpinner = true;
this.dataService.getLoginResponse(username.value, password.value,
type.value).subscribe(res => {
this.showSpinner = false;
...
}, error => this.matService.openSnackBar(this.loginFailed, "error"));
break;

Angular 2 periodically pull real time data

I have developed an app which basically has admin and client portal running in separate ports and when an order is placed from client side, the admin dashboard should be able to get the new order shown.
Basically the view has to be refreshed to keep an updated UI.
For which i have referred the below link:
http://beyondscheme.com/2016/angular2-discussion-portal
Below is what i have tried.
order-issue.component.ts
ngOnInit() {
const user_id = {
user_ids: this.user_id
};
// To display the Pending Orders into the table
this.orderService.getAllOrders("Pending").subscribe(data => {
if (data.success && data.Allorders.length != 0) {
for (let i = 0; i < data.Allorders.length; i++) {
this.orderService
.getOrderItemsByNo(data.Allorders[i].orderNo)
.subscribe(subData => {
data.Allorders[i].orderItems = subData;
});
}
this.source = data.Allorders; //To display the data into smart table
this.refreshData(); //For real time refresh
} else {
this.flashMessage.show("No Pending Orders", {
cssClass: "alert-success",
timeout: 300000
});
}
});
private refreshData(): void {
this.commentsSubscription = this.orderService.getAllOrders("Pending").subscribe(data => {
this.data = data;
console.log(data); //able to see the new orders
this.subscribeToData();
});
private subscribeToData(): void {
this.timerSubscription = Observable.timer(5000).first().subscribe(() => this.refreshData());
}
My service(orderService) will get all the orders:
getAllOrders(status) {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http.post(`${BASE_URL}/orders/getAllOrdersWithItems`, { status: status }, { headers: headers })
.map(res => res.json());
}
Ok i am able to fix it with below change.
//Function which refreshes the data in real time without page refresh
private refreshData(): void {
this.commentsSubscription = this.orderService.getAllOrders("Pending").subscribe(data => {
this.source = data.Allorders; //Updated here! and it worked
console.log(this.source);
this.subscribeToData(); //On success we call subscribeToData()
});
}

native messaging host chrome-token-signing

I am trying to make an extension that will communicate with a native messaging host chrome-token-signing (https://github.com/open-eid/chrome-token-signing).
I have installed extension , but the EXE is not started. I have message log TEST: {"message":"Invalid argument","result":"invalid_argument"}
Do I need to do Something
I have installed the host in the registry like
HKEY_LOCAL_MACHINE\software\Google\Chrome\NativeMessagingHosts\ee.ria.esteid
and value C:\Users\dev\Desktop\chrome-token-signing\host-windows\ee.ria.esteid.json
The native application manifest.json:
{
"name": "ee.ria.esteid",
"description": "Give signatures with your eID on the web",
"path": "chrome-token-signing.exe",
"type": "stdio",
"allowed_origins": [
"chrome-extension://ckjefchnfjhjfedoccjbhjpbncimppeg/"
]
}
manifest.json of extension
{
"name": "Token signing",
"version": "0.0.24",
"minimum_chrome_version": "40.0",
"manifest_version": 2,
"description": "Use your eID smart card on the web",
"icons": {
"48": "icon48.png",
"128": "icon128.png"
},
"content_scripts": [{
"matches": ["*://*/*", "file:///*"],
"exclude_matches": ["*://www.overdrive.com/*"],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": true
}],
"background": {
"scripts": ["background.js"]
},
"permissions": ["nativeMessaging"],
"applications": {
"gecko": {
"id": "{443830f0-1fff-4f9a-aa1e-444bafbc7319}"
}
}
}
background.js
var NO_NATIVE_URL = "https://open-eid.github.io/chrome-token-signing/missing.html";
var HELLO_URL = "https://open-eid.github.io/chrome-token-signing/hello.html";
var DEVELOPER_URL = "https://github.com/open-eid/chrome-token- signing/wiki/DeveloperTips";
var NATIVE_HOST = "ee.ria.esteid";
var K_SRC = "src";
var K_ORIGIN = "origin";
var K_NONCE = "nonce";
var K_RESULT = "result";
var K_TAB = "tab";
var K_EXTENSION = "extension";
// Stores the longrunning ports per tab
// Used to route all request from a tab to the same host instance
var ports = {};
// Probed to false if host component is OK.
var missing = true;
console.log("Background page activated");
// XXX: probe test, because connectNative() does not allow to check the presence
// of native component for some reason
typeof chrome.runtime.onStartup !== 'undefined' && chrome.runtime.onStartup.addListener(function() {
// Also probed for in onInstalled()
_testNativeComponent().then(function(result) {
if (result === "ok") {
missing = false;
}
});
});
// Force kill of native process
// Becasue Port.disconnect() does not work
function _killPort(tab) {
if (tab in ports) {
console.log("KILL " + tab);
// Force killing with an empty message
ports[tab].postMessage({});
}
}
// Check if native implementation is OK resolves with "ok", "missing" or "forbidden"
function _testNativeComponent() {
return new Promise(function(resolve, reject) {
chrome.runtime.sendNativeMessage(NATIVE_HOST, {}, function(response) {
if (!response) {
console.log("TEST: ERROR " + JSON.stringify(chrome.runtime.lastError));
// Try to be smart and do some string matching
var permissions = "Access to the specified native messaging host is forbidden.";
var missing = "Specified native messaging host not found.";
if (chrome.runtime.lastError.message === permissions) {
resolve("forbidden")
} else if (chrome.runtime.lastError.message === missing) {
resolve("missing");
} else {
resolve("missing");
}
} else {
console.log("TEST: " + JSON.stringify(response));
if (response["result"] === "invalid_argument") {
resolve("ok");
} else {
resolve("missing"); // TODO: something better here
}
}
});
});
}
// When extension is installed, check for native component or direct to helping page
typeof chrome.runtime.onInstalled !== 'undefined' && chrome.runtime.onInstalled.addListener(function(details) {
if (details.reason === "install" || details.reason === "update") {
_testNativeComponent().then(function(result) {
var url = null;
if (result === "ok" && details.reason === "install") {
// Also set the flag, onStartup() shall be called only
// on next startup
missing = false;
// TODO: Add back HELLO page on install
// once there is a nice tutorial
url = HELLO_URL;
} else if (result === "forbidden") {
url = DEVELOPER_URL;
} else if (result === "missing"){
url = NO_NATIVE_URL;
}
if (url) {
chrome.tabs.create({'url': url + "?" + details.reason});
}
});
}
});
// When message is received from page send it to native
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(sender.id !== chrome.runtime.id && sender.extensionId !== chrome.runtime.id) {
console.log('WARNING: Ignoring message not from our extension');
// Not our extension, do nothing
return;
}
if (sender.tab) {
// Check if page is DONE and close the native component without doing anything else
if (request["type"] === "DONE") {
console.log("DONE " + sender.tab.id);
if (sender.tab.id in ports) {
// FIXME: would want to use Port.disconnect() here
_killPort(sender.tab.id);
}
} else {
request[K_TAB] = sender.tab.id;
if (missing) {
_testNativeComponent().then(function(result) {
if (result === "ok") {
missing = false;
_forward(request);
} else {
return _fail_with (request, "no_implementation");
}
});
} else {
// TODO: Check if the URL is in allowed list or not
// Either way forward to native currently
_forward(request);
}
}
}
});
// Send the message back to the originating tab
function _reply(tab, msg) {
msg[K_SRC] = "background.js";
msg[K_EXTENSION] = chrome.runtime.getManifest().version;
chrome.tabs.sendMessage(tab, msg);
}
// Fail an incoming message if the underlying implementation is not
// present
function _fail_with(msg, result) {
var resp = {};
resp[K_NONCE] = msg[K_NONCE];
resp[K_RESULT] = result;
_reply(msg[K_TAB], resp);
}
// Forward a message to the native component
function _forward(message) {
var tabid = message[K_TAB];
console.log("SEND " + tabid + ": " + JSON.stringify(message));
// Open a port if necessary
if(!ports[tabid]) {
// For some reason there does not seem to be a way to detect missing components from longrunning ports
// So we probe before opening a new port.
console.log("OPEN " + tabid + ": " + NATIVE_HOST);
// create a new port
var port = chrome.runtime.connectNative(NATIVE_HOST);
// XXX: does not indicate anything for some reason.
if (!port) {
console.log("OPEN ERROR: " + JSON.stringify(chrome.runtime.lastError));
}
port.onMessage.addListener(function(response) {
if (response) {
console.log("RECV "+tabid+": " + JSON.stringify(response));
_reply(tabid, response);
} else {
console.log("ERROR "+tabid+": " + JSON.stringify(chrome.runtime.lastError));
_fail_with(message, "technical_error");
}
});
port.onDisconnect.addListener(function() {
console.log("QUIT " + tabid);
delete ports[tabid];
// TODO: reject all pending promises for tab, if any
});
ports[tabid] = port;
ports[tabid].postMessage(message);
} else {
// Port already open
ports[tabid].postMessage(message);
}
}
The native app is started and it replies to you that the arguments you give it are invalid.
You need to check with native app documentation and see what arguments are valid for that particular app and use them in the messages you send it from the extension. Your request will look like:
chrome.runtime.sendNativeMessage(NATIVE_HOST, {text: "some_valid_argument"}, function(response){
........