PageSourceSearch

https://syracusecityschools.com/js/deviceDetector.js?v=20260923224145

js syracusecityschools.com collected 2026-09-24 09:12:50 UTC 17,690 bytes, 593 lines download raw bytes

1/**
2 * Device Detection Utility
3 *
4 * A modern device detection utility that follows the recommended approach of using
5 * User-Agent Client Hints as primary detection method. Only loads ua-parser-js from CDN
6 * when UA-CH is not available, making it efficient and future-proof.
7 * Handles iOS 17+ iPads that identify as desktop Safari.
8 *
9 * Detection Priority:
10 * 1. User-Agent Client Hints (UA-CH) - Modern browser API
11 * 2. ua-parser-js library (dynamically loaded only when UA-CH unavailable)
12 * 3. Feature detection (additional checks for special cases)
13 * 4. Basic UA string patterns (final fallback)
14 *
15 * Loading Strategy:
16 * - If UA-CH available: No external dependencies, immediate initialization
17 * - If UA-CH unavailable: Dynamically loads ua-parser-js from CDN as fallback
18 *
19 */
20
21(function(window) {
22    'use strict';
23
24    /**
25     * Device Detection Class
26     */
27    function DeviceDetector() {
28        this.parser = null;
29        this.cache = {};
30        this.initialized = false;
31
32        // Initialize UAParser as fallback option
33        this._initializeParser();
34    }
35
36    /**
37     * Initialize the UA parser (only if UA-CH is not available)
38     * @private
39     */
40    DeviceDetector.prototype._initializeParser = function() {
41        // Check if User-Agent Client Hints are available
42        if (this._isUACHAvailable()) {
43            // UA-CH is available, no need for ua-parser-js
44            this.parser = null;
45            this.initialized = true;
46        } else {
47            // UA-CH not available, try to load ua-parser-js as fallback
48            if (typeof UAParser !== 'undefined') {
49                // Already loaded
50                this.parser = new UAParser();
51                this.initialized = true;
52            } else {
53                // Dynamically load ua-parser-js
54                this._loadUAParser();
55            }
56        }
57    };
58
59    /**
60     * Dynamically load UAParser from CDN when UA-CH is not available
61     * @private
62     */
63    DeviceDetector.prototype._loadUAParser = function() {
64        var self = this;
65        var script = document.createElement('script');
66        script.src = 'https://cdn.smartsites.parentsquare.com/lib/uaparser/ua-parser.min.js';
67        script.crossOrigin = 'anonymous';
68        script.integrity = 'sha384-X/QJP4vk5ve2IvDaZK2ri7hmLKvVucgazjmRVmGCBiN+Y/cOI786DPPDe8ljOZw6';
69
70        script.onload = function() {
71            self.parser = new UAParser();
72            self.initialized = true;
73            self._clearCache();
74            // Dispatch ready event (with iOS Safari compatibility)
75            self._dispatchReadyEvent();
76        };
77
78        script.onerror = function() {
79            console.warn('DeviceDetector: Failed to load ua-parser-js, using basic detection only');
80            self.parser = null;
81            self.initialized = true;
82            self._dispatchReadyEvent();
83        };
84
85        document.head.appendChild(script);
86    };
87
88
89    /**
90     * Check if User-Agent Client Hints are available
91     * @returns {boolean}
92     * @private
93     */
94    DeviceDetector.prototype._isUACHAvailable = function() {
95        return !!(navigator.userAgentData && navigator.userAgentData.getHighEntropyValues);
96    };
97
98    /**
99     * Get device information using UA-CH
100     * @returns {Promise<Object>|null}
101     * @private
102     */
103    DeviceDetector.prototype._getUACHInfo = function() {
104        if (!this._isUACHAvailable()) {
105            return null;
106        }
107
108        return navigator.userAgentData.getHighEntropyValues([
109            'architecture',
110            'model',
111            'platform',
112            'platformVersion',
113            'fullVersionList'
114        ]);
115    };
116
117    /**
118     * Get device information object
119     * @returns {Object} Device information
120     */
121    DeviceDetector.prototype.getDeviceInfo = function() {
122        if (this.cache.deviceInfo) {
123            return this.cache.deviceInfo;
124        }
125
126        var deviceInfo = {
127            isMobile: this.isMobile(),
128            isTablet: this.isTablet(),
129            isDesktop: this.isDesktop(),
130            isIPad: this.isIPad(),
131            isTouchDevice: this.isTouchDevice(),
132            browser: this.getBrowserName(),
133            os: this.getOSName(),
134            userAgent: navigator.userAgent,
135            platform: navigator.platform
136        };
137
138        this.cache.deviceInfo = deviceInfo;
139        return deviceInfo;
140    };
141
142    /**
143     * Check if device is mobile (phone)
144     * @returns {boolean}
145     */
146    DeviceDetector.prototype.isMobile = function() {
147        if (this.cache.isMobile !== undefined) {
148            return this.cache.isMobile;
149        }
150
151        var result = false;
152
153        // Primary: User-Agent Client Hints (if available)
154        if (this._isUACHAvailable()) {
155            result = this._detectMobileFromUACH();
156        }
157        // Fallback: ua-parser-js (only if UA-CH not available)
158        else if (this.parser) {
159            var deviceType = this.parser.getDevice().type;
160            result = deviceType === 'mobile';
161        }
162
163        // Feature detection as additional check
164        if (!result) {
165            result = this._featureBasedMobileDetection();
166        }
167
168        // Final fallback: Basic UA string detection
169        if (!result) {
170            result = this._fallbackMobileDetection();
171        }
172
173        this.cache.isMobile = result;
174        return result;
175    };
176
177    /**
178     * Check if device is tablet
179     * @returns {boolean}
180     */
181    DeviceDetector.prototype.isTablet = function() {
182        if (this.cache.isTablet !== undefined) {
183            return this.cache.isTablet;
184        }
185
186        var result = false;
187
188        // Primary: User-Agent Client Hints (if available)
189        if (this._isUACHAvailable()) {
190            result = this._detectTabletFromUACH();
191        }
192        // Fallback: ua-parser-js (only if UA-CH not available)
193        else if (this.parser) {
194            var deviceType = this.parser.getDevice().type;
195            result = deviceType === 'tablet';
196        }
197
198        // Feature detection for special cases (iOS 17+ iPad issue)
199        if (!result && this.isIPad()) {
200            result = true;
201        }
202
203        // Additional feature-based detection
204        if (!result) {
205            result = this._featureBasedTabletDetection();
206        }
207
208        // Final fallback: Basic detection
209        if (!result) {
210            result = this._fallbackTabletDetection();
211        }
212
213        this.cache.isTablet = result;
214        return result;
215    };
216
217    /**
218     * Check if device is desktop
219     * @returns {boolean}
220     */
221    DeviceDetector.prototype.isDesktop = function() {
222        return !this.isMobile() && !this.isTablet();
223    };
224
225    /**
226     * Check if device is iPad (handles iOS 17+ desktop user agent)
227     * @returns {boolean}
228     */
229    DeviceDetector.prototype.isIPad = function() {
230        if (this.cache.isIPad !== undefined) {
231            return this.cache.isIPad;
232        }
233
234        var result = false;
235
236        // Check explicit iPad mentions
237        if (/iPad/.test(navigator.userAgent)) {
238            result = true;
239        }
240        // Check for iOS 17+ iPad (reports as Mac with touch)
241        else if (navigator.platform === 'MacIntel' && this.isTouchDevice()) {
242            result = this._isIPadByScreenSize();
243        }
244
245        this.cache.isIPad = result;
246        return result;
247    };
248
249    /**
250     * Check if device supports touch
251     * @returns {boolean}
252     */
253    DeviceDetector.prototype.isTouchDevice = function() {
254        if (this.cache.isTouchDevice !== undefined) {
255            return this.cache.isTouchDevice;
256        }
257
258        var result = (
259            'ontouchstart' in window ||
260            navigator.maxTouchPoints > 0 ||
261            navigator.msMaxTouchPoints > 0
262        );
263
264        this.cache.isTouchDevice = result;
265        return result;
266    };
267
268    /**
269     * Get browser name
270     * @returns {string}
271     */
272    DeviceDetector.prototype.getBrowserName = function() {
273        if (this.parser) {
274            return this.parser.getBrowser().name || 'Unknown';
275        }
276        return this._fallbackBrowserDetection();
277    };
278
279    /**
280     * Get OS name
281     * @returns {string}
282     */
283    DeviceDetector.prototype.getOSName = function() {
284        if (this.parser) {
285            return this.parser.getOS().name || 'Unknown';
286        }
287        return this._fallbackOSDetection();
288    };
289
290    /**
291     * Determine if should show PDF link (mobile devices get links)
292     * @returns {boolean}
293     */
294    DeviceDetector.prototype.shouldShowPDFLink = function() {
295        return this.isMobile() || this.isTablet();
296    };
297
298    /**
299     * Check if iPad by screen dimensions
300     * @returns {boolean}
301     * @private
302     */
303    DeviceDetector.prototype._isIPadByScreenSize = function() {
304        var screenWidth = window.screen.width;
305        var screenHeight = window.screen.height;
306
307        // Common iPad screen dimensions
308        var iPadSizes = [
309            [768, 1024],   // iPad Mini, Air, 9.7" Pro
310            [834, 1112],   // 10.5" Pro
311            [1024, 1366],  // 12.9" Pro
312            [820, 1180],   // 10.9" Air (4th gen)
313            [1668, 2388],  // 11" Pro (high DPI)
314            [2048, 2732]   // 12.9" Pro (high DPI)
315        ];
316
317        var currentSize = [
318            Math.min(screenWidth, screenHeight),
319            Math.max(screenWidth, screenHeight)
320        ];
321
322        for (var i = 0; i < iPadSizes.length; i++) {
323            var size = iPadSizes[i];
324            if (Math.abs(currentSize[0] - size[0]) <= 10 &&
325                Math.abs(currentSize[1] - size[1]) <= 10) {
326                return true;
327            }
328        }
329
330        return false;
331    };
332
333    /**
334     * Detect mobile using User-Agent Client Hints
335     * @returns {boolean}
336     * @private
337     */
338    DeviceDetector.prototype._detectMobileFromUACH = function() {
339        if (!navigator.userAgentData) {
340            return false;
341        }
342
343        // Check if it's mobile from basic UA-CH data
344        return navigator.userAgentData.mobile === true;
345    };
346
347    /**
348     * Detect tablet using User-Agent Client Hints
349     * @returns {boolean}
350     * @private
351     */
352    DeviceDetector.prototype._detectTabletFromUACH = function() {
353        if (!navigator.userAgentData) {
354            return false;
355        }
356
357        // UA-CH doesn't explicitly indicate tablets, so we need to combine with feature detection
358        // If it's not mobile according to UA-CH but has touch, it might be a tablet
359        var isMobileByUACH = navigator.userAgentData.mobile === true;
360        var hasTouch = this.isTouchDevice();
361
362        // Exclude desktop operating systems - touchscreen PCs are not tablets
363        var platform = navigator.userAgentData.platform || '';
364        if (this._isDesktopPlatform(platform)) {
365            return false;
366        }
367
368        // Tablet: not mobile by UA-CH but has touch capabilities
369        return !isMobileByUACH && hasTouch && this._featureBasedTabletDetection();
370    };
371
372    /**
373     * Feature-based mobile detection
374     * @returns {boolean}
375     * @private
376     */
377    DeviceDetector.prototype._featureBasedMobileDetection = function() {
378        // Check for explicit mobile indicators
379        if (/iPhone|iPod|Android.*Mobile|webOS|BlackBerry|Windows Phone/i.test(navigator.userAgent)) {
380            return true;
381        }
382
383        // Feature-based detection: small touch screen
384        if (this.isTouchDevice()) {
385            var screenWidth = window.screen.width;
386            var screenHeight = window.screen.height;
387            var minDimension = Math.min(screenWidth, screenHeight);
388
389            // Typical phone dimensions (up to ~6.5" phones)
390            return minDimension <= 480;
391        }
392
393        return false;
394    };
395
396    /**
397     * Feature-based tablet detection (primary method)
398     * @returns {boolean}
399     * @private
400     */
401    DeviceDetector.prototype._featureBasedTabletDetection = function() {
402        if (!this.isTouchDevice()) {
403            return false;
404        }
405
406        // Exclude desktop operating systems - touchscreen PCs are not tablets
407        if (this._isDesktopOS()) {
408            return false;
409        }
410
411        var screenWidth = window.screen.width;
412        var screenHeight = window.screen.height;
413        var minDimension = Math.min(screenWidth, screenHeight);
414        var maxDimension = Math.max(screenWidth, screenHeight);
415
416        // Exclude phones first
417        if (minDimension <= 480) {
418            return false;
419        }
420
421        // Typical tablet dimensions (7" to 13" tablets)
422        return minDimension >= 600 && maxDimension >= 800;
423    };
424
425    /**
426     * Fallback mobile detection
427     * @returns {boolean}
428     * @private
429     */
430    DeviceDetector.prototype._fallbackMobileDetection = function() {
431        var ua = navigator.userAgent;
432        return /iPhone|iPod|Android.*Mobile|webOS|BlackBerry|Windows Phone/i.test(ua);
433    };
434
435    /**
436     * Fallback tablet detection
437     * @returns {boolean}
438     * @private
439     */
440    DeviceDetector.prototype._fallbackTabletDetection = function() {
441        if (!this.isTouchDevice()) {
442            return false;
443        }
444
445        // Exclude desktop operating systems - touchscreen PCs are not tablets
446        if (this._isDesktopOS()) {
447            return false;
448        }
449
450        var screenWidth = window.screen.width;
451        var screenHeight = window.screen.height;
452        var minDimension = Math.min(screenWidth, screenHeight);
453        var maxDimension = Math.max(screenWidth, screenHeight);
454
455        // Typical tablet dimensions (7" to 13" tablets)
456        return minDimension >= 600 && maxDimension >= 800;
457    };
458
459    /**
460     * Fallback browser detection
461     * @returns {string}
462     * @private
463     */
464    DeviceDetector.prototype._fallbackBrowserDetection = function() {
465        var ua = navigator.userAgent;
466        if (/Safari/.test(ua) && !/Chrome/.test(ua)) return 'Safari';
467        if (/Chrome/.test(ua)) return 'Chrome';
468        if (/Firefox/.test(ua)) return 'Firefox';
469        if (/Edge/.test(ua)) return 'Edge';
470        return 'Unknown';
471    };
472
473    /**
474     * Fallback OS detection
475     * @returns {string}
476     * @private
477     */
478    DeviceDetector.prototype._fallbackOSDetection = function() {
479        var ua = navigator.userAgent;
480        if (/Windows/.test(ua)) return 'Windows';
481        if (/Mac OS X|macOS/.test(ua)) return 'Mac OS';
482        if (/Android/.test(ua)) return 'Android';
483        if (/iPhone|iPad|iPod/.test(ua)) return 'iOS';
484        if (/Linux/.test(ua)) return 'Linux';
485        return 'Unknown';
486    };
487
488    /**
489     * Check if platform string indicates a desktop OS (for UA-CH)
490     * @param {string} platform - The platform string from UA-CH
491     * @returns {boolean}
492     * @private
493     */
494    DeviceDetector.prototype._isDesktopPlatform = function(platform) {
495        var desktopPlatforms = ['Windows', 'Linux', 'Chrome OS'];
496        return desktopPlatforms.indexOf(platform) !== -1;
497    };
498
499    /**
500     * Check if the current OS is a desktop OS (using UA string fallback)
501     * Excludes macOS because iPads report as Mac, handled separately by isIPad()
502     * @returns {boolean}
503     * @private
504     */
505    DeviceDetector.prototype._isDesktopOS = function() {
506        var ua = navigator.userAgent;
507
508        // Windows or Linux are definitely desktop OSes
509        if (/Windows|Linux/.test(ua) && !/Android/.test(ua)) {
510            return true;
511        }
512
513        // Chrome OS is a desktop OS
514        if (/CrOS/.test(ua)) {
515            return true;
516        }
517
518        return false;
519    };
520
521    /**
522     * Clear cache
523     */
524    DeviceDetector.prototype.clearCache = function() {
525        this.cache = {};
526    };
527
528    /**
529     * Clear cache
530     * @private
531     */
532    DeviceDetector.prototype._clearCache = function() {
533        this.clearCache();
534    };
535
536    /**
537     * Dispatch ready event with iOS Safari compatibility
538     * @private
539     */
540    DeviceDetector.prototype._dispatchReadyEvent = function() {
541        var event;
542        try {
543            // Modern browsers
544            event = new CustomEvent('deviceDetectorReady');
545        } catch (e) {
546            // iOS Safari and older browsers fallback
547            event = document.createEvent('Event');
548            event.initEvent('deviceDetectorReady', true, true);
549        }
550        window.dispatchEvent(event);
551    };
552
553    /**
554     * Get debug information
555     * @returns {Object}
556     */
557    DeviceDetector.prototype.getDebugInfo = function() {
558        return {
559            initialized: this.initialized,
560            parserAvailable: !!this.parser,
561            userAgent: navigator.userAgent,
562            platform: navigator.platform,
563            maxTouchPoints: navigator.maxTouchPoints,
564            screenSize: {
565                width: window.screen.width,
566                height: window.screen.height
567            },
568            deviceInfo: this.getDeviceInfo(),
569            timestamp: new Date().toISOString()
570        };
571    };
572
573    // Create and export singleton instance
574    var deviceDetectorInstance = new DeviceDetector();
575
576    // Export factory function that follows the example pattern
577    window.getDeviceInfo = function() {
578        return deviceDetectorInstance.getDeviceInfo();
579    };
580
581    // Export the instance for backward compatibility
582    window.DeviceDetector = deviceDetectorInstance;
583
584    // Notify when ready
585    // If UA-CH is available or UAParser was already loaded, dispatch immediately
586    if (deviceDetectorInstance.initialized) {
587        setTimeout(function() {
588            deviceDetectorInstance._dispatchReadyEvent();
589        }, 0);
590    }
591    // Otherwise, the ready event will be dispatched after ua-parser-js loads
592
593})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.