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.

middleware.ts 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. // @ts-ignore
  2. import jwtDecode from 'jwt-decode';
  3. import { AnyAction } from 'redux';
  4. import { IStore } from '../../app/types';
  5. import { SET_CONFIG } from '../config/actionTypes';
  6. import { SET_LOCATION_URL } from '../connection/actionTypes';
  7. import { participantUpdated } from '../participants/actions';
  8. import { getLocalParticipant } from '../participants/functions';
  9. import { IParticipant } from '../participants/types';
  10. import MiddlewareRegistry from '../redux/MiddlewareRegistry';
  11. import { SET_JWT } from './actionTypes';
  12. import { setJWT } from './actions';
  13. import { parseJWTFromURLParams } from './functions';
  14. import logger from './logger';
  15. /**
  16. * Middleware to parse token data upon setting a new room URL.
  17. *
  18. * @param {Store} store - The redux store.
  19. * @private
  20. * @returns {Function}
  21. */
  22. MiddlewareRegistry.register(store => next => action => {
  23. switch (action.type) {
  24. case SET_CONFIG:
  25. case SET_LOCATION_URL:
  26. // XXX The JSON Web Token (JWT) is not the only piece of state that we
  27. // have decided to store in the feature jwt
  28. return _setConfigOrLocationURL(store, next, action);
  29. case SET_JWT:
  30. return _setJWT(store, next, action);
  31. }
  32. return next(action);
  33. });
  34. /**
  35. * Overwrites the properties {@code avatarURL}, {@code email}, and {@code name}
  36. * of the local participant stored in the redux state base/participants.
  37. *
  38. * @param {Store} store - The redux store.
  39. * @param {Object} localParticipant - The {@code Participant} structure to
  40. * overwrite the local participant stored in the redux store base/participants
  41. * with.
  42. * @private
  43. * @returns {void}
  44. */
  45. function _overwriteLocalParticipant(
  46. { dispatch, getState }: IStore,
  47. { avatarURL, email, id: jwtId, name, features }:
  48. { avatarURL?: string; email?: string; features?: any; id?: string; name?: string; }) {
  49. let localParticipant;
  50. if ((avatarURL || email || name)
  51. && (localParticipant = getLocalParticipant(getState))) {
  52. const newProperties: IParticipant = {
  53. id: localParticipant.id,
  54. local: true
  55. };
  56. if (avatarURL) {
  57. newProperties.avatarURL = avatarURL;
  58. }
  59. if (email) {
  60. newProperties.email = email;
  61. }
  62. if (jwtId) {
  63. newProperties.jwtId = jwtId;
  64. }
  65. if (name) {
  66. newProperties.name = name;
  67. }
  68. if (features) {
  69. newProperties.features = features;
  70. }
  71. dispatch(participantUpdated(newProperties));
  72. }
  73. }
  74. /**
  75. * Notifies the feature jwt that the action {@link SET_CONFIG} or
  76. * {@link SET_LOCATION_URL} is being dispatched within a specific redux
  77. * {@code store}.
  78. *
  79. * @param {Store} store - The redux store in which the specified {@code action}
  80. * is being dispatched.
  81. * @param {Dispatch} next - The redux dispatch function to dispatch the
  82. * specified {@code action} to the specified {@code store}.
  83. * @param {Action} action - The redux action {@code SET_CONFIG} or
  84. * {@code SET_LOCATION_URL} which is being dispatched in the specified
  85. * {@code store}.
  86. * @private
  87. * @returns {Object} The new state that is the result of the reduction of the
  88. * specified {@code action}.
  89. */
  90. function _setConfigOrLocationURL({ dispatch, getState }: IStore, next: Function, action: AnyAction) {
  91. const result = next(action);
  92. const { locationURL } = getState()['features/base/connection'];
  93. dispatch(
  94. setJWT(locationURL ? parseJWTFromURLParams(locationURL) : undefined));
  95. return result;
  96. }
  97. /**
  98. * Notifies the feature jwt that the action {@link SET_JWT} is being dispatched
  99. * within a specific redux {@code store}.
  100. *
  101. * @param {Store} store - The redux store in which the specified {@code action}
  102. * is being dispatched.
  103. * @param {Dispatch} next - The redux dispatch function to dispatch the
  104. * specified {@code action} to the specified {@code store}.
  105. * @param {Action} action - The redux action {@code SET_JWT} which is being
  106. * dispatched in the specified {@code store}.
  107. * @private
  108. * @returns {Object} The new state that is the result of the reduction of the
  109. * specified {@code action}.
  110. */
  111. function _setJWT(store: IStore, next: Function, action: AnyAction) {
  112. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  113. const { jwt, type, ...actionPayload } = action;
  114. if (!Object.keys(actionPayload).length) {
  115. if (jwt) {
  116. let jwtPayload;
  117. try {
  118. jwtPayload = jwtDecode(jwt);
  119. } catch (e) {
  120. logger.error(e);
  121. }
  122. if (jwtPayload) {
  123. const { context, iss, sub } = jwtPayload;
  124. action.jwt = jwt;
  125. action.issuer = iss;
  126. if (context) {
  127. const user = _user2participant(context.user || {});
  128. action.callee = context.callee;
  129. action.group = context.group;
  130. action.server = context.server;
  131. action.tenant = context.tenant || sub || undefined;
  132. action.user = user;
  133. const newUser = user ? { ...user } : {};
  134. _overwriteLocalParticipant(
  135. store, { ...newUser,
  136. features: context.features });
  137. }
  138. }
  139. } else if (typeof APP === 'undefined') {
  140. // The logic of restoring JWT overrides make sense only on mobile.
  141. // On Web it should eventually be restored from storage, but there's
  142. // no such use case yet.
  143. const { user } = store.getState()['features/base/jwt'];
  144. user && _undoOverwriteLocalParticipant(store, user);
  145. }
  146. }
  147. return next(action);
  148. }
  149. /**
  150. * Undoes/resets the values overwritten by {@link _overwriteLocalParticipant}
  151. * by either clearing them or setting to default values. Only the values that
  152. * have not changed since the overwrite happened will be restored.
  153. *
  154. * NOTE Once it is possible to edit and save participant properties, this
  155. * function should restore values from the storage instead.
  156. *
  157. * @param {Store} store - The redux store.
  158. * @param {Object} localParticipant - The {@code Participant} structure used
  159. * previously to {@link _overwriteLocalParticipant}.
  160. * @private
  161. * @returns {void}
  162. */
  163. function _undoOverwriteLocalParticipant(
  164. { dispatch, getState }: IStore,
  165. { avatarURL, name, email }: { avatarURL?: string; email?: string; name?: string; }) {
  166. let localParticipant;
  167. if ((avatarURL || name || email)
  168. && (localParticipant = getLocalParticipant(getState))) {
  169. const newProperties: IParticipant = {
  170. id: localParticipant.id,
  171. local: true
  172. };
  173. if (avatarURL === localParticipant.avatarURL) {
  174. newProperties.avatarURL = undefined;
  175. }
  176. if (email === localParticipant.email) {
  177. newProperties.email = undefined;
  178. }
  179. if (name === localParticipant.name) {
  180. newProperties.name = undefined;
  181. }
  182. newProperties.features = undefined;
  183. dispatch(participantUpdated(newProperties));
  184. }
  185. }
  186. /**
  187. * Converts the JWT {@code context.user} structure to the {@code Participant}
  188. * structure stored in the redux state base/participants.
  189. *
  190. * @param {Object} user - The JWT {@code context.user} structure to convert.
  191. * @private
  192. * @returns {{
  193. * avatarURL: ?string,
  194. * email: ?string,
  195. * id: ?string,
  196. * name: ?string,
  197. * hidden-from-recorder: ?boolean
  198. * }}
  199. */
  200. function _user2participant({ avatar, avatarUrl, email, id, name, 'hidden-from-recorder': hiddenFromRecorder }:
  201. { avatar: any; avatarUrl: string; email: string; 'hidden-from-recorder': string | boolean;
  202. id: string; name: string; }) {
  203. const participant: any = {};
  204. if (typeof avatarUrl === 'string') {
  205. participant.avatarURL = avatarUrl.trim();
  206. } else if (typeof avatar === 'string') {
  207. participant.avatarURL = avatar.trim();
  208. }
  209. if (typeof email === 'string') {
  210. participant.email = email.trim();
  211. }
  212. if (typeof id === 'string') {
  213. participant.id = id.trim();
  214. }
  215. if (typeof name === 'string') {
  216. participant.name = name.trim();
  217. }
  218. if (hiddenFromRecorder === 'true' || hiddenFromRecorder === true) {
  219. participant.hiddenFromRecorder = true;
  220. }
  221. return Object.keys(participant).length ? participant : undefined;
  222. }