@edx/frontend-platform 4.6.2 → 4.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/package.json +1 -1
  2. package/.env.development +0 -30
  3. package/.env.test +0 -30
  4. package/.eslintignore +0 -6
  5. package/.eslintrc.js +0 -28
  6. package/.github/PULL_REQUEST_TEMPLATE.md +0 -13
  7. package/.github/workflows/add-depr-ticket-to-depr-board.yml +0 -19
  8. package/.github/workflows/add-remove-label-on-comment.yml +0 -20
  9. package/.github/workflows/ci.yml +0 -42
  10. package/.github/workflows/commitlint.yml +0 -10
  11. package/.github/workflows/lockfileversion-check.yml +0 -13
  12. package/.github/workflows/manual-publish.yml +0 -43
  13. package/.github/workflows/npm-deprecate.yml +0 -22
  14. package/.github/workflows/release.yml +0 -45
  15. package/.github/workflows/self-assign-issue.yml +0 -12
  16. package/.github/workflows/update-browserslist-db.yml +0 -12
  17. package/.nvmrc +0 -1
  18. package/.releaserc +0 -32
  19. package/catalog-info.yaml +0 -21
  20. package/dist/LICENSE +0 -661
  21. package/dist/README.md +0 -155
  22. package/dist/package.json +0 -86
  23. package/docs/addTagsPlugin.js +0 -10
  24. package/docs/auth-API.md +0 -114
  25. package/docs/decisions/0001-record-architecture-decisions.rst +0 -32
  26. package/docs/decisions/0002-frontend-base-design-goals.rst +0 -222
  27. package/docs/decisions/0003-consolidation-into-frontend-platform.rst +0 -71
  28. package/docs/decisions/0004-axios-caching-implementation.rst +0 -88
  29. package/docs/decisions/0005-token-null-after-successful-refresh.rst +0 -69
  30. package/docs/decisions/0006-middleware-support-for-http-clients.rst +0 -44
  31. package/docs/decisions/0007-javascript-file-configuration.rst +0 -143
  32. package/docs/how_tos/automatic-case-conversion.rst +0 -58
  33. package/docs/how_tos/caching.rst +0 -93
  34. package/docs/how_tos/i18n.rst +0 -305
  35. package/docs/removeExport.js +0 -24
  36. package/docs/template/edx/README.md +0 -12
  37. package/docs/template/edx/publish.js +0 -713
  38. package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.eot +0 -0
  39. package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.svg +0 -1830
  40. package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.woff +0 -0
  41. package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  42. package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
  43. package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  44. package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.eot +0 -0
  45. package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.svg +0 -1830
  46. package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.woff +0 -0
  47. package/docs/template/edx/static/fonts/OpenSans-Light-webfont.eot +0 -0
  48. package/docs/template/edx/static/fonts/OpenSans-Light-webfont.svg +0 -1831
  49. package/docs/template/edx/static/fonts/OpenSans-Light-webfont.woff +0 -0
  50. package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  51. package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
  52. package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  53. package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.eot +0 -0
  54. package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.svg +0 -1831
  55. package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.woff +0 -0
  56. package/docs/template/edx/static/scripts/linenumber.js +0 -25
  57. package/docs/template/edx/static/scripts/prettify/Apache-License-2.0.txt +0 -202
  58. package/docs/template/edx/static/scripts/prettify/lang-css.js +0 -2
  59. package/docs/template/edx/static/scripts/prettify/prettify.js +0 -28
  60. package/docs/template/edx/static/styles/jsdoc-default.css +0 -356
  61. package/docs/template/edx/static/styles/prettify-jsdoc.css +0 -111
  62. package/docs/template/edx/static/styles/prettify-tomorrow.css +0 -132
  63. package/docs/template/edx/tmpl/augments.tmpl +0 -10
  64. package/docs/template/edx/tmpl/container.tmpl +0 -196
  65. package/docs/template/edx/tmpl/details.tmpl +0 -143
  66. package/docs/template/edx/tmpl/example.tmpl +0 -2
  67. package/docs/template/edx/tmpl/examples.tmpl +0 -13
  68. package/docs/template/edx/tmpl/exceptions.tmpl +0 -32
  69. package/docs/template/edx/tmpl/layout.tmpl +0 -39
  70. package/docs/template/edx/tmpl/mainpage.tmpl +0 -10
  71. package/docs/template/edx/tmpl/members.tmpl +0 -38
  72. package/docs/template/edx/tmpl/method.tmpl +0 -131
  73. package/docs/template/edx/tmpl/modifies.tmpl +0 -14
  74. package/docs/template/edx/tmpl/params.tmpl +0 -131
  75. package/docs/template/edx/tmpl/properties.tmpl +0 -108
  76. package/docs/template/edx/tmpl/returns.tmpl +0 -19
  77. package/docs/template/edx/tmpl/source.tmpl +0 -8
  78. package/docs/template/edx/tmpl/tutorial.tmpl +0 -19
  79. package/docs/template/edx/tmpl/type.tmpl +0 -7
  80. package/env.config.js +0 -8
  81. package/jsdoc.json +0 -36
  82. package/openedx.yaml +0 -12
  83. package/service-interface.png +0 -0
  84. package/src/analytics/MockAnalyticsService.js +0 -71
  85. package/src/analytics/SegmentAnalyticsService.js +0 -243
  86. package/src/analytics/index.js +0 -12
  87. package/src/analytics/interface.js +0 -142
  88. package/src/auth/AxiosCsrfTokenService.js +0 -60
  89. package/src/auth/AxiosJwtAuthService.js +0 -364
  90. package/src/auth/AxiosJwtTokenService.js +0 -134
  91. package/src/auth/LocalForageCache.js +0 -78
  92. package/src/auth/MockAuthService.js +0 -285
  93. package/src/auth/index.js +0 -19
  94. package/src/auth/interceptors/createCsrfTokenProviderInterceptor.js +0 -37
  95. package/src/auth/interceptors/createJwtTokenProviderInterceptor.js +0 -38
  96. package/src/auth/interceptors/createProcessAxiosRequestErrorInterceptor.js +0 -20
  97. package/src/auth/interceptors/createRetryInterceptor.js +0 -72
  98. package/src/auth/interface.js +0 -309
  99. package/src/auth/utils.js +0 -105
  100. package/src/config.js +0 -327
  101. package/src/constants.js +0 -66
  102. package/src/i18n/countries.js +0 -57
  103. package/src/i18n/index.js +0 -123
  104. package/src/i18n/injectIntlWithShim.jsx +0 -45
  105. package/src/i18n/languages.js +0 -60
  106. package/src/i18n/lib.js +0 -282
  107. package/src/i18n/scripts/README.md +0 -29
  108. package/src/i18n/scripts/intl-imports.js +0 -259
  109. package/src/i18n/scripts/transifex-utils.js +0 -75
  110. package/src/index.js +0 -42
  111. package/src/initialize.js +0 -357
  112. package/src/logging/MockLoggingService.js +0 -31
  113. package/src/logging/NewRelicLoggingService.js +0 -181
  114. package/src/logging/index.js +0 -9
  115. package/src/logging/interface.js +0 -110
  116. package/src/pubSub.js +0 -47
  117. package/src/react/AppContext.jsx +0 -24
  118. package/src/react/AppProvider.jsx +0 -93
  119. package/src/react/AuthenticatedPageRoute.jsx +0 -60
  120. package/src/react/ErrorBoundary.jsx +0 -44
  121. package/src/react/ErrorPage.jsx +0 -76
  122. package/src/react/LoginRedirect.jsx +0 -16
  123. package/src/react/OptionalReduxProvider.jsx +0 -28
  124. package/src/react/PageRoute.jsx +0 -31
  125. package/src/react/hooks.js +0 -50
  126. package/src/react/index.js +0 -16
  127. package/src/scripts/GoogleAnalyticsLoader.js +0 -53
  128. package/src/scripts/index.js +0 -2
  129. package/src/testing/index.js +0 -9
  130. package/src/testing/initializeMockApp.js +0 -77
  131. package/src/testing/mockMessages.js +0 -21
  132. package/src/utils.js +0 -167
  133. /package/{dist/analytics → analytics}/MockAnalyticsService.js +0 -0
  134. /package/{dist/analytics → analytics}/MockAnalyticsService.js.map +0 -0
  135. /package/{dist/analytics → analytics}/SegmentAnalyticsService.js +0 -0
  136. /package/{dist/analytics → analytics}/SegmentAnalyticsService.js.map +0 -0
  137. /package/{dist/analytics → analytics}/index.js +0 -0
  138. /package/{dist/analytics → analytics}/index.js.map +0 -0
  139. /package/{dist/analytics → analytics}/interface.js +0 -0
  140. /package/{dist/analytics → analytics}/interface.js.map +0 -0
  141. /package/{dist/auth → auth}/AxiosCsrfTokenService.js +0 -0
  142. /package/{dist/auth → auth}/AxiosCsrfTokenService.js.map +0 -0
  143. /package/{dist/auth → auth}/AxiosJwtAuthService.js +0 -0
  144. /package/{dist/auth → auth}/AxiosJwtAuthService.js.map +0 -0
  145. /package/{dist/auth → auth}/AxiosJwtTokenService.js +0 -0
  146. /package/{dist/auth → auth}/AxiosJwtTokenService.js.map +0 -0
  147. /package/{dist/auth → auth}/LocalForageCache.js +0 -0
  148. /package/{dist/auth → auth}/LocalForageCache.js.map +0 -0
  149. /package/{dist/auth → auth}/MockAuthService.js +0 -0
  150. /package/{dist/auth → auth}/MockAuthService.js.map +0 -0
  151. /package/{dist/auth → auth}/index.js +0 -0
  152. /package/{dist/auth → auth}/index.js.map +0 -0
  153. /package/{dist/auth → auth}/interceptors/createCsrfTokenProviderInterceptor.js +0 -0
  154. /package/{dist/auth → auth}/interceptors/createCsrfTokenProviderInterceptor.js.map +0 -0
  155. /package/{dist/auth → auth}/interceptors/createJwtTokenProviderInterceptor.js +0 -0
  156. /package/{dist/auth → auth}/interceptors/createJwtTokenProviderInterceptor.js.map +0 -0
  157. /package/{dist/auth → auth}/interceptors/createProcessAxiosRequestErrorInterceptor.js +0 -0
  158. /package/{dist/auth → auth}/interceptors/createProcessAxiosRequestErrorInterceptor.js.map +0 -0
  159. /package/{dist/auth → auth}/interceptors/createRetryInterceptor.js +0 -0
  160. /package/{dist/auth → auth}/interceptors/createRetryInterceptor.js.map +0 -0
  161. /package/{dist/auth → auth}/interface.js +0 -0
  162. /package/{dist/auth → auth}/interface.js.map +0 -0
  163. /package/{dist/auth → auth}/utils.js +0 -0
  164. /package/{dist/auth → auth}/utils.js.map +0 -0
  165. /package/{dist/config.js → config.js} +0 -0
  166. /package/{dist/config.js.map → config.js.map} +0 -0
  167. /package/{dist/constants.js → constants.js} +0 -0
  168. /package/{dist/constants.js.map → constants.js.map} +0 -0
  169. /package/{dist/i18n → i18n}/countries.js +0 -0
  170. /package/{dist/i18n → i18n}/countries.js.map +0 -0
  171. /package/{dist/i18n → i18n}/index.js +0 -0
  172. /package/{dist/i18n → i18n}/index.js.map +0 -0
  173. /package/{dist/i18n → i18n}/injectIntlWithShim.js +0 -0
  174. /package/{dist/i18n → i18n}/injectIntlWithShim.js.map +0 -0
  175. /package/{dist/i18n → i18n}/languages.js +0 -0
  176. /package/{dist/i18n → i18n}/languages.js.map +0 -0
  177. /package/{dist/i18n → i18n}/lib.js +0 -0
  178. /package/{dist/i18n → i18n}/lib.js.map +0 -0
  179. /package/{dist/i18n → i18n}/scripts/README.md +0 -0
  180. /package/{dist/i18n → i18n}/scripts/intl-imports.js +0 -0
  181. /package/{dist/i18n → i18n}/scripts/intl-imports.js.map +0 -0
  182. /package/{dist/i18n → i18n}/scripts/transifex-utils.js +0 -0
  183. /package/{dist/i18n → i18n}/scripts/transifex-utils.js.map +0 -0
  184. /package/{dist/index.js → index.js} +0 -0
  185. /package/{dist/index.js.map → index.js.map} +0 -0
  186. /package/{dist/initialize.js → initialize.js} +0 -0
  187. /package/{dist/initialize.js.map → initialize.js.map} +0 -0
  188. /package/{dist/logging → logging}/MockLoggingService.js +0 -0
  189. /package/{dist/logging → logging}/MockLoggingService.js.map +0 -0
  190. /package/{dist/logging → logging}/NewRelicLoggingService.js +0 -0
  191. /package/{dist/logging → logging}/NewRelicLoggingService.js.map +0 -0
  192. /package/{dist/logging → logging}/index.js +0 -0
  193. /package/{dist/logging → logging}/index.js.map +0 -0
  194. /package/{dist/logging → logging}/interface.js +0 -0
  195. /package/{dist/logging → logging}/interface.js.map +0 -0
  196. /package/{dist/pubSub.js → pubSub.js} +0 -0
  197. /package/{dist/pubSub.js.map → pubSub.js.map} +0 -0
  198. /package/{dist/react → react}/AppContext.js +0 -0
  199. /package/{dist/react → react}/AppContext.js.map +0 -0
  200. /package/{dist/react → react}/AppProvider.js +0 -0
  201. /package/{dist/react → react}/AppProvider.js.map +0 -0
  202. /package/{dist/react → react}/AuthenticatedPageRoute.js +0 -0
  203. /package/{dist/react → react}/AuthenticatedPageRoute.js.map +0 -0
  204. /package/{dist/react → react}/ErrorBoundary.js +0 -0
  205. /package/{dist/react → react}/ErrorBoundary.js.map +0 -0
  206. /package/{dist/react → react}/ErrorPage.js +0 -0
  207. /package/{dist/react → react}/ErrorPage.js.map +0 -0
  208. /package/{dist/react → react}/LoginRedirect.js +0 -0
  209. /package/{dist/react → react}/LoginRedirect.js.map +0 -0
  210. /package/{dist/react → react}/OptionalReduxProvider.js +0 -0
  211. /package/{dist/react → react}/OptionalReduxProvider.js.map +0 -0
  212. /package/{dist/react → react}/PageRoute.js +0 -0
  213. /package/{dist/react → react}/PageRoute.js.map +0 -0
  214. /package/{dist/react → react}/hooks.js +0 -0
  215. /package/{dist/react → react}/hooks.js.map +0 -0
  216. /package/{dist/react → react}/index.js +0 -0
  217. /package/{dist/react → react}/index.js.map +0 -0
  218. /package/{dist/scripts → scripts}/GoogleAnalyticsLoader.js +0 -0
  219. /package/{dist/scripts → scripts}/GoogleAnalyticsLoader.js.map +0 -0
  220. /package/{dist/scripts → scripts}/index.js +0 -0
  221. /package/{dist/scripts → scripts}/index.js.map +0 -0
  222. /package/{dist/testing → testing}/index.js +0 -0
  223. /package/{dist/testing → testing}/index.js.map +0 -0
  224. /package/{dist/testing → testing}/initializeMockApp.js +0 -0
  225. /package/{dist/testing → testing}/initializeMockApp.js.map +0 -0
  226. /package/{dist/testing → testing}/mockMessages.js +0 -0
  227. /package/{dist/testing → testing}/mockMessages.js.map +0 -0
  228. /package/{dist/utils.js → utils.js} +0 -0
  229. /package/{dist/utils.js.map → utils.js.map} +0 -0
@@ -1,71 +0,0 @@
1
- Consolidation of libraries into frontend-platform
2
- =================================================
3
-
4
- Status
5
- ------
6
-
7
- Accepted
8
-
9
- Context
10
- -------
11
-
12
- The frontend-platform repository replaces five earlier repositories:
13
-
14
- - edx/frontend-analytics
15
- - edx/frontend-auth
16
- - edx/frontend-base
17
- - edx/frontend-i18n
18
- - edx/frontend-logging
19
-
20
- We found that development across these five libraries was growing very difficult in a number of ways.
21
-
22
- Dependency graph complexity
23
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
24
-
25
- The overhead involved in maintaining individual packages and semantic version numbers was very difficult to reason about and maintain. Updates to low-level packages tended to cause cascading updates through the dependency tree. If a breaking change or incompatibility present in the tree forces dependent packages to absorb unrelated work, it can be very difficult to realize it's happening, decreasing developer velocity and destabilizing our applications.
26
-
27
- As a rough illustration of this complexity:
28
-
29
- - 12 Micro-frontends depend on 5 libraries. (60 edges max)
30
- - 12 Micro-frontends depend on 5 reusable organisms (headers, footers, Paragon) (60 edges max)
31
- - 5 Reusable organisms depend on the 5 libraries. (25 edges max)
32
- - The 5 libraries sometimes depend on each other. (8 edges total)
33
-
34
- This graph has 153 possible edges, each of which represents a version number with its own set of compatibilities. By combining the libraries, we drop the number of edges to 77. Further combining some of our reusable organisms could get this number as low as 51.
35
-
36
- For a single micro-frontend, these numbers go from 43 to 11, then 9.
37
-
38
- Difficult cross-cutting feature development
39
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
40
-
41
- Often we've found that features and bug fixes require us to work in several of the above repositories simultaneously. Getting npm to link packages from peer directories is tedious and error prone, making this kind of work difficult to get right.
42
-
43
- Interdependent services
44
- ~~~~~~~~~~~~~~~~~~~~~~~
45
-
46
- Going along with the difficulty of cross-cutting concerns, we realized at some point that many of packages were simply rather inter-connected. Meaningful features and behaviors span packages, especially with respect to frontend-base's usage of the other libraries. This means that validating correct behavior requires a fully functioning system; developing the libraries in isolation prevented us from doing substantive integration testing.
47
-
48
- Decision
49
- --------
50
-
51
- By collapsing these libraries into a single repository published together under a single version number, we drastically reduce the layers of complexity involved in publishing new library versions.
52
- Note that this is a tactical technology choice - architecturally and strategically, the libraries are still independent and contain strong boundaries isolating them from each other.
53
-
54
- Note that we've preserved the platform's ability to utilize different service implementations, meaning that using the platform does not preclude applications from providing customized/extended functionality. This means that we haven't really lost much flexibility by the consolidation.
55
-
56
- Alternatives
57
- ------------
58
-
59
- We strongly considered making a Lerna monorepo with each of the original five packages published with its own version number. There were two problems with this approach - first, it wouldn't address the dependency graph complexity, which was one of the most difficult-to-reason aspects of the original repositories. Secondary to that, our usage of babel and building to a dist directory erases some of the developer experience benefits of using Lerna. One of Lerna's strengths is that it automatically rewires your package.json dependencies to point at other packages in the repository, rather than pulling them down from the package registry. Since we have a build step, we'd have to manually build each package any time we wanted to use it, which isn't all that different from what we had to do with the five separate repos. Lerna just didn't really seem worth it.
60
-
61
- Adoption
62
- --------
63
-
64
- As of this writing, frontend-platform is in use in four of our existing micro-frontends. It's our intention to help teams migrate the others (approximately 8) to use the platform as well, and to archive the five legacy implementations mentioned above.
65
-
66
- The platform's API surface is very similar to the original five libraries, simply with a different import package. If necessary, we expect that consumers of the old libraries will be able to do gradual, partial upgrades by cutting over individual libraries until they reach full adoption.
67
-
68
- Consequences
69
- ------------
70
-
71
- If we collapse these libraries into a single versioned platform, consumers of the platform (applications) may still have to absorb undesired breaking changes at times, but we've greatly reduced the complexity of doing so. Updating an application or dependent library involves consulting a single changelog for breaking changes, rather than five and trying to reason which are compatible with each other.
@@ -1,88 +0,0 @@
1
- Axios Caching Layer Implementation
2
- =================================================
3
-
4
- Status
5
- ------
6
-
7
- Accepted
8
-
9
- Context
10
- -------
11
-
12
- It was noticed that on a lot of pages the same API calls are repeatedly being made as the user navigates around the website. The data being returned from these requests doesn't change between page loads leading to the same endpoints reloading the same data repeatedly. We decided that we wanted to avoid repeatedly loading unchanged data and reuse the data that was in the response from the initial load.
13
-
14
- We think it's beneficial to cut as much overhead as possible so each page becomes interactive more quickly.
15
-
16
- We decided to add a frontend caching layer to the AxiosJwtAuthService by leveraging an already existing npm package: `axios-cache-adapter <https://www.npmjs.com/package/axios-cache-adapter>`_
17
-
18
- Having a frontend caching layer would also allow for a stale version of a page to be displayed for a short amount of time in the case of a backend outage.
19
-
20
- When Frontend Caching Should Be Used
21
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
22
-
23
- The frontend cache should be used to eliminate the reloading of unchanged data as a user navigates between different pages in the application. It shouldn't be used to reduce the number of times multiple components on the same page try to make the same network call. Reducing the number of times the same network call is made on a single page should be done by moving the call higher up in the hierarchy so the call only gets made once and all lower level components are able to access the response data from it.
24
-
25
- Requests that are good candidates to use frontend caching are ones that:
26
- * The data being returned changes very infrequently, and independently from the actions of the user on the page
27
-
28
- - For example changes made by a background job or an admin
29
-
30
- * The data being returned isn't specific to the authenticated user
31
-
32
- * The data returned is specific to the authenticated user but front end does not make the request when there isn't an authenticated user
33
-
34
- It should not be used as the only caching strategy that gets used. It is preferable to use a serverside caching strategy in conjunction with frontend caching.
35
-
36
- How To Use Frontend Caching
37
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
38
-
39
- The frontend caching layer can be used by passing {useCache: true} into the function that gets the httpClient.
40
-
41
- e.g.
42
- service.getAuthenticatedHttpClient({ useCache: true });
43
-
44
- By default the response for a GET request will be stored in IndexedDB for 5 minutes and that cached value will get invalidated on any POST, PUT, PATCH, or DELETE request made to the same url. The caching layer also works with the standard cache-control headers: max-age, no-cache, and no-store
45
-
46
- Each request can also override the default cache configurations. `The How To document about caching <https://github.com/openedx/frontend-platform/blob/master/docs/how_tos/caching.rst>`_ has more detailed examples about how this caching layer can be used.
47
-
48
-
49
- Implementation Details
50
- ~~~~~~~~~~~~~~~~~~~~~~
51
-
52
- For both versions of the current AxiosJwtAuthService HTTP clients, the authenticated client and the base unauthenticated clients, there are now two additional versions that use the cache (i.e. CachedAuthenticated and CachedUnauthenticated). Bringing the total number of httpClient instances up to four.
53
- In order to keep the interface more simple the cached clients don’t have their own getter functions exposing this detail, in order to use the cached http client micro frontends pass in a param for using the cache with the current getter functions.
54
-
55
- In the case of the cached clients failing to get created the uncached clients will be returned and used instead.
56
-
57
- The async hydrateAuthenticatedUser function will continue only ever using the uncached authenticated HTTP client so there won't be any concerns about the cache changing expected behavior of authentication.
58
-
59
- Only the AxiosJwtAuthService currently has cached http clients.
60
-
61
- Decisions
62
- ---------
63
-
64
- By using the existing axios-cache-adapter we were able to avoid needing to implement our own caching strategy and forgo having micro frontends need to implement their own front end caching strategies.
65
-
66
- The default max-age for cached data was decided to be 5 minutes for now. 5 minutes was deemed to be a reasonable amount of time that was neither too long or too short. The assumption is made that users won’t be switching to different accounts on their browser frequently enough to have a wide impact on their experience.
67
- If they experience any impact at all then they will only see cached data for a maximum of 5 minutes. Micro frontends are also able to override the default cache time to suit their own needs.
68
- The default max-age can also be changed at a later time if the current default of 5 minutes is deemed to be inadequate.
69
-
70
- "Integration tests were added to ensure that the cache adapter does not break the current behavior of the existing interceptors. These tests make real HTTP requests to https://httpbin.org/ as suggested by the tests in the axios-cache-adapter source code."
71
-
72
- Alternatives
73
- ------------
74
-
75
- We discussed the pros and cons of having a frontend-platform cache implementation for micro front ends to use versus micro front ends implementing their own front end caching strategies utilizing Local/Session storage, ETags, and endpoints utilizing Cache-Control headers.
76
- Having a frontend-platform caching solution be widely available to micro frontends was decided to be a good thing and micro frontends are still able to use other caching strategies in conjunction with the caching in frontend-platform.
77
-
78
- Adoption
79
- --------
80
-
81
- As of this writing, only a few HTTP requests within `frontend-app-learner-portal-enterprise <http://github.com/openedx/frontend-app-learner-portal-enterprise>`_ have adopted using the cached clients. Usage of the cache clients is on an opt-in basis where micro frontends determine whether or not using the front end cache clients suits their needs.
82
-
83
-
84
- Consequences
85
- ------------
86
-
87
- Axios is version locked to version 0.18.1 so per request overrides work until this issue gets resolved: https://github.com/RasCarlito/axios-cache-adapter/issues/99.
88
- At of this writing it seems that a fix for the issue might possibly be in version 0.20
@@ -1,69 +0,0 @@
1
- Access Token Null After Successful Refresh
2
- ==========================================
3
-
4
- Status
5
- ------
6
-
7
- Accepted
8
-
9
- Context
10
- -------
11
-
12
- There are cases where our frontend authentication code requests an updated access token (JWT cookies), gets a successful response, but the cookies never appear. This results in the JavaScript error: "[frontend-auth] Access token is still null after successful refresh."
13
-
14
- Decision
15
- --------
16
-
17
- Since we have been unable to determine root cause, the de facto decision is to allow this issue to continue to exist.
18
-
19
- This doc is less about capturing the decision, then to capture the background in-case anyone wishes to understand what has been attempted so far.
20
-
21
- Failed Hypotheses
22
- -----------------
23
-
24
- Several failed hypotheses have been tested to determine why this is happening:
25
-
26
- * Tested if caused by a race condition between getting the response and having the cookies set.
27
-
28
- * See https://github.com/edx-unsupported/frontend-auth/pull/38/files
29
-
30
- * Tested if caused by cookies being disabled, by creating/reading a separate cookie.
31
-
32
- * Failure noted in https://openedx.atlassian.net/browse/ARCH-948?focusedCommentId=401201
33
-
34
- * Tested if the cookies library has a bug by attempting vanilla javascript.
35
-
36
- * Failure noted in https://openedx.atlassian.net/browse/ARCH-948?focusedCommentId=401201
37
-
38
- * Tested if caused by a difference between the browser time and server time, causing the cookie to expire on arrival.
39
-
40
- * Only a small subset of the errors (~0.8%) had a time difference of >5 minutes.
41
-
42
- * See https://github.com/openedx/frontend-platform/pull/207
43
-
44
- Additional Data
45
- ---------------
46
-
47
- Here is a query used in New Relic to find these errors in the Learning MFE::
48
-
49
- SELECT count(*) From JavaScriptError
50
- WHERE errorMessage = '[frontend-auth] Access token is still null after successful refresh.' AND
51
- appName = 'prod-frontend-app-learning'
52
- SINCE 4 days ago FACET userAgentName
53
-
54
- Almost all of these errors are seen in Safari (2.9 k), with the rest (166) found in Chrome and Microsoft Edge.
55
-
56
- Here is an adjusted query searching for errors with a difference of >5 minutes between the server and browser times::
57
-
58
- SELECT count(*) From JavaScriptError
59
- WHERE errorMessage = '[frontend-auth] Access token is still null after successful refresh.' AND
60
- browserDriftSeconds > 60*5
61
- SINCE 4 days ago FACET userAgentName
62
-
63
- There were only 24 of these found in Safari, or ~0.8% of all of these errors found in Safari.
64
-
65
- Lastly, at the time this data was captured (2021-08-17), this error type made up ~12% of all errors in Safari, and ~3.3% of all errors across all browsers in the Learning MFE.
66
-
67
- * It is unclear if these errors can be duplicated with special privacy settings in Safari (or other browsers).
68
-
69
- * We have not learned about this issue from Customer Support, but only from monitoring errors. Since we are not receiving the JWT cookies, we do not know the affected users.
@@ -1,44 +0,0 @@
1
- Middleware Support for HTTP clients
2
- ===================================
3
-
4
- Status
5
- ------
6
-
7
- Accepted
8
-
9
- Context
10
- -------
11
-
12
- We currently expose HTTP clients(axios instances) via ``getAuthenticatedHttpClient`` and ``getHttpClient`` used to make API requests
13
- in our MFEs. There are instances where it would be helpful if consumers could apply middleware to these clients.
14
- For example the `axios-case-converter <https://www.npmjs.com/package/axios-case-converter>`_ package provides
15
- a middleware that handles snake-cased <-> camelCase conversions via axios interceptors. This middleware would allow our MFEs to
16
- avoid having to do this conversion manually.
17
-
18
- Decision
19
- --------
20
-
21
- The ``initialize`` function provided in the initialize module initializes the ``AxiosJwtAuthService`` provided by ``@edx/frontend-platform``.
22
- We will add an optional param ``authMiddleware``, an array of middleware functions that will be applied to all http clients in
23
- the ``AxiosJwtAuthService``.
24
-
25
- Consumers will install the middleware they want to use and provide it to ``initialize``::
26
-
27
- initialize({
28
- messages: [appMessages],
29
- requireAuthenticatedUser: true,
30
- hydrateAuthenticatedUser: true,
31
- authMiddleware: [axiosCaseConverter, (client) => axiosRetry(client, { retries: 3 })],
32
- });
33
-
34
- If a consumer chooses not to use ``initialize`` and instead the ``configure`` function, the middleware can be passed in the options param::
35
-
36
- configure({
37
- loggingService: getLoggingService(),
38
- config: getConfig(),
39
- options: {
40
- middleware: [axiosCaseConverter, (client) => axiosRetry(client, { retries: 3 })]
41
- }
42
- });
43
-
44
- We decided to let consumers install their own middleware packages, removing the need to install the dependency as part of ``@edx/frontend-platform``.
@@ -1,143 +0,0 @@
1
- Promote JavaScript file configuration and deprecate environment variable configuration
2
- ======================================================================================
3
-
4
- Status
5
- ------
6
-
7
- Accepted
8
-
9
- Context
10
- -------
11
-
12
- Our webpack build process allows us to set environment variables on the command
13
- line or via .env files. These environment variables are available in the
14
- application via ``process.env``.
15
-
16
- The implementation of this uses templatization and string interpolation to
17
- replace any instance of ``process.env.XXXX`` with the value of the environment
18
- variable named ``XXXX``. As an example, in our source code we may write::
19
-
20
- const LMS_BASE_URL = process.env.LMS_BASE_URL;
21
-
22
- After the build process runs, the compiled source code will instead read::
23
-
24
- const LMS_BASE_URL = 'http://localhost:18000';
25
-
26
- Put another way, `process.env` is not actually an object available at runtime,
27
- it's a templatization token that helps the build replace it with a string
28
- literal.
29
-
30
- This approach has several important limitations:
31
-
32
- - There's no way to add variables without hard-coding process.env.XXXX
33
- somewhere in the file, complicating our ability to add additional
34
- application-specific configuration without explicitly merging it into the
35
- configuration document after it's been created in frontend-platform.
36
- - The method can *only* handle strings.
37
-
38
- Other data types are converted to strings::
39
-
40
- # Build command:
41
- BOOLEAN_VAR=false NULL_VAR=null NUMBER_VAR=123 npm run build
42
-
43
- ...
44
-
45
- // Source code:
46
- const BOOLEAN_VAR = process.env.BOOLEAN_VAR;
47
- const NULL_VAR = process.env.NULL_VAR;
48
- const NUMBER_VAR = process.env.NUMBER_VAR;
49
-
50
- ...
51
-
52
- // Compiled source after the build runs:
53
- const BOOLEAN_VAR = "false";
54
- const NULL_VAR = "null";
55
- const NUMBER_VAR = "123";
56
-
57
- This is not good!
58
-
59
- - It makes it very difficult to supply array and object configuration
60
- variables, and unreasonable to supply class or function config since we'd
61
- have to ``eval()`` them.
62
-
63
- Related to all this, frontend-platform has long had the ability to replace the
64
- implementations of its analytics, auth, and logging services, but no way to
65
- actually *configure* the app with a new implementation. Because of the above
66
- limitations, there's no reasonable way to configure a JavaScript class via
67
- environment variables.
68
-
69
- Decision
70
- --------
71
-
72
- For the above reasons, we will deprecate environment variable configuration in
73
- favor of JavaScript file configuration.
74
-
75
- This method makes use of an ``env.config.js`` file to supply configuration
76
- variables to an application::
77
-
78
- const config = {
79
- LMS_BASE_URL: 'http://localhost:18000',
80
- BOOLEAN_VAR: false,
81
- NULL_VAR: null,
82
- NUMBER_VAR: 123
83
- };
84
-
85
- export default config;
86
-
87
- This file is imported by the frontend-build webpack build process if it exists,
88
- and expected by frontend-platform as part of its initialization process. If the
89
- file doesn't exist, frontend-build falls back to importing an empty object for
90
- backwards compatibility. This functionality already exists today in
91
- frontend-build in preparation for using it here in frontend-platform.
92
-
93
- This interdependency creates a peerDependency for frontend-platform on `frontend-build v8.1.0 <frontend_build_810_>`_ or
94
- later.
95
-
96
- Using a JavaScript file for configuration is standard practice in the
97
- JavaScript/node community. Babel, webpack, eslint, Jest, etc., all accept
98
- configuration via JavaScript files (which we take advantage of in
99
- frontend-build), so there is ample precedent for using a .js file for
100
- configuration.
101
-
102
- In order to achieve deprecation of environment variable configuration, we will
103
- follow the deprecation process described in
104
- `OEP-21: Deprecation and Removal <oep21_>`_. In addition, we will add
105
- build-time warnings to frontend-build indicating the deprecation of environment
106
- variable configuration. Practically speaking, this will mean adjusting build
107
- processes throughout the community and in common tools like Tutor.
108
-
109
- Relationship to runtime configuration
110
- *************************************
111
-
112
- JavaScript file configuration is compatible with runtime MFE configuration.
113
- frontend-platform loads configuration in a predictable order:
114
-
115
- - environment variable config
116
- - optional handlers (commonly used to merge MFE-specific config in via additional
117
- process.env variables)
118
- - JS file config
119
- - runtime config
120
-
121
- In the end, runtime config wins. That said, JS file config solves some use
122
- cases that runtime config can't solve around extensibility and customization.
123
-
124
- In the future if we deprecate environment variable config, it's likely that
125
- we keep both JS file config and runtime configuration around. JS file config
126
- primarily to handle extensibility, and runtime config for everything else.
127
-
128
- Rejected Alternatives
129
- ---------------------
130
-
131
- Another option was to use JSON files for this purpose. This solves some of our
132
- issues (limited use of non-string primitive data types) but is otherwise not
133
- nearly as expressive or flexible as using a JavaScript file directly.
134
- Anecdotally, in the past frontend-build used JSON versions of many of
135
- its configuration files (Babel, eslint, jest) but over time they were all
136
- converted to JavaScript files so we could express more complicated
137
- configuration needs. Since one of the primary use cases and reasons we need a
138
- new configuration method is to allow developers to supply alternate
139
- implementations of frontend-platform's core services (analytics, logging), JSON
140
- was effectively a non-starter.
141
-
142
- .. _oep21: https://docs.openedx.org/projects/openedx-proposals/en/latest/processes/oep-0021-proc-deprecation.html
143
- .. _frontend_build_810: https://github.com/openedx/frontend-build/releases/tag/v8.1.0
@@ -1,58 +0,0 @@
1
- #####################################################################
2
- How to: Convert SnakeCase to CamelCase automatically for API Requests
3
- #####################################################################
4
-
5
- Introduction
6
- ************
7
-
8
- When using the HTTP client from ``@edx/frontend-platform``, you are making an API request to an
9
- Open edX service which requires you to handle snake-cased <-> camelCase conversions manually. The manual conversion quickly gets
10
- tedious, and is error prone if you forget to do it.
11
-
12
- Here is how you can configure the HTTP client to automatically convert snake_case <-> camelCase for you.
13
-
14
- How do I use configure automatic case conversion?
15
- *************************************************
16
-
17
- You want to install `axios-case-converter <https://www.npmjs.com/package/axios-case-converter>`_, and add it
18
- as a middleware when calling ``initialize`` in the consumer::
19
-
20
- import axiosCaseConverter from 'axios-case-converter';
21
-
22
- initialize({
23
- messages: [],
24
- requireAuthenticatedUser: true,
25
- hydrateAuthenticatedUser: true,
26
- authMiddleware: [axiosCaseConverter],
27
- });
28
-
29
- Or, if you choose to use ``configure`` instead::
30
-
31
- import axiosCaseConverter from 'axios-case-converter';
32
-
33
- configure({
34
- loggingService: getLoggingService(),
35
- config: getConfig(),
36
- options: {
37
- middleware: [axiosCaseConverter, (client) => axiosRetry(client, { retries: 3 })]
38
- }
39
- });
40
-
41
- By default the middleware will convert camelCase -> snake_case for payloads, and snake_case -> camelCase for responses.
42
- If you want to customize middleware behavior, i.e. only have responses transformed, you can configure it like this::
43
- initialize({
44
- messages: [],
45
- requireAuthenticatedUser: true,
46
- hydrateAuthenticatedUser: true,
47
- authMiddleware: [(client) => axiosCaseConverter(client, {
48
- // options for the middleware
49
- ignoreHeaders: true, // don't convert headers
50
- caseMiddleware: {
51
- requestInterceptor: (config) => {
52
- return config;
53
- }
54
- }
55
- })],
56
- });
57
-
58
- See `axios-case-converter <https://github.com/mpyw/axios-case-converter>`_ for more details on configurations supported by the package.
@@ -1,93 +0,0 @@
1
- ############################
2
- How to: Caching API Requests
3
- ############################
4
-
5
- .. contents:: Table of Contents
6
-
7
- Introduction
8
- ************
9
-
10
- Often, a web application needs to make the same repeated API calls, where the data returned is mostly static (i.e., does not change often). In such situations, caching is a viable solution to prevent unnecessary and potentially expensive operations, resulting in increased performance and a better user experience.
11
-
12
- When considering caching options, there's a few approaches to consider:
13
-
14
- Server caching
15
- ==============
16
-
17
- Server caching helps to limit the cost of an API request on the server and its dependent systems. When a client makes a request to the API, the server will check for a local copy of the requested resource. If the local resource exists, the server will respond with it; otherwise, the request is processed normally (e.g., performing database lookups). With this approach, the cost savings to the server may be significant, utilizing less resources.
18
-
19
- Client caching
20
- ==============
21
-
22
- Client caching helps to limit the cost of an API request incurred by the user. Rather than relying on a API implementing server caching, commonly referenced data may be cached locally within the client (i.e., browser). Similar to server caching, on the first request of an API, the request will occur as usual but the response data will be stored in the client.
23
-
24
- However, on subsequent requests (within a given timeframe), the client will check if a copy of the requested resource exists. If it does, it will read from the client cache rather than making a network request to the API. This has the benefit of freeing up resources on the server as it no longer needs to handle repeat queries within a given timeframe.
25
-
26
- Typically, client caching stores data for a given time (e.g., 5 minutes) since it was last requested. This allows the client cache to be dynamic in the sense that data will be eventually consistent and unneeded local data can be cleared once it is no longer relevant.
27
-
28
- Hybrid caching
29
- ==============
30
-
31
- Both server and client caching can be combined to provide best of both worlds. Regardless of how an API request is made, utilizing a hybrid approach will ensure data is read from a local cache first, whether that be on the server or the client.
32
-
33
- How do I use client caching with ``@edx/frontend-platform?``
34
- ************************************************************
35
-
36
- The ``@edx/frontend-platform`` package supports an Axios HTTP client that uses client caching under the hood through the use of `axios-cache-interceptor <https://www.npmjs.com/package/axios-cache-interceptor>`_.
37
-
38
- Currently, `localForage <https://www.npmjs.com/package/localforage>`_ is configured to be the underlying storage; ``localForage`` is a package that provides a similar API to browser's localStorage, except is asynchronous (non-blocking). ``localForage`` is configured to prefer using IndexedDB, falling back to localStorage if browser support for IndexedDB is lacking. If all else fails, an in-memory store is used instead. IndexedDB is more performant and may contain a broader range (and volume) of data in comparison to localStorage (see `browser support <https://caniuse.com/indexeddb>`_ for IndexedDB).
39
-
40
- When importing an HTTP client from ``@edx/frontend-platform``, you may specify options for how you wish to configure the HTTP client::
41
-
42
- import { getAuthenticatedHttpClient } from '@edx/frontend-platform';
43
-
44
- // ``cachedHttpClient`` is configured to use client caching under the hood.
45
- const cachedHttpClient = getAuthenticatedHttpClient({ useCache: true });
46
-
47
- The examples below demonstrate how to configure commonly used caching behavior on a per-request basis. For more use-cases, you may refer to the `axios-cache-interceptor documentation <https://axios-cache-interceptor.js.org/#/pages/per-request-configuration>`_.
48
-
49
- Overriding the request id
50
- =========================
51
-
52
- Every request passed through the axios-cache-interceptor interceptor has an id. Each request id is responsible for binding a request to its cache
53
- for referencing (or invalidating) it later.
54
-
55
- const { id: requestId} = cachedHttpClient.get('/courses/', {
56
- id: 'custom-id'
57
- });
58
-
59
- Modify expiry behavior for a single request
60
- ===========================================
61
-
62
- By default, API requests using the cached HTTP client will be cached for 5 minutes. However, cache options may be configured on a per-request basis::
63
-
64
- cachedHttpClient.get('/courses/', {
65
- cache: {
66
- ttl: 15 * 60 * 1000, // 15 minutes instead of the default 5.
67
- },
68
- });
69
-
70
- Invalidate cache for a single request
71
- =====================================
72
-
73
- In certain situations, it may be necessary to invalidate cached data to force a true network request to the server::
74
-
75
- cachedHttpClient.get('/courses/', {
76
- cache: {
77
- override: true, // This will replace the cache when the response arrives.
78
- },
79
- });
80
-
81
- To remove the cache of a request::
82
-
83
- await axios.storage.remove('list-posts');
84
-
85
-
86
- Check if response is served from network or from cache
87
- ======================================================
88
-
89
- If there is a need to know whether a response was served from the network (i.e., server) or from the local client cache, you may refer to the ``response.request`` object::
90
-
91
- cachedHttpClient.get('/courses/').then((response) => {
92
- console.log(response.cached); // will be true if served from the client cache, false otherwise
93
- });