diff --git a/src/providers/AdobeHeartbeat.js b/src/providers/AdobeHeartbeat.js index a782ae5..7b93141 100644 --- a/src/providers/AdobeHeartbeat.js +++ b/src/providers/AdobeHeartbeat.js @@ -11,7 +11,7 @@ class AdobeHeartbeatProvider extends BaseProvider { super(); this._key = "ADOBEHEARTBEAT"; - this._pattern = /\.hb\.omtrdc\.net\//; + this._pattern = /\.hb\.omtrdc\.net\/|\/api\/v1\/sessions/; this._name = "Adobe Heartbeat"; this._type = "analytics"; this._keywords = ["video"]; @@ -25,7 +25,7 @@ class AdobeHeartbeatProvider extends BaseProvider get columnMapping() { return { - "account": "s:sc:rsid", + "account": "omnibug_account", "requestType": "omnibug_requestType" }; } @@ -41,6 +41,30 @@ class AdobeHeartbeatProvider extends BaseProvider { "key": "general", "name": "General" + }, + { + "key": "customMetadata", + "name": "Custom Meta Data" + }, + { + "key": "analytics", + "name": "Analytics" + }, + { + "key": "visitorID", + "name": "Visitor ID" + }, + { + "key": "media", + "name": "Media Content" + }, + { + "key": "ads", + "name": "Media Ads" + }, + { + "key": "player", + "name": "Player" } ]; } @@ -161,12 +185,309 @@ class AdobeHeartbeatProvider extends BaseProvider "name": "Event Type", "group": "general" }, - "omnibug_requestType": { - "hidden": true - } + "params.analytics.trackingServer": { + "name": "Tracking Server", + "group": "analytics" + }, + "params.analytics.reportSuite": { + "name": "Report Suite", + "group": "analytics" + }, + "params.analytics.enableSSL": { + "name": "Enable SSL", + "group": "analytics" + }, + "params.analytics.visitorId": { + "name": "Analytics Visitor ID", + "group": "visitorID" + }, + "params.visitor.marketingCloudOrgId": { + "name": "Experience Cloud Org ID", + "group": "visitorID" + }, + "params.visitor.marketingCloudUserId": { + "name": "Experience Cloud User ID", + "group": "visitorID" + }, + "params.visitor.aamLocationHint": { + "name": "Adobe Audience Manager Edge Data", + "group": "visitorID" + }, + "params.appInstallationId": { + "name": "App/Device ID", + "group": "visitorID" + }, + "params.analytics.optOutServerSideForwarding ": { + "name": "Analytics Server-Side Opt Out", + "group": "visitorID" + }, + "params.analytics.optOutShare ": { + "name": "Analytics Federated Opt Out", + "group": "visitorID" + }, + "params.media.state.name": { + "name": "Media State", + "group": "media" + }, + "params.media.id": { + "name": "Content ID", + "group": "media" + }, + "params.media.name": { + "name": "Content Name", + "group": "media" + }, + "params.media.length": { + "name": "Content Length", + "group": "media" + }, + "params.media.contentType": { + "name": "Content Format", + "group": "media" + }, + "params.media.streamType": { + "name": "Stream Type", + "group": "media" + }, + "params.media.playerName": { + "name": "Player Name", + "group": "player" + }, + "params.media.channel": { + "name": "Content Channel", + "group": "media" + }, + "params.media.resume": { + "name": "Session Resume", + "group": "media" + }, + "params.media.sdkVersion": { + "name": "SDK Name", + "group": "general" + }, + "params.media.libraryVersion": { + "name": "SDK Version", + "group": "general" + }, + "params.media.show": { + "name": "Show / Series Name", + "group": "media" + }, + "params.media.season": { + "name": "Season Number", + "group": "media" + }, + "params.media.episode": { + "name": "Episode Number", + "group": "media" + }, + "params.media.assetId": { + "name": "Asset ID", + "group": "media" + }, + "params.media.genre": { + "name": "Genre", + "group": "media" + }, + "params.media.firstAirDate": { + "name": "First Air Date", + "group": "media" + }, + "params.media.firstDigitalDate": { + "name": "First Digital Date", + "group": "media" + }, + "params.media.rating": { + "name": "Rating", + "group": "media" + }, + "params.media.originator": { + "name": "Originator", + "group": "media" + }, + "params.media.network": { + "name": "Network", + "group": "media" + }, + "params.media.showType": { + "name": "Content Type", + "group": "media" + }, + "params.media.adLoad": { + "name": "Ad Type", + "group": "ads" + }, + "params.media.pass.mvpd": { + "name": "MVPD", + "group": "media" + }, + "params.media.pass.auth": { + "name": "Adobe Auth", + "group": "media" + }, + "params.media.dayPart": { + "name": "Timeparting", + "group": "media" + }, + "params.media.feed": { + "name": "Feed Type", + "group": "media" + }, + "params.media.ad.podFriendlyName": { + "name": "Ad Break Name", + "group": "ads" + }, + "params.media.ad.podIndex": { + "name": "Ad Break Index", + "group": "ads" + }, + "params.media.ad.podSecond": { + "name": "Ad Break Start Time", + "group": "ads" + }, + "params.media.ad.podPosition": { + "name": "Ad Break Position", + "group": "ads" + }, + "params.media.ad.name": { + "name": "Ad Name", + "group": "ads" + }, + "params.media.ad.id": { + "name": "Ad ID", + "group": "ads" + }, + "params.media.ad.length": { + "name": "Ad Length", + "group": "ads" + }, + "params.media.ad.playerName": { + "name": "Ad Player Name", + "group": "ads" + }, + "params.media.ad.advertiser": { + "name": "Advertiser", + "group": "ads" + }, + "params.media.ad.campaignId": { + "name": "Ad Campaign ID", + "group": "ads" + }, + "params.media.ad.creativeId": { + "name": "Ad Creative ID", + "group": "ads" + }, + "params.media.ad.siteId": { + "name": "Ad Site ID", + "group": "ads" + }, + "params.media.ad.creativeURL": { + "name": "Ad Creative URL", + "group": "ads" + }, + "params.media.ad.placementId": { + "name": "Ad Placement ID", + "group": "ads" + }, + "params.media.chapter.index": { + "name": "Chapter Index", + "group": "media" + }, + "params.media.chapter.offset": { + "name": "Chapter Time Start", + "group": "media" + }, + "params.media.chapter.length": { + "name": "Chapter Length", + "group": "media" + }, + "params.media.chapter.friendlyName": { + "name": "Chapter Name", + "group": "media" + }, + "qoeData.media.qoe.bitrate": { + "name": "Player Bitrate", + "group": "player" + }, + "qoeData.media.qoe.droppedFrames": { + "name": "Dropped Frames", + "group": "player" + }, + "qoeData.media.qoe.framesPerSecond": { + "name": "Frames Per Second", + "group": "player" + }, + "qoeData.media.qoe.timeToStart": { + "name": "Time to Start", + "group": "player" + }, + "eventType": { + "name": "Event Type", + "group": "general" + }, }; } + + /** + * Parse a given URL parameter into human-readable form + * + * @param {string} name + * @param {string} value + * + * @returns {void|{}} + */ + handleQueryParam(name, value) + { + let result = {}; + if(/^customMetadata\./i.test(name)) { + result = { + "key": name, + "field": "prop" + RegExp.$1, + "value": value, + "group": "props" + }; + } else if(/^params\.analytics\./i.test(name) && !(name in this.keys)) { + result = { + "key": name, + "field": name.replace(/^params\.analytics\./i, ""), + "value": value, + "group": "analytics" + }; + } else if(/^params\.visitor\./i.test(name) && !(name in this.keys)) { + result = { + "key": name, + "field": name.replace(/^params\.visitor\./i, ""), + "value": value, + "group": "visitorID" + }; + } else if(/^params\.media\.ad\./i.test(name) && !(name in this.keys)) { + result = { + "key": name, + "field": name.replace(/^params\.media\.ad\./i, ""), + "value": value, + "group": "ads" + }; + } else if(/^params\.media\./i.test(name) && !(name in this.keys)) { + result = { + "key": name, + "field": name.replace(/^params\.media\./i, ""), + "value": value, + "group": "media" + }; + } else if(/^(playerTime|media\.player)?\./i.test(name) && !(name in this.keys)) { + result = { + "key": name, + "field": name.replace(/^(playerTime|media\.player)\./i, ""), + "value": value, + "group": "player" + }; + } else { + result = super.handleQueryParam(name, value); + } + return result; + } + /** * Parse custom properties for a given URL * @@ -177,8 +498,31 @@ class AdobeHeartbeatProvider extends BaseProvider */ handleCustom(url, params) { - let results = [], + let results = [], event = "", account = ""; + + if(/\/api\/v1\/sessions\/?(([^/?#]+))?/.test(url)) { + // Media SDK v3 + event = params.get("eventType") || ""; + account = params.get("params.analytics.reportSuite"); + if(RegExp.$1) { + results.push({ + "key": "omnibug_sessionID", + "field": "Media Session ID", + "value": RegExp.$1, + "group": "general" + }); + } + } else { + // Media SDK v1/v2 event = params.get("s:event:type"); + account = params.get("s:sc:rsid"); + } + + results.push({ + "key": "omnibug_account", + "value": account, + "hidden": true + }); results.push({ "key": "omnibug_requestType", "value": event.charAt(0).toUpperCase() + event.slice(1), @@ -186,4 +530,4 @@ class AdobeHeartbeatProvider extends BaseProvider }); return results; } -} \ No newline at end of file +} diff --git a/test/providers/AdobeHeartbeat.js b/test/providers/AdobeHeartbeat.js index 6228c3b..c6d8b4f 100644 --- a/test/providers/AdobeHeartbeat.js +++ b/test/providers/AdobeHeartbeat.js @@ -15,6 +15,10 @@ test("AdobeHeartbeatProvider pattern should match Adobe Heartbeat calls", t => { let provider = new AdobeHeartbeatProvider(), urls = [ "https://nbcume.hb.omtrdc.net/?s:sc:rsid=nbcutve%2Cnbcunetworkbu&s:sc:tracking_server=nbcume.sc.omtrdc.net&h:sc:ssl=1&s:user:aid=2D859E6B85035994-4000118EA001378C&s:user:mid=07193930379656792541444603315072046627&s:user:id=2D859E6B85035994-4000118EA001378C&s:aam:blob=RKhpRz8krg2tLO6pguXWp5olkAcUniQYPHaMWWgdJ3xzPWQmdj0y&l:aam:loc_hint=9&s:sp:ovp=theplatform&s:sp:sdk=mpx-javascript-player-sdk&s:sp:channel=On-Domain&s:sp:player_name=PDK%206%20-%20NBC.com%20Instance%20of%3A%20rational-player-production&s:sp:hb_version=js-n-1.6.9.113-3dff70&l:sp:hb_api_lvl=4&s:event:sid=1527464150037459529420&s:event:type=start&l:event:duration=0&l:event:playhead=0&l:event:ts=1527464150048&l:event:prev_ts=-1&s:asset:type=main&s:asset:name=Town%20Hall&s:asset:video_id=3710748&s:asset:publisher=A8AB776A5245B4220A490D44%40AdobeOrg&l:asset:length=1330&s:stream:type=VOD%20Episode&l:stream:bitrate=0&l:stream:fps=0&l:stream:dropped_frames=0&l:stream:startup_time=0&s:meta:videoprogram=Superstore&s:meta:videotitle=Town%20Hall&s:meta:videodaypart=Primetime&s:meta:videominute=16%3A35&s:meta:videohour=16%3A00&s:meta:videoday=Sunday&s:meta:videodate=05%2F27%2F2018&s:meta:videoplayertech=HTML5%20Desktop&s:meta:videoplatform=PC&s:meta:videonetwork=NBC%20Entertainment&s:meta:videoinitiate=Manual&s:meta:videocliptype=Full%20Episode&s:meta:videosubcat1=Comedy&s:meta:videoscreen=Normal&s:meta:videostatus=Unrestricted&s:meta:videoplayerurl=https%3A%2F%2Fwww.nbc.com%2Fsuperstore%2Fvideo%2Ftown-hall%2F3710748&s:meta:videoepnumber=22&s:meta:videoguid=3710748&s:meta:videoairdate=5%2F3%2F2018&s:meta:videoseason=3&s:meta:videodescription=Glenn%20gets%20stage%20fright%20when%20he%20realizes%20his%20speech%20for%20Cloud%209%27s%20town%20hall%20will%20be%20broadcast%20live%20in%20stores%20around%20the%20world%2C%20and%20Amy%20and%20Jonah%20set%20out%20to%20discover%20the%20truth%20about%20a%20recent%20employee%20firing%20after%20Laurie%20makes%20a%20suspicious%20comment.&s:meta:a.nielsen.clientid=us-800148&s:meta:a.nielsen.vcid=c05&s:meta:a.nielsen.appid=P27B8D04D-DDA7-456D-954F-7F032457B022&s:meta:a.nielsen.program=Superstore&s:meta:a.nielsen.sega=Town%20Hall&s:meta:a.nielsen.admodel=2&s:meta:a.nielsen.accmethod=0&s:meta:a.nielsen.ctype=VIDEO", + "https://obumobile5.hb-api.omtrdc.net/api/v1/sessions", + "https://obumobile5.hb-api.omtrdc.net/api/v1/sessions/139bd3c6f625bfb71e40a328e40512e3c506035d4d0dad024587ba2b442a5cb0/events", + "https://hb-test.omnibug.io/api/v1/sessions", + "https://hb-test.omnibug.io/api/v1/sessions/139bd3c6f625bfb71e40a328e40512e3c506035d4d0dad024587ba2b442a5cb0/events", ]; urls.forEach((url) => { @@ -24,7 +28,7 @@ test("AdobeHeartbeatProvider pattern should match Adobe Heartbeat calls", t => { t.false(provider.checkUrl("https://omnibug.io/testing"), "Provider should not match on non-provider based URLs"); }); -test("OmnibugProvider returns Adobe Heartbeat", t => { +test("OmnibugProvider returns Adobe Heartbeat v1/v2", t => { let url = "https://nbcume.hb.omtrdc.net/?s:sc:rsid=nbcutve%2Cnbcunetworkbu&s:sc:tracking_server=nbcume.sc.omtrdc.net&h:sc:ssl=1&s:user:aid=2D859E6B85035994-4000118EA001378C&s:user:mid=07193930379656792541444603315072046627&s:user:id=2D859E6B85035994-4000118EA001378C&s:aam:blob=RKhpRz8krg2tLO6pguXWp5olkAcUniQYPHaMWWgdJ3xzPWQmdj0y&l:aam:loc_hint=9&s:sp:ovp=theplatform&s:sp:sdk=mpx-javascript-player-sdk&s:sp:channel=On-Domain&s:sp:player_name=PDK%206%20-%20NBC.com%20Instance%20of%3A%20rational-player-production&s:sp:hb_version=js-n-1.6.9.113-3dff70&l:sp:hb_api_lvl=4&s:event:sid=1527464150037459529420&s:event:type=start&l:event:duration=0&l:event:playhead=0&l:event:ts=1527464150048&l:event:prev_ts=-1&s:asset:type=main&s:asset:name=Town%20Hall&s:asset:video_id=3710748&s:asset:publisher=A8AB776A5245B4220A490D44%40AdobeOrg&l:asset:length=1330&s:stream:type=VOD%20Episode&l:stream:bitrate=0&l:stream:fps=0&l:stream:dropped_frames=0&l:stream:startup_time=0&s:meta:videoprogram=Superstore&s:meta:videotitle=Town%20Hall&s:meta:videodaypart=Primetime&s:meta:videominute=16%3A35&s:meta:videohour=16%3A00&s:meta:videoday=Sunday&s:meta:videodate=05%2F27%2F2018&s:meta:videoplayertech=HTML5%20Desktop&s:meta:videoplatform=PC&s:meta:videonetwork=NBC%20Entertainment&s:meta:videoinitiate=Manual&s:meta:videocliptype=Full%20Episode&s:meta:videosubcat1=Comedy&s:meta:videoscreen=Normal&s:meta:videostatus=Unrestricted&s:meta:videoplayerurl=https%3A%2F%2Fwww.nbc.com%2Fsuperstore%2Fvideo%2Ftown-hall%2F3710748&s:meta:videoepnumber=22&s:meta:videoguid=3710748&s:meta:videoairdate=5%2F3%2F2018&s:meta:videoseason=3&s:meta:videodescription=Glenn%20gets%20stage%20fright%20when%20he%20realizes%20his%20speech%20for%20Cloud%209%27s%20town%20hall%20will%20be%20broadcast%20live%20in%20stores%20around%20the%20world%2C%20and%20Amy%20and%20Jonah%20set%20out%20to%20discover%20the%20truth%20about%20a%20recent%20employee%20firing%20after%20Laurie%20makes%20a%20suspicious%20comment.&s:meta:a.nielsen.clientid=us-800148&s:meta:a.nielsen.vcid=c05&s:meta:a.nielsen.appid=P27B8D04D-DDA7-456D-954F-7F032457B022&s:meta:a.nielsen.program=Superstore&s:meta:a.nielsen.sega=Town%20Hall&s:meta:a.nielsen.admodel=2&s:meta:a.nielsen.accmethod=0&s:meta:a.nielsen.ctype=VIDEO"; let results = OmnibugProvider.parseUrl(url); @@ -32,5 +36,36 @@ test("OmnibugProvider returns Adobe Heartbeat", t => { t.is(results.provider.key, "ADOBEHEARTBEAT", "Results provider is Adobe Heartbeat"); }); +test("OmnibugProvider returns Adobe Heartbeat v3", t => { + let url = "https://obumobile5.hb-api.omtrdc.net/api/v1/sessions"; + + let results = OmnibugProvider.parseUrl(url); + t.true(typeof results === "object" && results !== null, "Results is a non-null object"); + t.is(results.provider.key, "ADOBEHEARTBEAT", "Results provider is Adobe Heartbeat"); +}); + +test("AdobeHeartbeatProvider returns data for v3", t => { + let provider = new AdobeHeartbeatProvider(), + url = "https://obumobile5.hb-api.omtrdc.net/api/v1/sessions/139bd3c6f625bfb71e40a328e40512e3c506035d4d0dad024587ba2b442a5cb0/events", + postData = `{"eventType":"adStart","playerTime":{"playhead":14.965013,"ts":1607292775736},"customMetadata":{"affiliate":"Sample affiliate","campaign":"Sample ad campaign"},"params":{"media.ad.name":"Sample ad","media.ad.id":"001","media.ad.length":15,"media.ad.podPosition":1,"media.ad.advertiser":"Sample Advertiser","media.ad.campaignId":"Sample Campaign","media.ad.playerName":"HTML5 basic media player"}}`; + + let results = provider.parseUrl(url, postData), + eventType = results.data.find(r => r.key === "eventType"), + playhead = results.data.find(r => r.key === "playerTime.playhead"), + sessionId = results.data.find(r => r.key === "omnibug_sessionID"); + + t.is(eventType.field, "Event Type"); + t.is(eventType.value, "adStart"); + t.is(eventType.group, "general"); + + t.is(playhead.field, "playhead"); + t.is(playhead.value, "14.965013"); + t.is(playhead.group, "player"); + + t.is(sessionId.field, "Media Session ID"); + t.is(sessionId.value, "139bd3c6f625bfb71e40a328e40512e3c506035d4d0dad024587ba2b442a5cb0"); + t.is(sessionId.group, "general"); +}); + test.todo("AdobeHeartbeatProvider returns static data"); -test.todo("AdobeHeartbeatProvider returns custom data"); \ No newline at end of file +test.todo("AdobeHeartbeatProvider returns custom data");