Riddle embed webcomponent
The Riddle embed webcomponent is created with stencil. It works with Riddle V1 and V2.
Complete code
import { Component, Element, Host, h, Prop, State } from '@stencil/core';
import { RiddleEvent, RiddleEventV1, RiddleTrackEvent, RiddleTrackNetworks, RiddleTrackNetworksConfig_Custom, RiddleTrackNetworksConfig_Default } from './types/RiddleEvent';
import { RiddleDataLayerItem } from './types/RiddleDataLayer';
@Component({
tag: 'riddle-embed',
styleUrl: 'riddle-embed.css',
shadow: true,
})
export class RiddleEmbed {
@Prop() riddleid!: string;
@Prop() autoscroll: boolean = false;
@Prop() autoscrolloffset: number = 0;
@Element() el: HTMLElement;
private _riddleDataLayer: Array<RiddleDataLayerItem> = [];
private _scrollPosition: number = 0;
private _iframeElement?: HTMLIFrameElement;
@State() iframeHeight: number = 0;
@State() isRiddleV1: boolean = false;
@State() isRiddleLoaded: boolean = false;
componentWillLoad() {
this.isRiddleV1 = /^[0-9]+$/.test(this.riddleid);
if (this.isRiddleV1) {
window.addEventListener('message', this.onRiddleV1Event, false);
} else {
window.addEventListener('message', this.onRiddleV2Event, false);
}
}
componentDidLoad() {
this._iframeElement = this.el.shadowRoot.querySelector('#riddle-iframe-' + this.riddleid);
this._scrollPosition = 0;
document.addEventListener('scroll', () => {
if (this._scrollPosition != 0) {
window.scrollTo(0, this._scrollPosition);
this._scrollPosition = 0;
}
this.sendPositionDataToIframe();
});
}
private validateDataLayerItem = (target: Array<RiddleDataLayerItem>, value: RiddleDataLayerItem): boolean => {
const validKeyTypes = ['string', 'number'];
let isValid = true;
if (typeof value !== 'object') {
console.log('typeof DataLayerItem:', typeof value);
console.log('DataLayerItem must be an object like: { key: "key", value: "value" || 0 }');
isValid = false;
}
if (typeof value.key !== 'string') {
console.log('typeof DataLayerItem.key:', typeof value.key);
console.log('DataLayerItem must have a "key"');
isValid = false;
}
if (!validKeyTypes.includes(typeof value.value)) {
console.log('typeof DataLayerItem.value:', typeof value.value);
console.log('DataLayerItem must have a "value" of type: ' + validKeyTypes.join(' or '));
isValid = false;
}
if (isValid) {
target.push(value);
}
return isValid;
};
private initDataLayer() {
const itemHandler = {
set: (target: Array<RiddleDataLayerItem>, p: PropertyKey, value: RiddleDataLayerItem) => {
if (p === 'length') {
(target as object)[p] = value;
return true;
}
const isValid = this.validateDataLayerItem(target, value);
if (isValid) {
this._iframeElement?.contentWindow?.postMessage(
{ dataLayerItem: value, riddleQueryString: window.location.href.split('?')[1], riddleParentLocation: window.location.href },
'*',
);
}
return isValid;
},
};
this._riddleDataLayer = new Proxy<Array<RiddleDataLayerItem>>([], itemHandler);
this.readFromUrl();
}
private readFromUrl() {
const queryString = window.location.search;
const urlSearchParams = new URLSearchParams(queryString);
const params = Object.fromEntries(urlSearchParams.entries());
for (const key in params) {
if (Object.prototype.hasOwnProperty.call(params, key)) {
const value = params[key];
this.pushDataLayerItem({
key: key,
value: value,
});
}
}
}
public pushDataLayerItem(item: RiddleDataLayerItem) {
if (this._riddleDataLayer) {
const index = this._riddleDataLayer.findIndex(i => i.key === item.key);
if (index === -1) {
this._riddleDataLayer.push(item);
} else {
this._riddleDataLayer[index] = item;
}
}
}
private UpdateHeight(height: number) {
this.iframeHeight = height;
}
private getRiddleEventObject = (data: any) => {
if (data.isRiddle2Event !== undefined && data.isRiddle2Event === true) {
return data as RiddleEvent;
} else {
return data as RiddleEventV1;
}
};
private pushTrackEvent(riddleEvent: RiddleTrackEvent) {
if (riddleEvent.trackNetworks) {
riddleEvent.trackNetworks.forEach(trackNetwork => {
try {
if (trackNetwork.networkName === RiddleTrackNetworks.customTracking) {
const tnef = (trackNetwork as RiddleTrackNetworksConfig_Custom).eventFunction;
const ef = eval(tnef);
ef(riddleEvent);
} else {
const tn = trackNetwork as RiddleTrackNetworksConfig_Default;
const data = {
...riddleEvent,
} as any;
delete data.trackNetworks;
switch (trackNetwork.networkName) {
// facebook pixel
case RiddleTrackNetworks.facebookPixel:
const fbq = window[tn.networkObjectName];
fbq('trackCustom', riddleEvent.category, {
eventAction: riddleEvent.action,
eventName: riddleEvent.name,
...data,
});
break;
// google analytics
case RiddleTrackNetworks.googleAnalytics:
const ga = window[tn.networkObjectName];
ga('send', 'event', [riddleEvent.category], [riddleEvent.action], [riddleEvent.name], ...data);
break;
case RiddleTrackNetworks.googleAnalytics4:
const gtag = window[tn.networkObjectName];
gtag('event', riddleEvent.category, {
event_name: riddleEvent.name,
event_action: riddleEvent.action,
...data,
});
break;
// google tag manager
case RiddleTrackNetworks.googleTagManager:
const dataLayer = window[tn.networkObjectName];
dataLayer.push({
event: 'RiddleEvent',
event_category: riddleEvent.category,
event_action: riddleEvent.action,
event_name: riddleEvent.name,
...data,
});
break;
// matomo tag
case RiddleTrackNetworks.matomoTag:
const _paq = window[tn.networkObjectName];
_paq.push(['trackEvent', riddleEvent.category, riddleEvent.action, riddleEvent.name, ...data]);
break;
default:
break;
}
}
} catch (error) {
console.log('[RiddleTrackingError] ' + trackNetwork.networkName + ' not found');
}
});
}
}
private sendPositionDataToIframe() {
var iframeOffsetTop = this._iframeElement.getBoundingClientRect().top + window.scrollY || window.pageYOffset;
var viewtop = window.scrollY || window.pageYOffset;
var viewbottom = viewtop + window.innerHeight;
// Post position to iframe.
this._iframeElement.contentWindow?.postMessage(
{
iframeOffsetTop: iframeOffsetTop,
viewtop: viewtop,
viewbottom: viewbottom,
},
'*',
);
}
private preventSiteJump() {
var doc = document.documentElement;
this._scrollPosition = (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
}
private onRiddleV1Event = (event: MessageEvent) => {
let riddleEvent = this.getRiddleEventObject(event.data);
if ((riddleEvent as RiddleEvent).isRiddle2Event) {
return;
}
if ((riddleEvent as RiddleEventV1).riddleData !== undefined && (riddleEvent as RiddleEventV1).riddleData.id.toString() === this.riddleid.toString()) {
if (this.isRiddleLoaded === false) {
this.isRiddleLoaded = true;
}
}
// Update height.
if ((riddleEvent as RiddleEventV1).riddleHeight) {
this.UpdateHeight((riddleEvent as RiddleEventV1).riddleHeight);
}
if (event.data.riddleEvent == 'page-change' && this.autoscroll) {
// Scroll to top of Riddle.
const pos = this._iframeElement.getBoundingClientRect().top;
const target = pos + window.scrollY - 10 - this.autoscrolloffset;
window.scrollTo({
top: target,
behavior: 'smooth',
});
this.sendPositionDataToIframe();
} else if (event.data.riddleEvent == 'page-change' && !this.autoscroll) {
this.preventSiteJump();
this.sendPositionDataToIframe();
}
// Prevent site jump.
if (event.data.riddleEvent && event.data.riddleEvent.action == 'answer-poll' && !this.autoscroll) {
this.preventSiteJump();
}
// Redirect to custom landing page.
this.redirectToCustomResultPage(riddleEvent);
};
private onRiddleV2Event = (event: MessageEvent) => {
let riddleEvent = this.getRiddleEventObject(event.data);
if (!(riddleEvent as RiddleEvent).isRiddle2Event || (riddleEvent as RiddleEvent).riddleId.toString() !== this.riddleid.toString()) {
return;
}
// Remove loader.
if ((riddleEvent as RiddleEvent).type === 'RiddleInited') {
this.isRiddleLoaded = true;
}
// Update height.
if ((riddleEvent as RiddleEvent).height) {
this.UpdateHeight((riddleEvent as RiddleEvent).height);
this.initDataLayer();
}
if (typeof event.data.category == 'string' && (event.data.category as string).startsWith('RiddleTrackEvent')) {
const data = event.data as RiddleTrackEvent;
this.pushTrackEvent(data);
}
if (((riddleEvent as RiddleEvent).type == 'PageChanged' || (event.data as RiddleTrackEvent).action === 'Block_View') && this.autoscroll) {
// Scroll to top of Riddle.
const pos = this._iframeElement.getBoundingClientRect().top;
const target = pos + window.scrollY - 10 - this.autoscrolloffset;
window.scrollTo({
top: target,
behavior: 'smooth',
});
this.sendPositionDataToIframe();
} else if ((riddleEvent as RiddleEvent).type == 'PageChanged' && !this.autoscroll) {
this.preventSiteJump();
this.sendPositionDataToIframe();
}
// Prevent site jump.
if ((riddleEvent as RiddleEvent).type === 'AnswerPoll' && !this.autoscroll) {
this.preventSiteJump();
}
// Redirect to custom landing page.
this.redirectToCustomResultPage(riddleEvent);
};
private redirectToCustomResultPage(riddleEvent: RiddleEvent | RiddleEventV1) {
if (
(riddleEvent as RiddleEvent).redirectToCustomLandingPage ||
((riddleEvent as RiddleEventV1).redirectToCustomLandingpagePath && (riddleEvent as RiddleEventV1).redirectToCustomLandingpageData)
) {
const path = (riddleEvent as RiddleEventV1).redirectToCustomLandingpagePath || (riddleEvent as RiddleEvent).redirectToCustomLandingPage.path;
const data = (riddleEvent as RiddleEventV1).redirectToCustomLandingpageData || (riddleEvent as RiddleEvent).redirectToCustomLandingPage.data;
const form = window.parent.document.createElement('form');
form.setAttribute('method', 'post');
form.setAttribute('action', path);
const hiddenField = window.parent.document.createElement('input');
hiddenField.setAttribute('type', 'hidden');
hiddenField.setAttribute('name', 'data');
hiddenField.setAttribute('value', data);
form.appendChild(hiddenField);
window.parent.document.body.appendChild(form);
form.submit();
}
}
private getIframeUrl = (): string => {
return 'https://www.riddle.com/a/' + this.riddleid;
};
private getIframeStyle = (): any => {
return { height: this.iframeHeight + 'px', display: this.isRiddleLoaded ? 'block' : 'none' };
};
private getRiddleLoaderHtml = (): string => {
return (
<div class="rid-load-wrapper" style={{ display: !this.isRiddleLoaded ? 'block' : 'none' }}>
<div class="rid-load-wrapper__loader">
<p>
<i></i>
<i></i>
<i></i>
</p>
</div>
</div>
);
};
private getRiddleV1Html = (): string => {
return (
<Host>
<div class="App">
{
<div class="riddle_target" data-rid-id={this.riddleid}>
<iframe id={'riddle-iframe-' + this.riddleid} class="riddle_target_iframe" src={this.getIframeUrl()} style={this.getIframeStyle()}></iframe>
{this.getRiddleLoaderHtml()}
</div>
}
</div>
</Host>
);
};
private getRiddleV2Html = (): string => {
return (
<Host>
<div class="App">
{
<div class="riddle2-wrapper" data-rid-id={this.riddleid}>
<iframe
id={'riddle-iframe-' + this.riddleid}
class="riddle2-wrapper_iframe"
src={this.getIframeUrl()}
style={this.getIframeStyle()}
allow="autoplay"
referrerPolicy="strict-origin"
></iframe>
{this.getRiddleLoaderHtml()}
</div>
}
</div>
</Host>
);
};
render() {
if (this.isRiddleV1) {
return this.getRiddleV1Html();
} else {
return this.getRiddleV2Html();
}
}
}