66import dynamicProto from "@microsoft/dynamicproto-js" ;
77import {
88 BaseTelemetryPlugin , Extensions , IAppInsightsCore , IConfig , IConfigDefaults , IConfiguration , IPlugin , IProcessTelemetryContext ,
9- IProcessTelemetryUnloadContext , ITelemetryItem , ITelemetryUnloadState , _eInternalMessageId , _throwInternal , addPageHideEventListener ,
9+ IProcessTelemetryUnloadContext , ITelemetryItem , ITelemetryUnloadState , Undefined , _eInternalMessageId , _throwInternal , addPageHideEventListener ,
1010 addPageUnloadEventListener , arrForEach , createProcessTelemetryContext , createUniqueNamespace , eLoggingSeverity , getSetValue ,
11- mergeEvtNamespace , onConfigChange , removePageHideEventListener , removePageUnloadEventListener , setValue , utlCanUseSessionStorage ,
11+ mergeEvtNamespace , onConfigChange , removePageHideEventListener , removePageUnloadEventListener , safeGetLogger , setValue ,
1212 utlGetSessionStorage , utlSetSessionStorage
1313} from "@microsoft/applicationinsights-core-js" ;
1414import { IPromise , doAwaitResponse } from "@nevware21/ts-async" ;
15- import { ITimerHandler , isString , objDeepFreeze , scheduleTimeout } from "@nevware21/ts-utils" ;
15+ import { ITimerHandler , asString , fnCall , getNavigator , isString , objDeepFreeze , scheduleTimeout } from "@nevware21/ts-utils" ;
1616import { IOSPluginConfiguration } from "./DataModels" ;
1717
1818const defaultMaxTimeout = 200 ;
1919const strExt = "ext" ;
20+
2021interface platformVersionInterface {
2122 platform ?: string ,
2223 platformVersion ?: string
2324}
25+
2426interface UserAgentHighEntropyData {
2527 platformVersion : platformVersionInterface
2628}
27- interface ModernNavigator {
29+
30+ interface ModernNavigator extends Navigator {
2831 userAgentData ?: {
2932 getHighEntropyValues ?: ( fields : [ "platformVersion" ] ) => IPromise < UserAgentHighEntropyData > ;
3033 } ;
31- }
34+ }
35+
3236const defaultOSConfig : IConfigDefaults < IOSPluginConfiguration > = objDeepFreeze ( {
3337 maxTimeout : defaultMaxTimeout ,
3438 mergeOsNameVersion : undefined
@@ -41,28 +45,26 @@ interface IDelayedEvent {
4145
4246export class OsPlugin extends BaseTelemetryPlugin {
4347 public identifier = "OsPlugin" ;
44- public priority = 195 ;
48+ public priority = 195 ; // Note: we want this to run after the AnalyticsPlugin so that it correctly sets whether we are allowed to use session storage
4549 public version = "#version#" ;
4650
4751 constructor ( ) {
4852 super ( ) ;
4953 let _core : IAppInsightsCore ;
5054 let _ocConfig : IOSPluginConfiguration ;
51- let _getOSInProgress : boolean ;
52- let _getOSTimeout : ITimerHandler ;
53- let _maxTimeout : number ;
55+ let _getOSTimeout : ITimerHandler | null ;
5456
55- let _platformVersionResponse : platformVersionInterface ;
56- let _retrieveFullVersion : boolean ;
57+ let _fetchedFullVersion : boolean ;
5758 let _mergeOsNameVersion : boolean ;
5859
5960 let _eventQueue : IDelayedEvent [ ] ;
6061 let _evtNamespace : string | string [ ] ;
61- let _excludePageUnloadEvents : string [ ] ;
62+ let _excludePageUnloadEvents : string [ ] | null ;
63+ let _disableFlushOnUnload : boolean ;
64+ let _addedUnloadEvents : boolean ;
6265
63- let _os : string ;
64- let _osVer : number ;
65- let _firstAttempt : boolean ;
66+ let _os : string | undefined | null ;
67+ let _osVer : number | undefined | null ;
6668
6769 dynamicProto ( OsPlugin , this , ( _self , _base ) => {
6870
@@ -73,130 +75,192 @@ export class OsPlugin extends BaseTelemetryPlugin {
7375 _core = core ;
7476 super . initialize ( coreConfig , core , extensions ) ;
7577 let identifier = _self . identifier ;
78+
7679 _evtNamespace = mergeEvtNamespace ( createUniqueNamespace ( identifier ) , core . evtNamespace && core . evtNamespace ( ) ) ;
77- if ( utlCanUseSessionStorage ) {
78- try {
79- _platformVersionResponse = JSON . parse ( utlGetSessionStorage ( core . logger , "ai_osplugin" ) ) ;
80- } catch ( error ) {
81- // do nothing
82- }
83- }
84- if ( _platformVersionResponse ) {
85- _retrieveFullVersion = true ;
86- _osVer = parseInt ( _platformVersionResponse . platformVersion ) ;
87- _os = _platformVersionResponse . platform ;
88- }
80+ _fetchedFullVersion = _fetchCachedOSVersion ( coreConfig ) ;
81+
8982 _self . _addHook ( onConfigChange ( coreConfig , ( details ) => {
9083 let coreConfig = details . cfg ;
9184 let ctx = createProcessTelemetryContext ( null , coreConfig , core ) ;
85+
9286 _ocConfig = ctx . getExtCfg < IOSPluginConfiguration > ( identifier , defaultOSConfig ) ;
93- _maxTimeout = _ocConfig . maxTimeout ;
94- if ( _ocConfig . mergeOsNameVersion !== undefined ) {
87+
88+ if ( _ocConfig . mergeOsNameVersion !== undefined ) {
9589 _mergeOsNameVersion = _ocConfig . mergeOsNameVersion ;
9690 } else if ( core . getPlugin ( "Sender" ) . plugin ) {
9791 _mergeOsNameVersion = true ;
9892 } else {
9993 _mergeOsNameVersion = false ;
10094 }
101-
95+
10296 let excludePageUnloadEvents = coreConfig . disablePageUnloadEvents || [ ] ;
97+ let disableFlushOnUnload = coreConfig . disableFlushOnUnload || false ;
98+ let removeEvents = _excludePageUnloadEvents && _excludePageUnloadEvents !== excludePageUnloadEvents ;
99+
100+ if ( _disableFlushOnUnload !== disableFlushOnUnload ) {
101+ removeEvents = true ;
102+ }
103103
104- if ( _excludePageUnloadEvents && _excludePageUnloadEvents !== excludePageUnloadEvents ) {
105- removePageUnloadEventListener ( null , _evtNamespace ) ;
106- removePageHideEventListener ( null , _evtNamespace ) ;
104+ if ( removeEvents && _addedUnloadEvents ) {
105+ _removeUnloadHandlers ( ) ;
107106 _excludePageUnloadEvents = null ;
108107 }
109-
110- if ( ! _excludePageUnloadEvents ) {
111- // If page is closed release queue
112- addPageUnloadEventListener ( _doUnload , excludePageUnloadEvents , _evtNamespace ) ;
113- addPageHideEventListener ( _doUnload , excludePageUnloadEvents , _evtNamespace ) ;
108+
109+ if ( ! _excludePageUnloadEvents && ! disableFlushOnUnload ) {
110+ _addUnloadHandlers ( excludePageUnloadEvents ) ;
114111 }
112+
115113 _excludePageUnloadEvents = excludePageUnloadEvents ;
114+ _disableFlushOnUnload = disableFlushOnUnload ;
116115 } ) ) ;
117- function _doUnload ( ) {
118- _releaseEventQueue ( ) ;
116+
117+ // Automatically start retrieving OS version without waiting for the first telemetry event
118+ if ( ! _fetchedFullVersion ) {
119+ // Start Requesting OS version process
120+ _startRetrieveOsVersion ( _ocConfig . maxTimeout as number ) ;
119121 }
120122 } ;
121123
122124 _self . processTelemetry = ( event : ITelemetryItem , itemCtx ?: IProcessTelemetryContext ) => {
123125 itemCtx = _self . _getTelCtx ( itemCtx ) ;
124126
125- if ( ! _retrieveFullVersion && ! _getOSInProgress && _firstAttempt ) {
126- // Start Requesting OS version process
127- _getOSInProgress = true ;
128- startRetrieveOsVersion ( ) ;
129- _firstAttempt = false ;
130- }
131-
132- if ( _getOSInProgress ) {
127+ if ( _getOSTimeout ) {
128+ // We have a timer waiting for the OS version to be retrieved, queue the event
133129 _eventQueue . push ( {
134130 ctx : itemCtx ,
135131 item : event
136132 } ) ;
137133 } else {
138- updateTeleItemWithOs ( event ) ;
134+ _updateTeleItemWithOs ( event ) ;
139135 _self . processNext ( event , itemCtx ) ;
140136 }
141137 } ;
142138
143139 _self . _doTeardown = ( unloadCtx ?: IProcessTelemetryUnloadContext , unloadState ?: ITelemetryUnloadState ) => {
144140 _completeOsRetrieve ( ) ;
145- removePageUnloadEventListener ( null , _evtNamespace ) ;
146- removePageHideEventListener ( null , _evtNamespace ) ;
141+ _removeUnloadHandlers ( ) ;
142+
147143 // Just register to remove all events associated with this namespace
148144 _initDefaults ( ) ;
149145 } ;
150146
151-
147+ function _fetchCachedOSVersion ( coreConfig : IConfiguration & IConfig ) {
148+ let fetched = false ;
149+
150+ // Special case check for if the runtime doesn't include the AnalyticsPlugin
151+ if ( coreConfig . isStorageUseDisabled !== true ) {
152+ try {
153+ let platformVersionResponse : platformVersionInterface = JSON . parse ( utlGetSessionStorage ( safeGetLogger ( _core ) , "ai_osplugin" ) ) as platformVersionInterface ;
154+ if ( platformVersionResponse ) {
155+ _os = platformVersionResponse . platform ;
156+ if ( platformVersionResponse . platformVersion ) {
157+ let ver = parseInt ( platformVersionResponse . platformVersion ) ;
158+ if ( ! isNaN ( ver ) ) {
159+ _osVer = ver ;
160+ }
161+ }
162+
163+ fetched = ! ! ( _os && _osVer ) ;
164+ }
165+ } catch ( error ) {
166+ // do nothing
167+ }
168+ }
169+
170+ return fetched ;
171+ }
172+
173+ function _storeCachedOSVersion ( coreConfig : IConfiguration & IConfig ) {
174+ // Special case check for if the runtime doesn't include the AnalyticsPlugin
175+ if ( coreConfig . isStorageUseDisabled !== true ) {
176+ try {
177+ utlSetSessionStorage ( safeGetLogger ( _core ) , "ai_osplugin" , JSON . stringify ( { platform : _os , platformVersion : _osVer } ) ) ;
178+ } catch ( error ) {
179+ // do nothing
180+ }
181+ }
182+ }
183+
184+ function _addUnloadHandlers ( excludePageUnloadEvents ?: string [ ] ) {
185+ function _unloading ( ) {
186+ _releaseEventQueue ( ) ;
187+ _removeUnloadHandlers ( ) ;
188+ }
189+
190+ // Only try and add unload handlers if we haven't already fetched the OS version
191+ if ( ! _addedUnloadEvents && ! _fetchedFullVersion ) {
192+ // If page is closed release queue
193+ addPageUnloadEventListener ( _unloading , excludePageUnloadEvents , _evtNamespace ) ;
194+ addPageHideEventListener ( _unloading , excludePageUnloadEvents , _evtNamespace ) ;
195+ _addedUnloadEvents = true ;
196+ }
197+ }
198+
199+ function _removeUnloadHandlers ( ) {
200+ if ( _addedUnloadEvents ) {
201+ removePageUnloadEventListener ( null , _evtNamespace ) ;
202+ removePageHideEventListener ( null , _evtNamespace ) ;
203+ _addedUnloadEvents = false ;
204+ }
205+ }
206+
152207 /**
153208 * Wait for the response from the browser for the OS version and store info in the session storage
154209 */
155- function startRetrieveOsVersion ( ) {
156- // Timeout request if it takes more than 5 seconds (by default)
157-
158- _getOSTimeout = scheduleTimeout ( ( ) => {
159- _completeOsRetrieve ( ) ;
160- } , _maxTimeout ) ;
161-
162- if ( navigator . userAgent ) {
163- const getHighEntropyValues = ( navigator as ModernNavigator ) . userAgentData ?. getHighEntropyValues ;
164- if ( getHighEntropyValues ) {
165- doAwaitResponse ( ( navigator as ModernNavigator ) . userAgentData . getHighEntropyValues ( [ "platformVersion" ] ) , ( response :any ) => {
166- if ( ! response . rejected ) {
167- _platformVersionResponse = response . value ;
168- _retrieveFullVersion = true ;
169- if ( _platformVersionResponse . platformVersion && _platformVersionResponse . platform ) {
170- _os = _platformVersionResponse . platform ;
171- _osVer = parseInt ( _platformVersionResponse . platformVersion ) ;
172- if ( _os === "Windows" ) {
173- if ( _osVer == 0 ) {
174- _osVer = 8 ;
175- } else if ( _osVer < 13 ) {
176- _osVer = 10 ;
177- } else {
178- _osVer = 11 ;
210+ function _startRetrieveOsVersion ( maxTimeout : number ) {
211+ if ( _core && ! _getOSTimeout ) {
212+ let nav : ModernNavigator | undefined = getNavigator ( ) as ModernNavigator | undefined ;
213+ let userAgentData = ( nav || { } ) . userAgentData ;
214+ if ( userAgentData ) {
215+ const getHighEntropyValues = userAgentData . getHighEntropyValues ;
216+ if ( getHighEntropyValues ) {
217+ // Timeout request if it takes more than 200 milliseconds (by default)
218+ _getOSTimeout = scheduleTimeout ( ( ) => {
219+ _completeOsRetrieve ( ) ;
220+ } , maxTimeout ) ;
221+
222+ doAwaitResponse ( fnCall ( getHighEntropyValues , userAgentData , [ "platformVersion" ] ) , ( response : any ) => {
223+ // Always mark as fetched regardless of success or failure
224+ _fetchedFullVersion = true ;
225+ try {
226+ if ( ! response . rejected ) {
227+ let platformVersionResponse = response . value ;
228+ if ( platformVersionResponse . platformVersion && platformVersionResponse . platform ) {
229+ _os = platformVersionResponse . platform ;
230+ _osVer = parseInt ( platformVersionResponse . platformVersion ) ;
231+ if ( _os === "Windows" && ! isNaN ( _osVer ) ) {
232+ if ( _osVer == 0 ) {
233+ _osVer = 8 ;
234+ } else if ( _osVer < 13 ) {
235+ _osVer = 10 ;
236+ } else {
237+ _osVer = 11 ;
238+ }
239+ }
240+
241+ _storeCachedOSVersion ( ( _core || { } ) . config as IConfig ) ;
179242 }
243+ } else {
244+ _throwInternal ( safeGetLogger ( _core ) ,
245+ eLoggingSeverity . CRITICAL ,
246+ _eInternalMessageId . PluginException ,
247+ "Could not retrieve operating system: " + response . reason ) ;
180248 }
181- utlSetSessionStorage ( _core . logger , "ai_osplugin" , JSON . stringify ( { platform : _os , platformVersion : _osVer } ) ) ;
249+ } finally {
250+ _completeOsRetrieve ( ) ;
182251 }
183- } else {
184- _throwInternal ( _core . logger ,
185- eLoggingSeverity . CRITICAL ,
186- _eInternalMessageId . PluginException ,
187- "Could not retrieve operating system: " + response . reason ) ;
188- }
189- _completeOsRetrieve ( ) ;
190- } ) ;
252+ } ) ;
253+ }
191254 }
192255 }
193256 }
194257
195- function updateTeleItemWithOs ( event : ITelemetryItem ) {
196- if ( _retrieveFullVersion ) {
197- let extOS = getSetValue ( getSetValue ( event , strExt ) , Extensions . OSExt ) ;
258+ function _updateTeleItemWithOs ( event : ITelemetryItem ) {
259+ if ( _fetchedFullVersion && ( _os || _osVer ) ) {
260+ let extOS : any = getSetValue ( getSetValue ( event , strExt ) as any , Extensions . OSExt ) ;
198261 if ( _mergeOsNameVersion ) {
199- setValue ( extOS , "osVer" , _os + _osVer , isString ) ;
262+ let mergedOS = ( _os || "" ) + ( _osVer ? asString ( _osVer ) : "" ) ;
263+ setValue ( extOS , "osVer" , mergedOS , isString ) ;
200264 } else {
201265 setValue ( extOS , "osVer" , _osVer ) ;
202266 setValue ( extOS , "os" , _os , isString ) ;
@@ -210,8 +274,10 @@ export class OsPlugin extends BaseTelemetryPlugin {
210274 function _completeOsRetrieve ( ) {
211275 if ( _getOSTimeout ) {
212276 _getOSTimeout . cancel ( ) ;
277+ _getOSTimeout = null ;
213278 }
214- _getOSInProgress = false ;
279+
280+ _removeUnloadHandlers ( ) ;
215281 _releaseEventQueue ( ) ;
216282 }
217283
@@ -220,7 +286,7 @@ export class OsPlugin extends BaseTelemetryPlugin {
220286 */
221287 function _releaseEventQueue ( ) {
222288 arrForEach ( _eventQueue , ( evt ) => {
223- updateTeleItemWithOs ( evt . item ) ;
289+ _updateTeleItemWithOs ( evt . item ) ;
224290 _self . processNext ( evt . item , evt . ctx ) ;
225291 } ) ;
226292 _eventQueue = [ ] ;
@@ -229,17 +295,18 @@ export class OsPlugin extends BaseTelemetryPlugin {
229295 function _initDefaults ( ) {
230296 _core = null ;
231297 _ocConfig = null ;
232- _getOSInProgress = false ;
233298 _getOSTimeout = null ;
234- _maxTimeout = null ;
235- _retrieveFullVersion = false ;
236299 _eventQueue = [ ] ;
237- _firstAttempt = true ;
300+ _os = null ;
301+ _osVer = null ;
302+ _fetchedFullVersion = false ;
303+ _addedUnloadEvents = false ;
304+ _excludePageUnloadEvents = null ;
238305 }
239306
240307 // Special internal method to allow the DebugPlugin to hook embedded objects
241308 _self [ "_getDbgPlgTargets" ] = ( ) => {
242- return [ _platformVersionResponse , _eventQueue , _getOSInProgress ] ;
309+ return [ { platform : _os , platformVersion : _osVer } , _eventQueue , ! ! _getOSTimeout ] ;
243310 } ;
244311 } ) ;
245312 }
0 commit comments