Application Android et IOS pour l'amicale des élèves
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

URLHandler.ts 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /*
  2. * Copyright (c) 2019 - 2020 Arnaud Vergnet.
  3. *
  4. * This file is part of Campus INSAT.
  5. *
  6. * Campus INSAT is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Campus INSAT is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Campus INSAT. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. import {Linking} from 'react-native';
  20. export type ParsedUrlDataType = {
  21. route: string;
  22. data: {[key: string]: string};
  23. };
  24. export type ParsedUrlCallbackType = (parsedData: ParsedUrlDataType) => void;
  25. type RawParsedUrlDataType = {
  26. path: string;
  27. queryParams: {[key: string]: string};
  28. };
  29. /**
  30. * Class use to handle depp links scanned or clicked.
  31. */
  32. export default class URLHandler {
  33. static SCHEME = 'campus-insat://'; // Urls beginning with this string will be opened in the app
  34. static CLUB_INFO_URL_PATH = 'club';
  35. static EVENT_INFO_URL_PATH = 'event';
  36. static CLUB_INFO_ROUTE = 'club-information';
  37. static EVENT_INFO_ROUTE = 'planning-information';
  38. onInitialURLParsed: ParsedUrlCallbackType;
  39. onDetectURL: ParsedUrlCallbackType;
  40. constructor(
  41. onInitialURLParsed: ParsedUrlCallbackType,
  42. onDetectURL: ParsedUrlCallbackType,
  43. ) {
  44. this.onInitialURLParsed = onInitialURLParsed;
  45. this.onDetectURL = onDetectURL;
  46. }
  47. /**
  48. * Parses the given url to retrieve the corresponding app path and associated arguments.
  49. *
  50. * @param url The url to parse
  51. * @returns {{path: string, queryParams: {}}}
  52. */
  53. static parseUrl(url: string): RawParsedUrlDataType | null {
  54. let parsedData: RawParsedUrlDataType | null = null;
  55. const urlNoScheme = url.replace(URLHandler.SCHEME, '');
  56. if (urlNoScheme != null) {
  57. const params: {[key: string]: string} = {};
  58. const [path, fullParamsString] = urlNoScheme.split('?');
  59. if (fullParamsString != null) {
  60. const paramsStringArray = fullParamsString.split('&');
  61. paramsStringArray.forEach((paramString: string) => {
  62. const [key, value] = paramString.split('=');
  63. if (value != null) {
  64. params[key] = value;
  65. }
  66. });
  67. }
  68. if (path != null) {
  69. parsedData = {path, queryParams: params};
  70. }
  71. }
  72. return parsedData;
  73. }
  74. /**
  75. * Gets routing data corresponding to the given url.
  76. * If the url does not match any existing route, null will be returned.
  77. *
  78. * @param rawParsedUrlData The data just parsed
  79. * @returns {null}
  80. */
  81. static getUrlData(
  82. rawParsedUrlData: RawParsedUrlDataType | null,
  83. ): ParsedUrlDataType | null {
  84. let parsedData: null | ParsedUrlDataType = null;
  85. if (rawParsedUrlData != null) {
  86. const {path} = rawParsedUrlData;
  87. const {queryParams} = rawParsedUrlData;
  88. if (URLHandler.isClubInformationLink(path)) {
  89. parsedData = URLHandler.generateClubInformationData(queryParams);
  90. } else if (URLHandler.isPlanningInformationLink(path)) {
  91. parsedData = URLHandler.generatePlanningInformationData(queryParams);
  92. }
  93. }
  94. return parsedData;
  95. }
  96. /**
  97. * Checks if the given url is in a valid format
  98. *
  99. * @param url The url to check
  100. * @returns {boolean}
  101. */
  102. static isUrlValid(url: string): boolean {
  103. return this.getUrlData(URLHandler.parseUrl(url)) !== null;
  104. }
  105. /**
  106. * Check if the given path links to the club information screen
  107. *
  108. * @param path The url to check
  109. * @returns {boolean}
  110. */
  111. static isClubInformationLink(path: string): boolean {
  112. return path === URLHandler.CLUB_INFO_URL_PATH;
  113. }
  114. /**
  115. * Check if the given path links to the planning information screen
  116. *
  117. * @param path The url to check
  118. * @returns {boolean}
  119. */
  120. static isPlanningInformationLink(path: string): boolean {
  121. return path === URLHandler.EVENT_INFO_URL_PATH;
  122. }
  123. /**
  124. * Generates data formatted for the club information screen from the url parameters.
  125. *
  126. * @param params Url parameters to convert
  127. * @returns {null|{route: string, data: {clubId: number}}}
  128. */
  129. static generateClubInformationData(params: {
  130. [key: string]: string;
  131. }): ParsedUrlDataType | null {
  132. if (params.id != null) {
  133. const id = parseInt(params.id, 10);
  134. if (!Number.isNaN(id)) {
  135. return {
  136. route: URLHandler.CLUB_INFO_ROUTE,
  137. data: {clubId: id.toString()},
  138. };
  139. }
  140. }
  141. return null;
  142. }
  143. /**
  144. * Generates data formatted for the planning information screen from the url parameters.
  145. *
  146. * @param params Url parameters to convert
  147. * @returns {null|{route: string, data: {clubId: number}}}
  148. */
  149. static generatePlanningInformationData(params: {
  150. [key: string]: string;
  151. }): ParsedUrlDataType | null {
  152. if (params.id != null) {
  153. const id = parseInt(params.id, 10);
  154. if (!Number.isNaN(id)) {
  155. return {
  156. route: URLHandler.EVENT_INFO_ROUTE,
  157. data: {eventId: id.toString()},
  158. };
  159. }
  160. }
  161. return null;
  162. }
  163. /**
  164. * Starts listening to events.
  165. *
  166. * There are 2 types of event.
  167. *
  168. * A classic event, triggered while the app is active.
  169. * An initial event, called when the app was opened by clicking on a link
  170. *
  171. */
  172. listen() {
  173. Linking.addEventListener('url', this.onUrl);
  174. Linking.getInitialURL().then(this.onInitialUrl);
  175. }
  176. /**
  177. * Gets data from the given url and calls the classic callback with it.
  178. *
  179. * @param url The url detected
  180. */
  181. onUrl = ({url}: {url: string}) => {
  182. if (url != null) {
  183. const data = URLHandler.getUrlData(URLHandler.parseUrl(url));
  184. if (data != null) {
  185. this.onDetectURL(data);
  186. }
  187. }
  188. };
  189. /**
  190. * Gets data from the given url and calls the initial callback with it.
  191. *
  192. * @param url The url detected
  193. */
  194. onInitialUrl = (url: string | null) => {
  195. if (url != null) {
  196. const data = URLHandler.getUrlData(URLHandler.parseUrl(url));
  197. if (data != null) {
  198. this.onInitialURLParsed(data);
  199. }
  200. }
  201. };
  202. }