TwainSport Iframe integration
Using integration script
Add this block of JavaScript code to your page and change the script parameters.
- Load the script tag from `<CLIENT_URL>/public/ts-loader.js`;
- Add timestamp to invalidate any cache: `<CLIENT_URL>/public/ts-loader.js?${Date.now()}`;
Using inside the iframe
<div id="<IFRAME_HTML_CONTAINER_ID>"></div>
<script>
const clientUrl = '<CLIENT_URL>';
const script = document.createElement('script');
script.src = `${clientUrl}/public/ts-loader.js?${Date.now()}`;
script.onload = () => {
window.TwainSports.setup({
containerElement: document.getElementById('<IFRAME_HTML_CONTAINER_ID>'),
clientUrl: '<CLIENT_URL>',
apiUrl: '<API_URL>',
wsUrl: '<WS_URL>',
partnerCode: '<PARTNER_CODE>',
token: '<TOKEN>',
timezone: '<TIMEZONE>',
locale: '<LOCALE>',
oddsFormat: '<ODDS_FORMAT>',
showBalance: '<SHOW_BALANCE>',
showLogo: '<SHOW_LOGO>',
theme: '<THEME>',
gameId:'<GAME_ID>',
gui: '<GUI>',
dynamicHeight: '<DYNAMIC_HEIGHT>',
platformID: '<PLATFORM_ID>',
});
};
document.head.appendChild(script);
</script>
- <CONTAINER_ELEMENT>: should be valid HTML node `document.getElementById('twainsport-iframe')` or valid CSS selector `#twainsport_iframe`;
- <CLIENT_URL>: should be used the same one from which the script was fetched;
Opening in new tab
<script>
const clientUrl = '<CLIENT_URL>';
const script = document.createElement('script');
script.src = `${clientUrl}/public/ts-loader.js?${Date.now()}`;
document.head.appendChild(script);
document.getElementById('<TWAIN_SPORT_BUTTON>').addEventListener('click', (e) => {
e.preventDefault();
window.TwainSports.setup({
containerElement: '<CONTAINER_ELEMENT>',
clientUrl: '<CLIENT_URL>',
apiUrl: '<API_URL>',
wsUrl: '<WS_URL>',
partnerCode: '<PARTNER_CODE>',
token: '<TOKEN>',
timezone: '<TIMEZONE>',
locale: '<LOCALE>',
oddsFormat: '<ODDS_FORMAT>',
showBalance: '<SHOW_BALANCE>',
showLogo: '<SHOW_LOGO>',
theme: '<THEME>',
gameId:'<GAME_ID>',
gui: '<GUI>',
dynamicHeight: '<DYNAMIC_HEIGHT>',
platformID: '<PLATFORM_ID>',
integration: 'tab', // <-- Just provide additional parameter `integration: 'tab'`
});
});
</script>
- <CONTAINER_ELEMENT>: should be valid HTML node `document.getElementById('twainsport-iframe')` or valid CSS selector `#twainsport_iframe`;
- <CLIENT_URL>: should be used the same one from which the script was fetched;
Direct integration via URL
Instead of using integration script, another option is to integrate using directly the URL of TwainSports.
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div id="iframe-content" style="margin: 0 auto; padding-bottom: 100px">
<iframe
frameborder="0"
allow="autoplay; fullscreen; encrypted-media; clipboard-write self <CLIENT_URL>"
scrolling="no"
src="<CLIENT_URL>/#/?apiUrl=<API_URL>&wsUrl=<WS_URL>&partnerCode=<PARTNER_CODE>&token=<TOKEN>&timezone=<TIMEZONE>&locale=<LOCALE>&oddsFormat=<ODDS_FORMAT>&showBalance=<SHOW_BALANCE>&showLogo=<SHOW_LOGO>&theme=<THEME>&gameId=<GAME_ID>&gui=<GUI>&dynamicHeight=<DYNAMIC_HEIGHT>"
style="min-width: 100%; height: 1410px;">
</iframe>
</div>
</body>
</html>
Environments
| Environment | CLIENT_URL | API_URL | WS_URL |
|---|---|---|---|
| Integrations / Testing | https://integrations-ag-iframe.betgames.tv | https://integrations-ag-bp.betgames.tv | https://integrations-ag-ws.betgames.tv |
| Production | https://web.twainsports.com | https://yrouw9o6.twainsports.com | https://ws.twainsports.com |
Parameters
| Parameter | Is required | Description | Default value |
|---|---|---|---|
| clientUrl | Yes | Url of iFrame client application. Provided by the Betgames Integration team. | |
| partnerCode | Yes | Provided by the Betgames Integration team. | |
| token | Yes | Generated token for the authorized player, for non-loged player ' - ' should be provided. | |
| apiUrl | Yes | Provided by the BetGames Integration team. | https://integrations-ag-bp.betgames.tv |
| wsUrl | Yes | Provided by the BetGames Integration team. | https://integrations-ag-ws.betgames.tv |
| locale | No | A list of supported languages can be found - https://wiki.betgames.tv/index.php?title=List_of_supported_languages | en |
| gameId | No | Used to open specific games during the launch instead of the lobby.
Values:
|
|
| timezone | Yes | Timezone UTC.
Values: any numerical value in the range from -12 to 14. |
0 |
| oddsFormat | No | Used to show add values in different formats.
Values:
|
decimal |
| showBalance | No | Used to show, or hide balance in the top header.
Values:
|
1 |
| showLogo | No | Used to show, or hide logo in the top header.
Values:
|
1 |
| theme | No | Used for colors customization.
Values:
|
default |
| dynamicHeight | No | Used to disable some functionality that doesn't work when we don't have access to the scroll and enables the listener for height update via post messages.
|
0 |
| height | No | Used to set the height of iframe in case we have dynamicHeight=0. | 100% |
| integration | No | Integration type. Embedded with <iframe> element or in new window (tab).
Values:
|
0 |
| homeUrl | No | Used to redirect the player to the partner home page when the player decides to leave TwainSport. The logout/home button is only visible on mobile resolutions. | |
| gui | No | Used to show Graphical User Interface (GUI) by default.
Values:
|
0 |
| platformID | No | Used to send platformID when placing bet. Values can be from 0 to any number according to partner customization.
Example:
|
0 |
| customMethods | No | Used when partner uses our ts-loader.js file, but wants to skip postMessages initialisation from it.
For ex. he has its own messaging methods and uses iframe inside iframe. In that case they should attach all eventListener to the top window (which has scroll) |
0 |
Messaging
For communication between parent and iframe, we're using window.postMessage.
Events
emit - emits message from iframe to partner page.
receive - sends message from partner page to iframe.
| Event name | Type | Payload | Description | REQUIRED |
|---|---|---|---|---|
| realityCheck | emit | {
"windowName": "twainsport_iframe",
"type": "realityCheck"
}
|
Sending each time user clicks place bet button. | YES |
| newHeight | emit | {
"windowName": "twainsport_iframe",
"type": "newHeight",
"payload": {
"height": 500
}
}
|
iFrame does not contain a scroll this is because it needs to be updated by a parent.
This event will be sent:
|
YES (only when `dynamicHeight` is enabled) |
| balanceCheck | emit | {
"windowName": "twainsport_iframe",
"type": "balanceCheck"
}
|
This event will be sent:
|
YES |
| scrollTo | emit | {
"windowName": "twainsport_iframe",
"type": "scrollTo",
"payload": {
"scrollTo": 34
}
}
|
Sending when iframe needs to update partners page scroll position. | YES (only when `dynamicHeight` is enabled) |
| iframeTop | emit | {
"windowName": "twainsport_iframe",
"type": "iframeTop",
"payload": {
"topOffset": 0
}
}
|
Sending when iframe needs to scroll parent pages to the top. topOffset is used to additional add a top scroll when scrolling the parent page to the top. |
YES (only when `dynamicHeight` is enabled) |
| getViewPort | emit | {
"windowName": "twainsport_iframe",
"type": "getViewPort"
}
|
Sending request to get view port dimensions from partner page. | YES (only when `dynamicHeight` is enabled) |
| getViewPort | receive | {
"type": "getViewPort",
"payload": {
"width": window.innerWidth,
"height": window.innerHeight,
"iFrameTopOffset": iFrame.offsetTop,
"scrollY": window.scrollY
}
}
|
Sending request to get view port dimensions from partner page. | YES (only when `dynamicHeight` is enabled) |
| onScroll | emit | {
"type": "onScroll",
"payload": {
"height": 1000,
"scrollY": 0,
"iFrameTopOffset": 100
}
}
|
Sending from partner's page on each window scroll event after receiving 'subscribeScroll' event. Example below | YES (only when `dynamicHeight` is enabled) |
| subscribeScroll | receive | {
"windowName": "twainsport_iframe",
"type": "subscribeScroll"
}
|
Sending to partner to subscribe to window.scroll events and start sending 'onScroll' events | YES (only when `dynamicHeight` is enabled) |
| unsubscribeScroll | receive | {
"windowName": "twainsport_iframe",
"type": "unsubscribeScroll"
}
|
Sending to partner to unsubscribe from window.scroll events and stop sending `onScroll` events | YES (only when `dynamicHeight` is enabled) |
Receiving messages
Code below could be inserted into the browser console to see incoming post messages
<script>
window.addEventListener('message', (message) => {
try {
const parsedData = JSON.parse(message.data);
console.log(parsedData);
} catch (e) {}
});
</script>
"onScroll" implementation
<script>
const iFrame = document.getElementById('twainsport_iframe');
const scrollSubscribe = () => {
iFrame.contentWindow.postMessage(
JSON.stringify({
type: 'onScroll',
payload: {
height: window.innerHeight,
scrollY: window.scrollY,
iFrameTopOffset: iFrame.offsetTop,
},
}),
'*',
);
};
window.addEventListener('message', (message) => {
try {
const parsedData = JSON.parse(message.data);
switch (parsedData.type) {
case 'subscribeScroll': {
window.addEventListener('scroll', scrollSubscribe);
break;
}
case 'unsubscribeScroll': {
window.removeEventListener('scroll', scrollSubscribe);
break;
}
}
} catch (e) {}
});
</script>
Examples
For partners without ts-loader.js implementation
<script>
const iFrame = document.getElementById('twainsport_iframe');
const scrollSubscribe = () => {
iFrame.contentWindow.postMessage(
JSON.stringify({
type: 'onScroll',
payload: {
height: window.innerHeight,
scrollY: window.scrollY,
iFrameTopOffset: iFrame.offsetTop,
},
}),
'*',
);
};
window.addEventListener('message', (message) => {
try {
const parsedData = JSON.parse(message.data);
switch (parsedData.type) {
case 'subscribeScroll': {
window.addEventListener('scroll', scrollSubscribe);
break;
}
case 'unsubscribeScroll': {
window.removeEventListener('scroll', scrollSubscribe);
break;
}
case 'newHeight': {
iFrame.style.height = `${parsedData.payload.height}px`;
break;
}
case 'getViewPort': {
iFrame.contentWindow.postMessage(
JSON.stringify({
type: 'getViewPort',
payload: {
width: window.innerWidth,
height: window.innerHeight,
iFrameTopOffset: iFrame.offsetTop,
scrollY: window.scrollY,
},
}),
'*',
);
break;
}
case 'scrollTo': {
window.scroll(0, Number(parsedData.payload.scrollTo));
break;
}
case 'iframeTop': {
window.scroll(0, Number(message.payload.topOffset) + iFrame.offsetTop);
break;
}
}
} catch (e) {}
});
</script>
For partners with iframe inside iframe without "ts-loader.js" implementation
<!-- inner-iframe.html -->
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<style>
html,
body {
height: 100%;
}
html {
overflow: hidden;
}
body {
margin: 0;
}
</style>
</head>
<body>
<iframe
name="twainsport_iframe"
allow="autoplay; fullscreen; encrypted-media; clipboard-write self <CLIENT_URL>"
src="<CLIENT_URL>/#/?apiUrl=<API_URL>&wsUrl=<WS_URL>&partnerCode=<PARTNER_CODE>&token=<TOKEN>&timezone=<TIMEZONE>&locale=<LOCALE>&oddsFormat=<ODDS_FORMAT>&showBalance=<SHOW_BALANCE>&showLogo=<SHOW_LOGO>&theme=<THEME>&gameId=<GAME_ID>&gui=<GUI>&dynamicHeight=<DYNAMIC_HEIGHT>"
style="
width: 100%;
height: 100%;
min-height: 500px;
border: 0;
">
</iframe>
</body>
</html>
<!-- partner-page.html -->
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script>
const innerIFrame = document.getElementById('inner_iframe');
const scrollSubscribe = () => {
innerIFrame.contentWindow.frames['twainsport_iframe'].postMessage(
JSON.stringify({
type: 'onScroll',
payload: {
height: window.innerHeight,
scrollY: window.scrollY,
iFrameTopOffset: innerIFrame.offsetTop,
},
}),
'*',
);
};
window.addEventListener('message', (message) => {
try {
const parsedData = JSON.parse(message.data);
switch (parsedData.type) {
case 'subscribeScroll': {
window.addEventListener('scroll', scrollSubscribe);
break;
}
case 'unsubscribeScroll': {
window.removeEventListener('scroll', scrollSubscribe);
break;
}
case 'newHeight': {
innerIFrame.style.height = `${parsedData.payload.height}px`;
break;
}
case 'getViewPort': {
innerIFrame.contentWindow.frames['twainsport_iframe'].postMessage(
JSON.stringify({
type: 'getViewPort',
payload: {
width: window.innerWidth,
height: window.innerHeight,
iFrameTopOffset: innerIFrame.offsetTop,
scrollY: window.scrollY,
},
}),
'*',
);
break;
}
case 'scrollTo': {
window.scroll(0, Number(parsedData.payload.scrollTo));
break;
}
case 'iframeTop': {
window.scroll(0, Number(message.payload.topOffset) + innerIFrame.offsetTop);
break;
}
}
} catch (e) {}
});
</script>
</head>
<body>
<iframe
id="inner_iframe"
src="<URL_TO_INNER_HTML>"
style="
width: 100%;
height: 100%;
min-height: 500px;
border: 0;
">
</iframe>
</body>
</html>
For partners with iframe inside iframe with "ts-loader.js" implementation
<!-- inner-iframe.html -->
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<style>
html,
body {
height: 100%;
}
html {
overflow: hidden;
}
body {
margin: 0;
}
</style>
</head>
<body>
<div id="<IFRAME_HTML_CONTAINER_ID>"></div>
<script>
const clientUrl = '<CLIENT_URL>';
const script = document.createElement('script');
script.src = `${clientUrl}/public/ts-loader.js?${Date.now()}`;
script.onload = () => {
window.TwainSports.setup({
containerElement: document.getElementById('<IFRAME_HTML_CONTAINER_ID>'),
clientUrl: '<CLIENT_URL>',
apiUrl: '<API_URL>',
wsUrl: '<WS_URL>',
partnerCode: '<PARTNER_CODE>',
token: '<TOKEN>',
timezone: '<TIMEZONE>',
locale: '<LOCALE>',
oddsFormat: '<ODDS_FORMAT>',
showBalance: '<SHOW_BALANCE>',
showLogo: '<SHOW_LOGO>',
theme: '<THEME>',
gameId:'<GAME_ID>',
gui: '<GUI>',
dynamicHeight: '<DYNAMIC_HEIGHT>',
customMethods: 1, // <-- "customMethods" disables default post messaging methods from loader. Needs to be implemented in "partner-page.html"
});
};
document.head.appendChild(script);
</script>
</body>
</html>
<!-- partner-page.html -->
<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script>
const innerIFrame = document.getElementById('inner_iframe');
const scrollSubscribe = () => {
innerIFrame.contentWindow.frames['twainsport_iframe'].postMessage(
JSON.stringify({
type: 'onScroll',
payload: {
height: window.innerHeight,
scrollY: window.scrollY,
iFrameTopOffset: innerIFrame.offsetTop,
},
}),
'*',
);
};
window.addEventListener('message', (message) => {
try {
const parsedData = JSON.parse(message.data);
switch (parsedData.type) {
case 'subscribeScroll': {
window.addEventListener('scroll', scrollSubscribe);
break;
}
case 'unsubscribeScroll': {
window.removeEventListener('scroll', scrollSubscribe);
break;
}
case 'newHeight': {
innerIFrame.style.height = `${parsedData.payload.height}px`;
break;
}
case 'getViewPort': {
innerIFrame.contentWindow.frames['twainsport_iframe'].postMessage(
JSON.stringify({
type: 'getViewPort',
payload: {
width: window.innerWidth,
height: window.innerHeight,
iFrameTopOffset: innerIFrame.offsetTop,
scrollY: window.scrollY,
},
}),
'*',
);
break;
}
case 'scrollTo': {
window.scroll(0, Number(parsedData.payload.scrollTo));
break;
}
case 'iframeTop': {
window.scroll(0, Number(message.payload.topOffset) + innerIFrame.offsetTop);
break;
}
}
} catch (e) {}
});
</script>
</head>
<body>
<iframe
id="inner_iframe"
src="<URL_TO_INNER_HTML>"
style="
width: 100%;
height: 100%;
min-height: 500px;
border: 0;
">
</iframe>
</body>
</html>
Custom CSS
To customize the look of the games, you need to edit the value of the predefined CSS variables.
List of possible variables with example values:
:root {
/* Main */
--primary: ...;
--secondary: ...;
--secondary-light: ...;
--error: ...;
--success: ...;
/* Text */
--text-light: ...;
--text-dark: ...;
--text: ...;
--text-neutral: ...;
--text-neutral-light: ...;
/* Font family */
--font-main: ...;
--font-accent: ...;
/* Generic elements */
--input: ...;
--input-border: ...;
--border: ...;
--page-background: ...;
--entity-background: ...;
--entity-item-background: ...;
--entity-item-border: ...;
/* Elements */
--participant-row: ...;
--participant-row-active: ...;
--participant-info-box: ...;
--color-chart-gradient: ...;
--video-elements-background: ...;
--scroll-to-top-size: ...;
--scroll-to-top-bottom-offset: ...;
--scroll-to-top-right-offset: ...;
}