Skip to content

Commit

Permalink
Merge pull request #32 from SRGSSR/develop
Browse files Browse the repository at this point in the history
Deep link improvements
  • Loading branch information
pyby authored Aug 13, 2019
2 parents 6d644af + e7a951b commit c5387e1
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 64 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
language: java
dist: trusty
jdk:
- oraclejdk8
notifications:
Expand Down
6 changes: 4 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ Playfff is a SRG micro service to serve extra datas to Play applications. Playff

## Compatibility

The service uses Spring Boot, a postgresql database and a NodeJS server.
- The service uses Spring Boot, a postgresql database and a NodeJS server.
- For admin sessions and multi instances, only sticky sessions are supported.

## Installation

Expand All @@ -21,6 +22,7 @@ A wide list of parameters are available.
* `PFFF_PASSWORD` (optional, string): A user password to admin service.
* `DEEP_LINK_REFRESH_DELAY_MS` (optional, integer): Scheduled fixed delay before refreshing the deep link script cache. If not set, defaults is `300000`.
* `MAX_DEEP_LINK_REPORTS` (optional, integer): Maximum number of deep link reports in the database. If not set, defaults is `2500`.
* `DEEP_LINK_ENVIRONMENTS` (optional, string, multiple): List of `Environment`s to pull deep link dynamic informations. If not set, defaults is `PROD`.

## API
* `urn` (string): an unique identifier.
Expand Down Expand Up @@ -49,7 +51,7 @@ A wide list of parameters are available.
#### Deep link

* `/api/v1/deeplink/parsePlayUrl.js` (GET): Get the Play web URL to mobile application scheme URL script (deep link script). The HTTP ETag caching is supported.
* `/api/v1/deeplink/report` (POST) : create or update a new deep link report object from the JSON body object. Send a report only if the script returns `[scheme]://redirect`. The JSON object must contains:
* `/api/v1/deeplink/report` (POST) : create or update a new deep link report object from the JSON body object. Send a report only if the script returns `[scheme]://unsupported`. The JSON object must contains:
* `clientTime` (string): date of the parsing execution in `yyyy-MM-dd'T'HH:mm:ssXXX` format.
* `clientId` (string): Bundle id or package name.
* `jsVersion` (integer): the `parsePlayUrl.js` value of `parsePlayUrlVersion` variable.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<groupId>ch.srgssr</groupId>
<artifactId>playfff</artifactId>
<version>13</version>
<version>14</version>
<packaging>jar</packaging>

<name>pfff</name>
Expand Down
16 changes: 11 additions & 5 deletions src/main/java/ch/srgssr/playfff/model/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@
* License information is available from the LICENSE file.
*/
public enum Environment {
PROD("il.srgssr.ch"),
STAGE("il-stage.srgssr.ch"),
TEST("il-test.srgssr.ch"),
MMF("play-mmf.herokuapp.com");
PROD("il.srgssr.ch", "production"),
STAGE("il-stage.srgssr.ch", "stage"),
TEST("il-test.srgssr.ch", "test"),
MMF("play-mmf.herokuapp.com", "play mmf");

Environment(String url) {
Environment(String url, String prettyName) {
this.name = name().toLowerCase();
this.baseUrl = url;
this.prettyName = prettyName;
}

private String name;
private String baseUrl;
private String prettyName;

public static Environment fromValue(String v) {
return valueOf(v.toUpperCase());
Expand All @@ -29,6 +31,10 @@ public String getBaseUrl() {
return baseUrl;
}

public String getPrettyName() {
return prettyName;
}

@JsonValue
@Override
public String toString() {
Expand Down
138 changes: 90 additions & 48 deletions src/main/java/ch/srgssr/playfff/service/DeepLinkService.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
Expand All @@ -28,10 +29,7 @@
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.*;

/**
* Copyright (c) SRG SSR. All rights reserved.
Expand All @@ -46,14 +44,22 @@ public class DeepLinkService {

private RestTemplate restTemplate;

private Set<Environment> pullEnvironmentSet = new HashSet<>();

@Autowired
protected ApplicationContext applicationContext;

@Autowired
private IntegrationLayerRequest integrationLayerRequest;

public DeepLinkService(RestTemplateBuilder restTemplateBuilder) {
public DeepLinkService(RestTemplateBuilder restTemplateBuilder,
@Value("${DEEP_LINK_ENVIRONMENTS:PROD}") String environments) {
restTemplate = restTemplateBuilder.build();

String[] environmentStrings = environments.split(",");
for (String environmentString : environmentStrings) {
pullEnvironmentSet.add(Environment.fromValue(environmentString));
}
}

@Cacheable("DeeplinkParsePlayUrlJSContent")
Expand All @@ -69,42 +75,95 @@ public DeepLinkJSContent getParsePlayUrlJSContent() {
public synchronized DeepLinkJSContent refreshParsePlayUrlJSContent() {
String javascript = BaseResourceString.getString(applicationContext, "parsePlayUrl.js");

Map<String, String> buMap = new HashMap<String, String>();
buMap.put("srf", "www.srf.ch");
buMap.put("rts", "www.rts.ch");
buMap.put("rsi", "www.rsi.ch");
buMap.put("rtr", "www.rtr.ch");
buMap.put("swi", "play.swissinfo.ch");
Map<String, Map<String, Map<String, String>>> tvGlobalTopicsMap = new HashMap<>();
Map<String, Map<String, Map<String, String>>> tvGlobalEventsMap = new HashMap<>();

for (Environment environment : pullEnvironmentSet) {

Map<String, String> buProdMap = new HashMap<>();
buProdMap.put("srf", "www.srf.ch");
buProdMap.put("rts", "www.rts.ch");
buProdMap.put("rsi", "www.rsi.ch");
buProdMap.put("rtr", "www.rtr.ch");
buProdMap.put("swi", "play.swissinfo.ch");

Map<String, String> buStageMap = new HashMap<>();
buStageMap.put("srf", "srgplayer-srf.stage.srf.ch");
buStageMap.put("rts", "srgplayer-rts.stage.srf.ch");
buStageMap.put("rsi", "srgplayer-rsi.stage.srf.ch");
buStageMap.put("rtr", "srgplayer-rtr.stage.srf.ch");
buStageMap.put("swi", "srgplayer-swi.stage.srf.ch");

Map<String, String> buTestMap = new HashMap<>();
buTestMap.put("srf", "srgplayer-srf.test.srf.ch");
buTestMap.put("rts", "srgplayer-rts.test.srf.ch");
buTestMap.put("rsi", "srgplayer-rsi.test.srf.ch");
buTestMap.put("rtr", "srgplayer-rtr.test.srf.ch");
buTestMap.put("swi", "srgplayer-swi.test.srf.ch");

Map<Environment, Map<String, String>> buMap = new HashMap<>();
buMap.put(Environment.PROD, buProdMap);
buMap.put(Environment.STAGE, buStageMap);
buMap.put(Environment.TEST, buTestMap);
buMap.put(Environment.MMF, new HashMap<>());

// Get tv topic list
Map<String, Map<String, String>> tvTopicsMap = new HashMap<>();

for (Map.Entry<String, String> bu : buMap.get(environment).entrySet()) {
URI tvTopicListUri = null;
try {
tvTopicListUri = new URI("https", null, bu.getValue(), 443, "/play/tv/topicList",
null, null);

ResponseEntity<PlayTopic[]> tvTopicListResponseEntity = restTemplate.exchange(tvTopicListUri, HttpMethod.GET, null, PlayTopic[].class);

if (tvTopicListResponseEntity.getBody() != null) {
PlayTopic[] tvTopicList = tvTopicListResponseEntity.getBody();
Map<String, String> tvTopicsSubMap = new HashMap<>();

for (PlayTopic playTopic : tvTopicList) {
tvTopicsSubMap.put(playTopic.getUrlEncodedTitle(), playTopic.getId());
}

tvTopicsMap.put(bu.getKey(), tvTopicsSubMap);
}
} catch (Exception e) {
e.printStackTrace();
}
}

if (tvTopicsMap.size() > 0) {
tvGlobalTopicsMap.put(environment.getPrettyName(), tvTopicsMap);
}

ObjectMapper mapperObj = new ObjectMapper();
// Get event module list
Map<String, Map<String, String>> tvEventsMap = new HashMap<>();

// Get tv topic list
Map<String, Map<String, String>> tvTopicsMap = new HashMap<>();
for (Map.Entry<String, String> bu : buProdMap.entrySet()) {
ModuleConfigList moduleConfigList = integrationLayerRequest.getEvents(bu.getKey(), environment);
if (moduleConfigList != null) {
Map<String, String> tvEventsSubMap = new HashMap<>();

for (Map.Entry<String, String> bu : buMap.entrySet()) {
URI tvTopicListUri = null;
try {
tvTopicListUri = new URI("https", null, bu.getValue(), 443, "/play/tv/topicList",
null, null);
} catch (URISyntaxException e) {
e.printStackTrace();
}
ResponseEntity<PlayTopic[]> tvTopicListResponseEntity = restTemplate.exchange(tvTopicListUri, HttpMethod.GET, null, PlayTopic[].class);
if (tvTopicListResponseEntity.getBody() != null) {
PlayTopic[] tvTopicList = tvTopicListResponseEntity.getBody();
Map<String, String> tvTopicsSubMap = new HashMap<>();
for (int i = 0; i < moduleConfigList.getModuleConfigList().size(); i++) {
ModuleConfig moduleConfig = moduleConfigList.getModuleConfigList().get(i);
tvEventsSubMap.put(moduleConfig.getSeoName(), moduleConfig.getId());
}

for (PlayTopic playTopic : tvTopicList) {
tvTopicsSubMap.put(playTopic.getUrlEncodedTitle(), playTopic.getId());
tvEventsMap.put(bu.getKey(), tvEventsSubMap);
}
}

tvTopicsMap.put(bu.getKey(), tvTopicsSubMap);
if (tvEventsMap.size() > 0) {
tvGlobalEventsMap.put(environment.getPrettyName(), tvEventsMap);
}
}

ObjectMapper mapperObj = new ObjectMapper();

String tvTopics = null;
try {
tvTopics = mapperObj.writeValueAsString(tvTopicsMap);
tvTopics = mapperObj.writeValueAsString(tvGlobalTopicsMap);
} catch (IOException e) {
e.printStackTrace();
}
Expand All @@ -113,26 +172,9 @@ public synchronized DeepLinkJSContent refreshParsePlayUrlJSContent() {
javascript = javascript.replaceAll("\\/\\* INJECT TVTOPICS OBJECT \\*\\/", "var tvTopics = " + tvTopics + ";");
}

// Get event module list
Map<String, Map<String, String>> tvEventsMap = new HashMap<>();

for (Map.Entry<String, String> bu : buMap.entrySet()) {
ModuleConfigList moduleConfigList = integrationLayerRequest.getEvents(bu.getKey(), Environment.PROD);
if (moduleConfigList != null) {
Map<String, String> tvEventsSubMap = new HashMap<>();

for (int i = 0; i < moduleConfigList.getModuleConfigList().size(); i++) {
ModuleConfig moduleConfig = moduleConfigList.getModuleConfigList().get(i);
tvEventsSubMap.put(moduleConfig.getSeoName(), moduleConfig.getId());
}

tvEventsMap.put(bu.getKey(), tvEventsSubMap);
}
}

String tvEvents = null;
try {
tvEvents = mapperObj.writeValueAsString(tvEventsMap);
tvEvents = mapperObj.writeValueAsString(tvGlobalEventsMap);
} catch (IOException e) {
e.printStackTrace();
}
Expand Down
71 changes: 64 additions & 7 deletions src/main/resources/parsePlayUrl.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// parsePlayUrl

var parsePlayUrlVersion = 16;
var parsePlayUrlVersion = 19;
var parsePlayUrlBuild = "mmf";

function parsePlayUrl(urlString) {
Expand All @@ -18,10 +18,10 @@ function parsePlayUrl(urlString) {
queryParams[queryItem[0]] = queryItem[1];
}

return parseForPlayApp(url.hostname, url.pathname, queryParams, url.hash);
return parseForPlayApp(url.protocol.replace(':', ''), url.hostname, url.pathname, queryParams, url.hash.replace('#', ''));
}

function parseForPlayApp(hostname, pathname, queryParams, anchor) {
function parseForPlayApp(scheme, hostname, pathname, queryParams, anchor) {

// fix path issue
pathname = pathname.replace("//", "/");
Expand Down Expand Up @@ -50,6 +50,9 @@ function parseForPlayApp(hostname, pathname, queryParams, anchor) {
case hostname.includes("play-mmf") && pathname.startsWith("/mmf/"):
bu = "mmf";
break;
case hostname.includes("radioswisspop.ch") || hostname.includes("radioswissclassic.ch") || hostname.includes("radioswissjazz.ch"):
bu = "radioswiss";
break;
}

if (! bu) {
Expand Down Expand Up @@ -94,6 +97,31 @@ function parseForPlayApp(hostname, pathname, queryParams, anchor) {
}
}

/**
* Catch special case: radio swiss
*
* Ex: http://www.radioswisspop.ch/de/webplayer
* Ex: http://www.radioswissclassic.ch/fr/webplayer
* Ex: http://www.radioswissjazz.ch/it/webplayer
*/
if (bu == "radioswiss") {
var redirectBu = null;
switch (true) {
case pathname.startsWith("/de"):
redirectBu = "srf";
break;
case pathname.startsWith("/fr"):
redirectBu = "rts";
break;
case pathname.startsWith("/it"):
redirectBu = "rsi";
break;
}
if (redirectBu) {
return openURL(server, redirectBu, scheme, hostname, pathname, queryParams, anchor);
}
}

/**
* Catch special case: Play MMF
*
Expand Down Expand Up @@ -505,7 +533,7 @@ function parseForPlayApp(hostname, pathname, queryParams, anchor) {
/* INJECT TVTOPICS OBJECT */

if (typeof tvTopics !== 'undefined' && lastPathComponent.length > 0) {
topicId = tvTopics[bu][lastPathComponent];
topicId = tvTopics[server][bu][lastPathComponent];
}

if (topicId) {
Expand Down Expand Up @@ -533,7 +561,7 @@ function parseForPlayApp(hostname, pathname, queryParams, anchor) {
/* INJECT TVEVENTS OBJECT */

if (typeof tvEvents !== 'undefined' && lastPathComponent.length > 0) {
eventId = tvEvents[bu][lastPathComponent];
eventId = tvEvents[server][bu][lastPathComponent];
}

if (eventId) {
Expand All @@ -555,8 +583,8 @@ function parseForPlayApp(hostname, pathname, queryParams, anchor) {
}

// Redirect fallback.
console.log("Can't parse Play URL. Redirect.");
return schemeForBu(bu) + "://redirect";
console.log("Can't parse Play URL. Unsupported URL.");
return schemeForBu(bu) + "://unsupported?server=" + server;
};
function openMedia(server, bu, mediaType, mediaId, startTime) {
var redirect = schemeForBu(bu) + "://open?media=urn:" + bu + ":" + mediaType + ":" + mediaId;
Expand Down Expand Up @@ -632,6 +660,35 @@ function openPage(server, bu, page, channelId, options) {
return redirect;
}

function openURL(server, bu, scheme, hostname, pathname, queryParams, anchor) {
if (! scheme) {
scheme = "http";
}

var queryParamsString = "";
if (queryParams) {
for (var key in queryParams) {
queryParamsString = queryParamsString + "&" + key + "=" + encodeURIComponent(queryParams[key]);
}
}
if (queryParamsString.length > 0) {
queryParamsString = queryParamsString.replace('&','?');
}

var anchorString = "";
if (anchor) {
anchorString = "#" + anchor;
}

var url = scheme + "://" + hostname + pathname + queryParamsString + anchorString;

var redirect = schemeForBu(bu) + "://open?url=" + encodeURIComponent(url);
if (server) {
redirect = redirect + "&server=" + encodeURIComponent(server);
}
return redirect;
}

function primaryChannelUidForBu(bu) {
switch (bu) {
case "srf":
Expand Down
Loading

0 comments on commit c5387e1

Please sign in to comment.