Last active
August 4, 2023 19:01
-
-
Save amcgregor/0c79746b8391da346c7590ddac6b1881 to your computer and use it in GitHub Desktop.
Python-like prototype additions, polyfills, and server-backed client-side behaviour.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// console.* Polyfill and, in production, no-op replacement. | |
(function() { | |
// An inspired polyfill for standard web browser user agent console interactions. | |
// | |
// This serves several purposes: | |
// | |
// 1. Add standardized methods missing from the local implementation to prevent code calling these from producing exceptions which halt JavaScript execution. | |
// 2. Direct certain logging actions at a back-end server collection endpoint, for archival, analytics, and diagnostics. | |
// 3. Squelch (mute or silence) "noisier" console endpoints to prevent user agents from exposing diagnostic information to end-users. | |
// Determine if we are running in "local development" or not based on hostname and protocol. | |
const LOCAL = new RegExp("^" + [ | |
"([^\.]\.)+local", // DNS-SD ("Bonjour") local-network automatic hostnames. | |
"([^\.]\.)+example", // Reserved for private use. | |
"([^\.]\.)+test", // Reserved for private use. | |
"([^\.]\.)+internal", // Reserved for private use. | |
"([^\.]\.)*localhost", // Loopback interface by name. | |
"127.0.0.1", // IPv4 loopback interface. | |
"::1", // IPv6 loopback interface. | |
].join("|") + "$", "i"); | |
const ISLOCAL = (window.location.protocol == 'file:') || LOCAL.test(window.location.hostname); | |
const NOOP = () => {}; | |
const COLLECT = (event, ...args) => { fetch(`/meta/collect/${event}`, { | |
method: 'POST', | |
mode: 'same-origin', | |
cache: 'no-cache', | |
credentials: 'same-origin', | |
redirect: 'error', | |
referrer: 'client', | |
headers: {'Content-Type': 'application/json'}, | |
body: JSON.stringify({'args': args}), | |
}) }; | |
const METHODS = [ | |
'assert', | |
'clear', | |
'count', | |
'countReset', | |
'debug', | |
'dir', | |
'dirxml', | |
'error', | |
'exception', // Deprecated! Do not use, prefer `error` instead! | |
'group', | |
'groupCollapsed', | |
'groupEnd', | |
'info', | |
'log', | |
'markTimeline', // Deprecated, WebKit-specific! Use timeStamp instead! | |
'profile', | |
'profileEnd', | |
'table', | |
'time', | |
'timeEnd', | |
'timeLog', | |
'timeStamp', // Deprecated, removed, and WebKit-specific! | |
'timeline', // Deprecated! | |
'timelineEnd', // Deprecated! | |
'trace', | |
'warn', | |
]; | |
const INSTRUMENT = [ | |
'debug', | |
'error', | |
'exception', | |
'info', | |
'log', | |
'table', | |
'trace', | |
'warn', | |
]; | |
const SILENCE = [ // In "production" silence these logging levels. | |
'debug', | |
'info', | |
'log', | |
] | |
var console = (window.console = window.console || {}); | |
var original = {}; // Needs to be exposed to nested contexts, thus not 'let'. | |
// Only populate ("polyfill") missing (undefined) methods. | |
for ( let method of METHODS ) | |
if ( !console[method] ) | |
console[method] = NOOP.bind(console); | |
// "Silence" certain logging levels/methods in production environments. | |
if ( !ISLOCAL ) | |
for ( let method of SILENCE ) | |
console[method] = NOOP.bind(console); | |
// Instrument (interpose) specific methods to route those classes of message to the back-end. | |
// Do not do so if we are served from the file: protocol, as there is no server to accept these notifications. | |
if ( window.location.protocol != 'file:' ) | |
for ( let method of INSTRUMENT ) { | |
original[method] = console[method]; | |
if ( SILENCE.includes(method) ) | |
console[method] = COLLECT.bind(console, method); | |
else | |
console[method] = (...args) => { COLLECT(method, ...args); original[method](...args) }; | |
} | |
}()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Python-like datetime.isoformat() method. | |
Date.prototype.isoformat = function ISODateString(sep, tz) { | |
if ( sep === undefined ) sep = 'T'; | |
if ( tz === undefined ) tz = 'Z'; | |
function pad(n) { return n < 10 ? '0' + n : n } | |
return this.getUTCFullYear() + '-' | |
+ pad(this.getUTCMonth() + 1) + '-' | |
+ pad(this.getUTCDate()) + sep | |
+ pad(this.getUTCHours()) + ':' | |
+ pad(this.getUTCMinutes()) + ':' | |
+ pad(this.getUTCSeconds()) + tz | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
window.console = { | |
original: console, | |
...console, | |
speak: (strings, func, start) => strings.forEach(string => { | |
speechSynthesis.speak(new SpeechSynthesisUtterance((start ? start : '') + string)) | |
return console.original[func](string) | |
}), | |
log: (...strings) => console.speak(strings, 'log', `Dear diary. `), | |
info: (...strings) => console.speak(strings, 'info', `For your information. `), | |
warn: (...strings) => console.speak(strings, 'warn', `I'm warning you. `), | |
error: (...strings) => console.speak(strings, 'error', `Red alert. `) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Semicolon use is roughshod due to lazy elimination of braces. Test it, throw out the ones that don't matter. | |
String.prototype.format = function () { | |
// Python str.format-like syntax interpolation. But, today, just use back-quoted string formatting. Examples: | |
// 'Lorem {0} ipsum.'.format('<script>') --> 'Lorem <script> ipsum.' | |
// 'Dolor {e0} amet.'.format('<script>') --> 'Dolor <script> amet.' | |
var args = arguments; | |
return this.replace(/\{(e?)(\d+)\}/g, function (match, flag, number) { | |
if (typeof args[number] != 'undefined') { | |
if (flag == 'e') return _.escape(args[number]); | |
else return args[number]; | |
} else return match; | |
}) | |
}; | |
String.prototype.startsWith = function (str) { | |
return this.substr(0, str.length) === str | |
} | |
String.prototype.endsWith = function(suffix) { | |
return this.indexOf(suffix, this.length - suffix.length) !== -1 | |
} | |
if (!String.prototype.trim) { | |
String.prototype.ltrim = function() { | |
return this.replace(/^\s+/, '') | |
} | |
String.prototype.rtrim = function() { | |
return this.replace(/\s+$/, '') | |
} | |
String.prototype.trim = function() { | |
return this.replace(/^\s+|\s+$/g, '') | |
} | |
} | |
String.prototype.join = function (array, ...args) { | |
// Python-like ",".join([1, 2, 3]) or ",".join(1, 2, 3) for the lazy. | |
if ( args.length ) return [array, ...args].join(this) | |
else return array.join(this) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment