I am currently using ApplicationInsights-JS in my progressive web app. It works in my react components as I can import what I need from the relevant npm packages.
In my service worker however, I can only import logic using importScripts.
I did manage to find a CDN for ApplicationInsights-JS on their Github page however it seems that in order to initialise app insights using this library you need to have access to window in order to store the appinsights, which you cannot do from a service worker.
I tried to use the web snippet approach since the CDN seemed to be
related to that particular library, but I can't use window and am not sure how else to implement this solution.
This is a copy paste of the suggested snippet to init the app insights object from: https://github.com/Microsoft/ApplicationInsights-JS
importScripts('https://az416426.vo.msecnd.net/beta/ai.2.min.js');
const sdkInstance = 'appInsightsSDK';
window[sdkInstance] = 'appInsights';
const aiName = window[sdkInstance];
const aisdk =
window[aiName] ||
(function(e) {
function n(e) {
i[e] = function() {
const n = arguments;
i.queue.push(function() {
i[e](...n);
});
};
}
let i = { config: e };
i.initialize = !0;
const a = document;
const t = window;
setTimeout(function() {
const n = a.createElement('script');
(n.src = e.url || 'https://az416426.vo.msecnd.net/next/ai.2.min.js'),
a.getElementsByTagName('script')[0].parentNode.appendChild(n);
});
try {
i.cookie = a.cookie;
} catch (e) {}
(i.queue = []), (i.version = 2);
for (
const r = [
'Event',
'PageView',
'Exception',
'Trace',
'DependencyData',
'Metric',
'PageViewPerformance'
];
r.length;
)
n(`track${r.pop()}`);
n('startTrackPage'), n('stopTrackPage');
const o = `Track${r[0]}`;
if (
(n(`start${o}`),
n(`stop${o}`),
!(
!0 === e.disableExceptionTracking ||
(e.extensionConfig &&
e.extensionConfig.ApplicationInsightsAnalytics &&
!0 ===
e.extensionConfig.ApplicationInsightsAnalytics
.disableExceptionTracking)
))
) {
n(`_${(r = 'onerror')}`);
const s = t[r];
(t[r] = function(e, n, a, t, o) {
const c = s && s(e, n, a, t, o);
return (
!0 !== c &&
i[`_${r}`]({
message: e,
url: n,
lineNumber: a,
columnNumber: t,
error: o
}),
c
);
}),
(e.autoExceptionInstrumented = !0);
}
return i;
})({ instrumentationKey: 'xxx-xxx-xxx-xxx-xxx' });
(window[aiName] = aisdk),
aisdk.queue && aisdk.queue.length === 0 && aisdk.trackPageView({});
I get window is not defined which is expected, but I'm not sure how else I can make use of this library from the service worker.
Has anyone else had a similar implementation in which they successfully logged telemetry using ApplicationInsights from a service worker?
I realised that I was over complicating this.
Since I only needed to track a custom event, and didn't need all the automated page tracking etc that appInsights does, I ended up doing a fetch from my service worker.
I just copied the header and body format from the requests that I made using my react pages.
The below successfully logged telemetry to my app insights dashboard:
fetch(url, {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify([
{
time: '2019-05-02T15:56:37.589Z',
iKey: 'INSTRUMENTATION_KEY',
name:
'Microsoft.ApplicationInsights.INSTRUMENTATION_KEY.Event',
tags: {
'ai.user.id': 'l6Tey',
'ai.session.id': 'TL+Ry',
'ai.device.id': 'browser',
'ai.device.type': 'Browser',
'ai.operation.id': 'HUfNE',
SampleRate: '100',
// eslint-disable-next-line no-script-url
'ai.internal.sdkVersion': 'javascript:2.0.0-rc4'
},
data: {
baseType: 'EventData',
baseData: {
ver: 2,
name: 'Testing manual event',
properties: {},
measurements: {}
}
}
}
])
})
.then(json)
.then(function(data) {
})
.catch(function(error) {
});
I've almost managed to use Microsoft Application Insights in our app's service worker.
The key parts are:
Using the lightweight version of appInsights (see this small remark at 4th step) with importScripts('https://az416426.vo.msecnd.net/next/aib.2.min.js').
Initialize an appInsights object:
appInsights = new Microsoft.AppInsights.AppInsights({ instrumentationKey: "[replace with your own key]" });
when track needed (during onpush event or onnotificationclick), go for appInsight.track({ eventItemFields }) then appInsights.flush().
I've said "almost" because the flush part seems to not working, I've got: "Sender was not initialized" internal error after enabling debugging.
I will publish here a working sample code if I successfully manage this issue.
References:
https://github.com/Azure-Samples/applicationinsights-web-sample1/blob/master/testlightsku.html
This response to the question: How to add analytics for Push notifications.
Using the Web SDK in a service worker is troublesome. The full version depends on a window object, while the basic SDK depends on Beacon or XmlHttpRequest for sending the messages (in file https://github.com/microsoft/ApplicationInsights-JS/blob/master/channels/applicationinsights-channel-js/src/Sender.ts):
if (!_self._senderConfig.isBeaconApiDisabled() && Util.IsBeaconApiSupported()) {
_self._sender = _beaconSender;
} else {
if (typeof XMLHttpRequest !== undefined) {
const xhr:any = getGlobalInst("XMLHttpRequest");
if(xhr) {
const testXhr = new xhr();
if ("withCredentials" in testXhr) {
_self._sender = _xhrSender;
_self._XMLHttpRequestSupported = true;
} else if (typeof XDomainRequest !== undefined) {
_self._sender = _xdrSender; // IE 8 and 9
}
}
}
}
At the moment Application Insights SDK does not seem to support service workers. Rajars solution seems to be the best option for now.
Update: There is an issue in the Github Repo about this: https://github.com/microsoft/ApplicationInsights-JS/issues/1436
A suggestion that works is by using the basic/lightweight version of Application Insights (as mentioned by Rajar) and adding a XMLHttpRequest polyfill (that uses the fetch api) before inititializing Application Insights. After that you can use the lightweight version.
An example can be found here: https://github.com/Pkiri/pwa-ai
I was trying to use AppInsightsSDK in E2E tests environment (pupeteer) and when I tried to log event or metric I got with "Sender was not initialized" error.
As #Pkiri mentioned one would need XMLHttpRequest polyfill to solve the issue. Although my scenario is not directly related to Service worker I wanted to mention that #Pkiri answer is not entirely true, because one can also use globalThis, self, window or global to get the same result according to SDK source code function getGlobalInst("XMLHttpRequest"); resolves to
function getGlobal() {
if (typeof globalThis !== strShimUndefined && globalThis) {
return globalThis;
}
if (typeof self !== strShimUndefined && self) {
return self;
}
if (typeof window !== strShimUndefined && window) {
return window;
}
if (typeof global !== strShimUndefined && global) {
return global;
}
return null;
}
And for my scenario this was a valid solution
const appInsights = new ApplicationInsights({
config: {
instrumentationKey: 'AppInsights_InstrumentationKey',
},
});
global.XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
global.appInsights = appInsights.loadAppInsights();
I have created following function in AngularJS
var enq_dt = new Date();
$.post("/api/EMSAPI/EnquiryDetails?enq_no="+o_enq_no, null, function (returnedData) {
enq_dt = returnedData["D_O_O"];
console.log("Loading Post Block");
console.log(enq_dt);
});
console.log("Loading General Block ");
console.log(enq_dt);
$scope.CurrentQuotation = {
EnquiryNo:o_enq_no,
EnquiryDate: enq_dt,
QuotationBy:"TEST"
};
I am getting following result in console window.
Loading General Block
2010-11-26T00:00:00
Loading Post Block
2010-12-12T00:00:00
I want to Load Post block first and after that I want to run General Block.
What I am missing (I am new to Angular) ?
Thanks in advance.
I suggest you Google the word "asynchronous". In JavaScript, things like HTTP requests are almost always asynchronous.
To get your general code to run after the post, call it with .then():
function generalCode() {
console.log("Loading General Block ");
console.log(enq_dt);
$scope.CurrentQuotation = {
EnquiryNo:o_enq_no,
EnquiryDate: enq_dt,
QuotationBy:"TEST"
};
}
var enq_dt = new Date();
$.post("/api/EMSAPI/EnquiryDetails?enq_no="+o_enq_no, null)
.then(function (returnedData) {
enq_dt = returnedData["D_O_O"];
console.log("Loading Post Block");
console.log(enq_dt);
})
.then(generalCode);
When i use my cordova app in the home page, i need to display message to user to ask him if he really want to exit app or not.
Than i need to exit app if user select "yes".
To do it, i use this code :
document.addEventListener('backbutton', function() {
$rootScope.back();
$rootScope.$apply();
}, false);
$rootScope.back = function(execute) {
var path = $location.$$path;
if (path !== '/') {
$location.path(path.split('/').slice(0, -1).join('/'));
} else {
$rootScope.exitApp();
}
}.bind(this);
$rootScope.exitApp = function() {
$rootScope.$emit('showPopover', {
message: $rootScope.LABELS.QUIT_APP,
confirmCallback: function() {
if (typeof navigator.app !== 'undefined') {
navigator.app.exitApp();
}
},
cancelCallback: doNothing
});
};
It's working in android and iOS, but not in Windows 10 app.
In W10, navigator.app is undefined.
I read that i'm supposed to suspend app and not exit it, so i tried this windows quirkswritten in cordova doc (https://cordova.apache.org/docs/en/latest/cordova/events/events.html#backbutton) :
$rootScope.exitApp = function() {
$rootScope.$emit('showPopover', {
message: $rootScope.LABELS.QUIT_APP,
confirmCallback: function() {
if (typeof navigator.app !== 'undefined') {
navigator.app.exitApp();
}
else if (platformUtils.isWindows()) {
throw new Error('Exit'); // This will suspend the app
}
},
cancelCallback: doNothing
});
};
throw new Error('Exit') is called and display error in logs, but app is not suspended.
Maybe because i'm in a angular app?
Does any one has an idea?
You could also use this:
if (device.platform.toLowerCase() === "windows") {
//this closes the app and leaves a message in the windows system eventlog if error object is provided
MSApp.terminateApp();
} else {
navigator.app.exitApp()
}
Problem is due to $emit used to display popover.
If i throw error on popover callback, this error is not returned to backbutton event callback.
But, on windows platform, we are not supposed to display popover to ask if user is sure to want to leave app, so if I add it to "back" function, it's working.
Final code used to Windows 10 app is :
document.addEventListener('backbutton', function() {
$rootScope.back();
$rootScope.$apply();
}, false);
$rootScope.back = function(execute) {
var path = $location.$$path;
if (path !== '/') {
$location.path(path.split('/').slice(0, -1).join('/'));
} else {
if (window.device.platform.toLowerCase() === 'windows') {
throw new Error('Exit'); // This will suspend the app
} else {
$rootScope.exitApp();
}
}
}.bind(this);
In my angular application, I am re-directing the user by conditinal. all are works fine other than firefox browser.
firefox browser considers this $window.location.href - as a server side request and my app trigger the error, because of unknown call. chrome,ie works fine.
here is my code :
fn.redirectToLogin = function() {
var site = fn.env.auth;
var clientId = fn.env.oauth.client_id;
var redirectUri = fn.env.oauth.redirect_uri;
var authorizePath = String( "authorization.oauth2?response_type=token&client_id=" );
var state = fn.env.state;
var URL = encodeURI(site + authorizePath + clientId + '&redirect_uri=&scope=&state=' + encodeURIComponent(JSON.stringify(state)));
$window.location.href = URL;//requesting to server!! throws error
console.log( 'fn.env.state::', fn.env.state );
}
if( !$rootScope.isUserLoggedIn && fn.env.state !== undefined ) {
console.log('going to call auto login!');
fn.redirectToLogin();
}
I believe the way to change URL in Angular is $location.url(URL). Inject $location in your controller and use it instead of directly setting value. Here you can read more about it.
I've got a pretty odd issue if I ask myself.
Currently I've got a function in my view which is something like this:
<h3 ng-click="{{ vm.open(item.info) }}"> {{ item.title }} </h3>
and a function in my controller looking like this:
open = (url) => {
console.log(url);
}
This function works as intended it writes out the slug of the item from the view. But the issue comes when I try to add my API service or a simple $http.get() call. It seems to break the entire function because if I add this to the function:
open = (url) => {
console.log(url);
result.getOpportunity(url).then(result => {
res = result.data;
var modal = res.title;
console.log(modal);
});
}
Nothing gets printed out in the console. not even the first console.log() that worked so pretty just seconds ago.
Can anyone tell me what is going on? I don't get any error in either console or terminal but something do seem to be broken somewhere.
Worth mentioning is that I'm using an yeoman generator called Angular fullstack which uses Babel.
If I look into my gulp serve:ed .controller.js file that when I use the function that works. the single console.log line my file contains this:
this.open = function (url) {
console.log(url);
};
and when I use the longer function which doesn't work this is in the ouputted controller.js:
on the line where the function that works was this is now: _initialiseProps.call(this); and later on this function:
var _initialiseProps = function _initialiseProps() {
var _this2 = this;
this.open = function (url) {
console.log(url);
_this2.result.getOpportunity(url).then(function (result) {
res = result.data;
var modal = res.title;
console.log(modal);
});
};
};
so Babel rewrites pretty much. but the most peculiar thing I noticed was that if I call _initialiseProps.call(this); explicit via the console I get returned undefined which do make some sense because the function _intiliaseProps() function does not contain a function called call()
but when I call _initialiseProps(this); from the console I get an error returned with this:
post.controller.js:29 Uncaught TypeError: Cannot set property '_checkURLforHTTP' of undefined(…)
Which is kinda funny or i dont know.. tearing my hair right now. Anyway, the _checkURLforHTTP function contains this in the code before babel had it's thing going:
_checkURLforHTTP = (img) => {
if (img.indexOf('http://') > -1 || img.indexOf('https://') > -1 ) {
return 'url("' + img + '")';
} else {
return 'url("http://HARDCODED-URL-HERE' + img + '")';
}
};
and the same function in the babel-processed file is the following:
var _initialiseProps = function _initialiseProps() {
var _this2 = this;
this._checkURLforHTTP = function (img) {
if (img.indexOf('http://') > -1 || img.indexOf('https://') > -1) {
return 'url("' + img + '")';
} else {
return 'url("https://HARDCODED-URL-HERE/' + img + '")';
}
};
};
Obviously it is something i'm not doing right, but i dont seem to understand what it is im doing wrong. I think I've managed to break it down into a click function in the view using $http.get();