As part of Integration for FANXP it is quite often required to implement native visual for the campaign inside mobile application. The simplest option would be to use web view and native url for the campaign or WPA like implementation, however our server supports and provides API for fully native implementation
Overall in order to build FanXP experience into native app you need to integrate following components
- Authentication with email/password
- Handler for forgotten password
- Handler for new registration
- Initialization of WebSocket Connection (Socket.IO)
- Video Player integration (Native control for HLS feed)
- Handler for Quiz/Clicker/Static Overlay events
- Chat Integration
- Style and Look&Feel synchronization between Web and Native FanXP
Examples are written in python, but obviously appropriate language has to be used in real implementation
Last Update November 30, 2020Authentication allows you to obtain session_uid, which is used for all following calls. First step is to draw some configuration information to help with visuals.
import requests
email = "test@example.com"
data = {"api_key":"b0dbe529-19a6-4323-8a58-8a95ffcbfb1c"}
res = requests.request("POST","https://tradablebits.com/api/v1/status",data=data)
if res.status_code == 200:
result = res.json()
legal_terms = result.get('legal_terms')
legal_privacy = result.get('legal_privacy')
header_text = result.get('header_text')
image_url = result.get('image_url')
else:
print("Server Error")
Once the form is displayed, the next step is to check for email validity and define whether login or registration flow must be used. Keep in mind, this call is throttled to restrict crawling.
import requests
email = "test@example.com"
data = {"api_key":"b0dbe529-19a6-4323-8a58-8a95ffcbfb1c","network":"email","email":email}
res = requests.request("POST","https://tradablebits.com/api/v1/sessions/connect",data=data)
if res.status_code == 200:
result = res.json()
if result["status"] == "register":
print("Register")
elif result['status'] == "login":
print("Fan found, now must log in.")
has_phone = result.get('phone',False)
has_password = result.get('password',False)
terms_required = result.get('terms_required',False)
else:
print("Server Error")
Once you know that the email is legit, you can choose to log in via password (if configured), email verification, or SMS verification (if the fan has a phone number stored). Code below covers password login.
import requests
email = "test@example.com"
password = "password123"
data = {"api_key":"b0dbe529-19a6-4323-8a58-8a95ffcbfb1c","network":"email","email":email,"password":password}
res = requests.request("POST","https://tradablebits.com/api/v1/sessions/connect",data=data)
if res.status_code == 200:
result = res.json()
session_uid = result['session_uid']
fan_id = result['fan_id']
print("Valid Login")
else:
print("Invalid Login/Pass")
In the case of invalid password or nonexistent password, you can prompt to send email or sms with verification code
import requests
email = "test@example.com"
# set network to verify_sms for SMS notification, verify_email for email notification
data = {"api_key":"b0dbe529-19a6-4323-8a58-8a95ffcbfb1c","network":"verify_email","email":email}
res = requests.request("POST","https://tradablebits.com/api/v1/sessions/connect",data=data)
if res.status_code == 200:
result = res.json()
status = result['status']
message = result['message']
request_uid = result['request_uid']
print("Request", request_uid, message)
else:
print("Error",res.text)
Once user gets email/sms with code, login can be completed via following call
import requests
email = "test@example.com"
request_uid = "36e26cf6-432c-4197-8788-fd1dac98e5f3"
reset_code = "49270"
data = {"api_key":"b0dbe529-19a6-4323-8a58-8a95ffcbfb1c","network":"submit_verification_code","email":email,"verification_code":verification_code,"request_uid":request_uid}
res = requests.request("POST","https://tradablebits.com/api/v1/sessions/connect",data=data)
if res.status_code == 200:
result = res.json()
session_uid = result['session_uid']
fan_id = result['fan_id']
print("Login Success", fan_id, session_uid)
else:
print("Error", res.text)
Once the session is retrieved, you may prompt the fan to create or update their password.
import requests
email = "test@example.com"
data = {"api_key":"b0dbe529-19a6-4323-8a58-8a95ffcbfb1c","password":"newPassword"}
session_uid = "abcdefij-1111-2222-3333-444455556666"
res = requests.request("POST",f"https://tradablebits.com/api/v1/sessions/{session_uid}/fan",data=data)
if res.status_code == 200:
result = res.json()
print(result)
else:
print("Error", res.text)
If email is not known to the system, application may decide to register new fan record
import requests
email = "test@example.com"
data = {"api_key":"b0dbe529-19a6-4323-8a58-8a95ffcbfb1c","network":"register","email":email,"first_name":"Test","last_name":"Test"}
res = requests.request("POST","https://tradablebits.com/api/v1/sessions/connect",data=data)
if res.status_code == 200:
result = res.json()
session_uid = result['session_uid']
fan_id = result['fan_id']
print("Login Success", fan_id, session_uid)
else:
print("Error", res.text)
Once you have the credentials sorted out, next step will be to get a list of available campaigns. Campaigns are grouped in the entity called Business, which represents a team. Calls below allow you to fetch all businesses configured on the account and campaigns, which are currently scheduled to be included (e.g. needs to be presented to the user). MediaMap includes a map to all assets uploaded as part of campaign. Assets can be rendered via adding link https://tradablebits.com/fb_media/11111111-1111-1111-11111111111111
import requests
data = {"api_key":"9eb875ac-6baf-4ee9-b189-afc725cba7f6"}
res = requests.request("GET","https://tradablebits.com/api/v1/businesses",params=data)
if res.status_code == 200:
result = res.json()
for row in result:
print(row['business_id'])
print(row['business_name'])
print(row['legal_terms'])
print(row['legal_rules'])
print(row['legal_privacy'])
print(row['scheduler_props'])
print(row['media_map'])
if row['media_map'].get('header_image',{}).get("1",None):
header_image = "https://tradablebits.com/fb_media/%s" % row['media_map'].get('header_image',{}).get("1",None)
else:
print("Error")
data = {"api_key":"9eb875ac-6baf-4ee9-b189-afc725cba7f6","app_type":"fanxp","is_scheduled":"true", "business_id":12345}
res = requests.request("GET","https://tradablebits.com/api/v1/apps",params=data)
if res.status_code == 200:
result = res.json()
for row in result:
print('campaign',row)
else:
print("Error")
Once you choose a specific campaign, your application can start integration into dependencies for full FanXP experience. In particular, connect into Socket IO for control messages and chat, fetch video configuration and setup a listener for quiz push.
First, we need to check authorization. This will check CRM gate and provide back details for campaign to connect
import requests
data = {"session_uid":"28c02479-068d-4600-bcab-fc4fcfbc43af"}
res = requests.request("GET","https://tradablebits.com/api/v1/fanxp/12272",params=data)
if res.status_code == 200:
result = res.json()
account_id = result['account_id']
ws_rooms = result['rooms']
chat_rooms = [x for x in filter(lambda x: x['room_type'] == "public_chat", ws_rooms)]
control_rooms = [x for x in filter(lambda x: x['room_type'] == "control", ws_rooms)]
if len(control_rooms) > 0: control_room = control_rooms[0]
if len(chat_rooms) > 0: chat_room = chat_rooms[0]
print(account_id)
print(chat_room)
else:
print(res.text)
print("Error")
Once, authorization, it obtained we need to initialize the connection to Socket IO Server. Please keep in mind, websocket server allows only a single connection per fan and any following connection will eliminate the past one.
import requests
import socketio
import asyncio
sio = socketio.AsyncClient(request_timeout=1)
account_id = 1
session_uid = "28c02479-068d-4600-bcab-fc4fcfbc43af"
room_name = "public_chat:room:12272"
def emit_callback(res):
print("Result:", res)
async def async_run():
await sio.connect('https://websockets.tbits.me/')
data = {"account_id": account_id, "session_uid": session_uid, "room_name": room_name}
await sio.emit("join", data, callback=emit_callback)
message = "Hello World"
data = {"account_id": account_id, "room_name": room_name, "message": message}
await sio.emit('chat_message', data, callback=emit_callback)
await sio.wait()
asyncio.run(async_run())
With websocket connection you need to listen for the following events
chat_message: This event includes a new chat message. Chat from the user is generated as the same event.
{
chat_message_uid: "[uuid]",
message: "html escaped message",
room_name: "[room_name]",
display_name: "user handle for the creator",
unix_timestamp: "creation timestamp",
fan_id: "ID for the user, who created a message",
moderator_status: "null|moderator"
}
tbits_live_update: This event is sent on the control room and will include a state change for the performance
// 1. Status change for the campaign:
{ "action": "state_change",
"state": "off|pending|active|finished",
"items": {
"vods": [{
"activity_id": null,
"clicks": 0,
"creation_timestamp": 1602874546,
"description": null,
"fan_id": null,
"fb_photo_id": null,
"fb_photo_url": "https://example.com/example.png",
"field_idx": 0,
"impressions": 0,
"is_approved": null,
"is_correct": true,
"media_uid": "XXXXX-XXXX-XXXXX-XXXXXXXXX",
"negative_vote_count": 0,
"option_id": 1,
"option_name": "first vod",
"option_type": "vimeo",
"page_tab_id": 1
}],
"home_items": {
"live_overlay": {
"overlay_uid": "XXXXX-XXXX-XXXXX-XXXXXXXXX",
"overlay_type": "static",
"overlay_public_name": "Temporary item in home tab",
"body_text": "Title for temporary item in home tab",
"target_url": "https://example.com",
"button_text": "Click here",
"content_media_uid": "XXXXX-XXXX-XXXXX-XXXXXXXXX",
"sponsor_media_uid": "XXXXX-XXXX-XXXXX-XXXXXXXXX",
"sponsor_image_url": "https://example.com/example.png",
"sponsor_name": "tradablebits",
"last_update_timestamp": 1602872546
},
"pinned_overlays": [
{
"overlay_uid": "XXXXX-XXXX-XXXXX-XXXXXXXXX",
"overlay_type": "static",
"overlay_public_name": "Item pinned in home tab",
"body_text": "Item pinned in home tab title",
"target_url": "https://example.com",
"button_text": "Click here",
"content_media_uid": "XXXXX-XXXX-XXXXX-XXXXXXXXX",
"sponsor_media_uid": "XXXXX-XXXX-XXXXX-XXXXXXXXX",
"sponsor_image_url": "https://example.com/example.png",
"sponsor_name": "tradablebits"
}
]
}
}
}
// 2. Live Feed update (new Feed):
{ "action": "update_live_feed",
"live_feed": {
"feed_name": "character feed",
"video_endpoint_uid": "XXXX-XXXX-XXXX-XXXXXXXX",
"vimeo_key": null,
"vimeo_event_key": 1234,
}
"live_overlay": {
}
}
// 3. Live Overlay update (new Clicker):
{ 'action': "update_live_overlay",
'live_feed': {},
'live_overlay': {
'overlay_type': 'clicker',
'clicker_instance_uid': "XXXX-XXXX-XXXX-XXXXXXXX",
'overlay_public_name': "test,
'content_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'sponsor_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'sponsor_image_url': "http://www.google.ca",
'sponsor_name': 'google',
'overlay_timer': 30,
'overlay_delay': 5,
'clicker_goal': 100000
'last_push_timestamp': 1603409607
'overlay_log_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
'overlay_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
}
}
// 4. Live Overlay update (new Static Overlay):
{ 'action': "update_live_overlay",
'live_feed': {},
'live_overlay': {
'overlay_type': 'static',
'overlay_public_name': "testing",
'sponsor_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'content_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'sponsor_image_url': "http://www.google.ca",
'button_text': "click me",
'body_text': "some test message",
'target_url': "https://www.tradablebits.com",
'sponsor_image_url': "http://www.google.ca",
'sponsor_name': 'google',
'last_push_timestamp': 1603409607
'overlay_log_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
'overlay_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
}
}
// 5. Live Overlay update (new Question):
{ 'action': "update_live_overlay",
'live_feed': {},
'live_overlay': {
'overlay_type': 'quiz_question',
'quiz_question_id': 123121,
'overlay_public_name': "what is the answer to the ultimate question in the universe",
'options': [{"answer_idx":1,"answer":42}],
'sponsor_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'content_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'sponsor_image_url': "http://www.google.ca",
'sponsor_name': 'google',
'show_results': true,
'overlay_timer': 30,
'last_push_timestamp': 1603409607
'overlay_log_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
'overlay_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
}
}
// 6. Live Overlay Update (new Custom Field Overlay)
{ 'action': "update_live_overlay",
'live_feed': {},
'live_overlay': {
'overlay_type': "custom_field"
'overlay_public_name': "Tell me something"
'sponsor_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'content_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'sponsor_image_url': "http://www.google.ca",
'sponsor_name': 'google',
'crm_field_key': "some_key"
'custom_field':{
crm_field_key: "some_key",
field_type: "select",
},
'last_push_timestamp': 1603409607
'overlay_log_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
'overlay_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
'overlay_timer': 10
}
}
// 7. Live Overlay update (new Banner Overlay):
{ 'action': "update_live_overlay",
'live_feed': {},
'live_overlay': {
'overlay_type': 'banner',
'overlay_public_name': "some name",
'sponsor_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'content_media_uid': 'XXXXX-XXXX-XXXXX-XXXXXXXXX',
'sponsor_image_url': "http://www.google.ca",
'sponsor_name': 'google',
'last_push_timestamp': 1603409607
'overlay_log_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
'overlay_uid': "XXXXX-XXXX-XXXXX-XXXXXXXXX"
}
}
Following are API calls, which allow to communicate various activities: clicker, quiz, etc. They all follow a similar pattern, so examples are grouped together
POST https://tradablebits.com/api/v1/fanxp/[page_tab_id]/clicker Push clicker counter into the server
GET https://tradablebits.com/api/v1/fanxp/[page_tab_id]/answer Get quiz question result for a given question
POST https://tradablebits.com/api/v1/fanxp/[page_tab_id]/answer Post Result on the question
GET https://tradablebits.com/api/v1/fanxp/[page_tab_id]/leaderboard Return public leaderboard
GET https://tradablebits.com/api/v1/fanxp/[page_tab_id]/chat_messages Get Last N messages for a given channel
POST https://tradablebits.com/api/v1/fanxp/[page_tab_id]/report_chat Report a chat message
# get leaderboard
data = {"session_uid":"28c02479-068d-4600-bcab-fc4fcfbc43af",api_key="28c02479-068d-4600-bcab-fc4fcfbc43af"}
res = requests.request("GET","https://tradablebits.com/api/v1/fanxp/12272/leaderboard",params=data)
result = res.json()
print('res',result)
Video stream is currently delivered in the form of HLS feed. Authenticated request to following endpoint will return either redirect or raw origin manifest file, which can and should be integrated into native player. Depending of the device you may want to include "prefetch" parameter to obtain origin manifest directly in the body of the response versus redirect
First, you can and should get a list of available video feeds
import requests
data = {"session_uid":"28c02479-068d-4600-bcab-fc4fcfbc43af"}
res = requests.request("GET","https://tradablebits.com/application/fanxp_live_video_endpoint/12272",params=data)
if res.status_code == 200:
result = res.json()
print('feeds',result)
else:
print(res.text)
print("Error")
Getting the manifest itself
import requests
data = {"session_uid":"28c02479-068d-4600-bcab-fc4fcfbc43af","prefetch":"true","video_endpoint_uid":"xxxxxx" }
res = requests.request("GET","https://tradablebits.com/application/hls/12345",params=data)
if res.status_code == 200:
result = res.text
print('manifest',result)
else:
print(res.text)
print("Error")