Skip to content

Instantly share code, notes, and snippets.

@jaggli

jaggli/1_HotjarFix.js

Last active Jul 9, 2018
Embed
What would you like to do?
Styled Components vs Hotjar (Sent to Hotjar support at 27. March 2018)
import React from 'react'
const fixHotjar = () => {
// add sync style tag to head
const syncStyles = document.createElement('style')
syncStyles.type = 'text/css'
document.head.insertBefore(syncStyles, document.head.children[0])
// start styles sync
let lastStyles
const updateStyles = () => {
const styleNodes = [].slice.call(document.querySelectorAll('head [data-styled-components]'))
if (!styleNodes.length) { return }
const styles = styleNodes
.map(({ sheet }) => [].slice.call(sheet.cssRules)
.map(rule => rule.cssText)
.join(' ')
)
.join(' ')
if (styles === lastStyles) { return }
syncStyles.textContent = styles
lastStyles = styles
}
// start updating interval
const startInterval = window.setInterval(() => {
if (!window.hj || !window.hj.behaviorData) { return }
window.clearInterval(startInterval)
window.setInterval(() => {
window.requestAnimationFrame(() => {
updateStyles()
})
}, 500)
}, 50)
}
const HotjarFix = props => (
<script id='hotjar-fix' dangerouslySetInnerHTML={{
__html: `(${fixHotjar.toString()})()`
}} />
)
export default HotjarFix
// ----------------------------------------------------------------------------
// This is a failed attempt to send fake mutations to hotjar
// For documentation reasons, this stays here
// ----------------------------------------------------------------------------
// THIS DOESN'T WORK, DON'T USE THIS IN PRODUCTION!!!
import React from 'react'
var fixHotjar = () => {
// add sync style tag to head
const syncStyles = document.createElement('style')
syncStyles.type = 'text/css'
document.head.insertBefore(syncStyles, document.head.children[0])
// style updates helper
let lastStyles = ''
const getStyleUpdates = () => {
const styleNodes = [].slice.call(document.querySelectorAll('head [data-styled-components]'))
if (!styleNodes.length) { return }
const styles = styleNodes
.map(({ sheet }) => [].slice.call(sheet.cssRules)
.map(rule => rule.cssText)
.join(' ')
)
.join(' ')
if (styles === lastStyles) { return }
const ret = {
removed: lastStyles,
added: styles
}
lastStyles = styles
return ret
}
// fake classes
const NodeList = function (...args) { return new Array(...args) }
const MutationRecord = function ({addedNodes, removedNodes}) {
this.addedNodes = addedNodes
this.attributeName = null
this.attributeNamespace = null
this.nextSibling = null
this.oldValue = null
this.previousSibling = null
this.removedNodes = removedNodes
this.type = 'childList'
this.target = syncStyles
}
// define PromotableObserver
const OriginalObserver =
window.MutationObserver ||
window.WebKitMutationObserver ||
window.MozMutationObserver
const PromotableObserver = function (fn) {
const _this = this
_this._promotedMutations = {
list: [],
nodeCache: {}
}
_this._super = new OriginalObserver((mutations, observer) => {
const all = [].concat(
mutations,
_this.takePromotedRecords()
)
fn.call(window, all, observer)
})
console.log('promotable mutation observer initialized')
const startInterval = window.setInterval(() => {
if (!window.hj || !window.hj.behaviorData) { return }
window.clearInterval(startInterval)
window.setInterval(() => {
window.requestAnimationFrame(() => {
const updates = getStyleUpdates()
if (!updates) { return }
let addedNode
if (updates.added) {
addedNode = document.createTextNode(updates.added)
_this._promotedMutations.nodeCache[updates.added] = addedNode
}
let removedNode
if (updates.removed) {
removedNode = _this._promotedMutations.nodeCache[updates.removed] ||
document.createTextNode(updates.removed)
}
const record = new MutationRecord({
addedNodes: new NodeList(addedNode || 0),
removedNodes: new NodeList(removedNode || 0)
})
_this.promoteRecord(record)
})
}, 500)
}, 50)
_this.promoteRecord = function (record) {
console.log('promote', record)
_this._promotedMutations.list.push(record)
window.setTimeout(() => {
console.log(_this._promotedMutations.list.length)
const promoted = _this.takePromotedRecords()
console.log(promoted)
if (promoted.length) {
// make sure, mutation handler is called, if not already by other modifications
fn.call(window, promoted, _this._super)
}
}, 50)
}
_this.takePromotedRecords = function () {
return _this._promotedMutations.list.splice(0, _this._promotedMutations.list.length)
}
_this.takeRecords = function () {
const allRecords = [].concat(
_this._super.takeRecords(),
_this.takePromotedRecords()
)
console.log('take records', allRecords)
return allRecords
}
_this.observe = function (target, options) {
return _this._super.observe(target, options)
}
_this.disconnect = function (target, options) {
return _this._super.disconnect(target, options)
}
}
window.MutationObserver = PromotableObserver
window.WebKitMutationObserver = PromotableObserver
window.MozMutationObserver = PromotableObserver
}
const HotjarFix = props => (
<script id='hotjar-fix' dangerouslySetInnerHTML={{
__html: `(${fixHotjar.toString()})()`
}} />
)
export default HotjarFix
@Ximik

This comment has been minimized.

Copy link

@Ximik Ximik commented Jul 9, 2018

Is this script still necessary and works for the new HotJat script version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.