@edx/frontend-platform 4.6.0 → 4.6.2

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/.env.development +30 -0
  2. package/.env.test +30 -0
  3. package/.eslintignore +6 -0
  4. package/.eslintrc.js +28 -0
  5. package/.github/PULL_REQUEST_TEMPLATE.md +13 -0
  6. package/.github/workflows/add-depr-ticket-to-depr-board.yml +19 -0
  7. package/.github/workflows/add-remove-label-on-comment.yml +20 -0
  8. package/.github/workflows/ci.yml +42 -0
  9. package/.github/workflows/commitlint.yml +10 -0
  10. package/.github/workflows/lockfileversion-check.yml +13 -0
  11. package/.github/workflows/manual-publish.yml +43 -0
  12. package/.github/workflows/npm-deprecate.yml +22 -0
  13. package/.github/workflows/release.yml +45 -0
  14. package/.github/workflows/self-assign-issue.yml +12 -0
  15. package/.github/workflows/update-browserslist-db.yml +12 -0
  16. package/.nvmrc +1 -0
  17. package/.releaserc +32 -0
  18. package/catalog-info.yaml +21 -0
  19. package/dist/LICENSE +661 -0
  20. package/dist/README.md +155 -0
  21. package/dist/package.json +86 -0
  22. package/docs/addTagsPlugin.js +10 -0
  23. package/docs/auth-API.md +114 -0
  24. package/docs/decisions/0001-record-architecture-decisions.rst +32 -0
  25. package/docs/decisions/0002-frontend-base-design-goals.rst +222 -0
  26. package/docs/decisions/0003-consolidation-into-frontend-platform.rst +71 -0
  27. package/docs/decisions/0004-axios-caching-implementation.rst +88 -0
  28. package/docs/decisions/0005-token-null-after-successful-refresh.rst +69 -0
  29. package/docs/decisions/0006-middleware-support-for-http-clients.rst +44 -0
  30. package/docs/decisions/0007-javascript-file-configuration.rst +143 -0
  31. package/docs/how_tos/automatic-case-conversion.rst +58 -0
  32. package/docs/how_tos/caching.rst +93 -0
  33. package/docs/how_tos/i18n.rst +305 -0
  34. package/docs/removeExport.js +24 -0
  35. package/docs/template/edx/README.md +12 -0
  36. package/docs/template/edx/publish.js +713 -0
  37. package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.eot +0 -0
  38. package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.svg +1830 -0
  39. package/docs/template/edx/static/fonts/OpenSans-Bold-webfont.woff +0 -0
  40. package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  41. package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  42. package/docs/template/edx/static/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  43. package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.eot +0 -0
  44. package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.svg +1830 -0
  45. package/docs/template/edx/static/fonts/OpenSans-Italic-webfont.woff +0 -0
  46. package/docs/template/edx/static/fonts/OpenSans-Light-webfont.eot +0 -0
  47. package/docs/template/edx/static/fonts/OpenSans-Light-webfont.svg +1831 -0
  48. package/docs/template/edx/static/fonts/OpenSans-Light-webfont.woff +0 -0
  49. package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  50. package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  51. package/docs/template/edx/static/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  52. package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.eot +0 -0
  53. package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.svg +1831 -0
  54. package/docs/template/edx/static/fonts/OpenSans-Regular-webfont.woff +0 -0
  55. package/docs/template/edx/static/scripts/linenumber.js +25 -0
  56. package/docs/template/edx/static/scripts/prettify/Apache-License-2.0.txt +202 -0
  57. package/docs/template/edx/static/scripts/prettify/lang-css.js +2 -0
  58. package/docs/template/edx/static/scripts/prettify/prettify.js +28 -0
  59. package/docs/template/edx/static/styles/jsdoc-default.css +356 -0
  60. package/docs/template/edx/static/styles/prettify-jsdoc.css +111 -0
  61. package/docs/template/edx/static/styles/prettify-tomorrow.css +132 -0
  62. package/docs/template/edx/tmpl/augments.tmpl +10 -0
  63. package/docs/template/edx/tmpl/container.tmpl +196 -0
  64. package/docs/template/edx/tmpl/details.tmpl +143 -0
  65. package/docs/template/edx/tmpl/example.tmpl +2 -0
  66. package/docs/template/edx/tmpl/examples.tmpl +13 -0
  67. package/docs/template/edx/tmpl/exceptions.tmpl +32 -0
  68. package/docs/template/edx/tmpl/layout.tmpl +39 -0
  69. package/docs/template/edx/tmpl/mainpage.tmpl +10 -0
  70. package/docs/template/edx/tmpl/members.tmpl +38 -0
  71. package/docs/template/edx/tmpl/method.tmpl +131 -0
  72. package/docs/template/edx/tmpl/modifies.tmpl +14 -0
  73. package/docs/template/edx/tmpl/params.tmpl +131 -0
  74. package/docs/template/edx/tmpl/properties.tmpl +108 -0
  75. package/docs/template/edx/tmpl/returns.tmpl +19 -0
  76. package/docs/template/edx/tmpl/source.tmpl +8 -0
  77. package/docs/template/edx/tmpl/tutorial.tmpl +19 -0
  78. package/docs/template/edx/tmpl/type.tmpl +7 -0
  79. package/env.config.js +8 -0
  80. package/jsdoc.json +36 -0
  81. package/openedx.yaml +12 -0
  82. package/package.json +6 -6
  83. package/service-interface.png +0 -0
  84. package/src/analytics/MockAnalyticsService.js +71 -0
  85. package/src/analytics/SegmentAnalyticsService.js +243 -0
  86. package/src/analytics/index.js +12 -0
  87. package/src/analytics/interface.js +142 -0
  88. package/src/auth/AxiosCsrfTokenService.js +60 -0
  89. package/src/auth/AxiosJwtAuthService.js +364 -0
  90. package/src/auth/AxiosJwtTokenService.js +134 -0
  91. package/src/auth/LocalForageCache.js +78 -0
  92. package/src/auth/MockAuthService.js +285 -0
  93. package/src/auth/index.js +19 -0
  94. package/src/auth/interceptors/createCsrfTokenProviderInterceptor.js +37 -0
  95. package/src/auth/interceptors/createJwtTokenProviderInterceptor.js +38 -0
  96. package/src/auth/interceptors/createProcessAxiosRequestErrorInterceptor.js +20 -0
  97. package/src/auth/interceptors/createRetryInterceptor.js +72 -0
  98. package/src/auth/interface.js +309 -0
  99. package/src/auth/utils.js +105 -0
  100. package/src/config.js +327 -0
  101. package/src/constants.js +66 -0
  102. package/src/i18n/countries.js +57 -0
  103. package/src/i18n/index.js +123 -0
  104. package/src/i18n/injectIntlWithShim.jsx +45 -0
  105. package/src/i18n/languages.js +60 -0
  106. package/src/i18n/lib.js +282 -0
  107. package/src/i18n/scripts/README.md +29 -0
  108. package/src/i18n/scripts/intl-imports.js +259 -0
  109. package/src/i18n/scripts/transifex-utils.js +75 -0
  110. package/src/index.js +42 -0
  111. package/src/initialize.js +357 -0
  112. package/src/logging/MockLoggingService.js +31 -0
  113. package/src/logging/NewRelicLoggingService.js +181 -0
  114. package/src/logging/index.js +9 -0
  115. package/src/logging/interface.js +110 -0
  116. package/src/pubSub.js +47 -0
  117. package/src/react/AppContext.jsx +24 -0
  118. package/src/react/AppProvider.jsx +93 -0
  119. package/src/react/AuthenticatedPageRoute.jsx +60 -0
  120. package/src/react/ErrorBoundary.jsx +44 -0
  121. package/src/react/ErrorPage.jsx +76 -0
  122. package/src/react/LoginRedirect.jsx +16 -0
  123. package/src/react/OptionalReduxProvider.jsx +28 -0
  124. package/src/react/PageRoute.jsx +31 -0
  125. package/src/react/hooks.js +50 -0
  126. package/src/react/index.js +16 -0
  127. package/src/scripts/GoogleAnalyticsLoader.js +53 -0
  128. package/src/scripts/index.js +2 -0
  129. package/src/testing/index.js +9 -0
  130. package/src/testing/initializeMockApp.js +77 -0
  131. package/src/testing/mockMessages.js +21 -0
  132. package/src/utils.js +167 -0
  133. /package/{analytics → dist/analytics}/MockAnalyticsService.js +0 -0
  134. /package/{analytics → dist/analytics}/MockAnalyticsService.js.map +0 -0
  135. /package/{analytics → dist/analytics}/SegmentAnalyticsService.js +0 -0
  136. /package/{analytics → dist/analytics}/SegmentAnalyticsService.js.map +0 -0
  137. /package/{analytics → dist/analytics}/index.js +0 -0
  138. /package/{analytics → dist/analytics}/index.js.map +0 -0
  139. /package/{analytics → dist/analytics}/interface.js +0 -0
  140. /package/{analytics → dist/analytics}/interface.js.map +0 -0
  141. /package/{auth → dist/auth}/AxiosCsrfTokenService.js +0 -0
  142. /package/{auth → dist/auth}/AxiosCsrfTokenService.js.map +0 -0
  143. /package/{auth → dist/auth}/AxiosJwtAuthService.js +0 -0
  144. /package/{auth → dist/auth}/AxiosJwtAuthService.js.map +0 -0
  145. /package/{auth → dist/auth}/AxiosJwtTokenService.js +0 -0
  146. /package/{auth → dist/auth}/AxiosJwtTokenService.js.map +0 -0
  147. /package/{auth → dist/auth}/LocalForageCache.js +0 -0
  148. /package/{auth → dist/auth}/LocalForageCache.js.map +0 -0
  149. /package/{auth → dist/auth}/MockAuthService.js +0 -0
  150. /package/{auth → dist/auth}/MockAuthService.js.map +0 -0
  151. /package/{auth → dist/auth}/index.js +0 -0
  152. /package/{auth → dist/auth}/index.js.map +0 -0
  153. /package/{auth → dist/auth}/interceptors/createCsrfTokenProviderInterceptor.js +0 -0
  154. /package/{auth → dist/auth}/interceptors/createCsrfTokenProviderInterceptor.js.map +0 -0
  155. /package/{auth → dist/auth}/interceptors/createJwtTokenProviderInterceptor.js +0 -0
  156. /package/{auth → dist/auth}/interceptors/createJwtTokenProviderInterceptor.js.map +0 -0
  157. /package/{auth → dist/auth}/interceptors/createProcessAxiosRequestErrorInterceptor.js +0 -0
  158. /package/{auth → dist/auth}/interceptors/createProcessAxiosRequestErrorInterceptor.js.map +0 -0
  159. /package/{auth → dist/auth}/interceptors/createRetryInterceptor.js +0 -0
  160. /package/{auth → dist/auth}/interceptors/createRetryInterceptor.js.map +0 -0
  161. /package/{auth → dist/auth}/interface.js +0 -0
  162. /package/{auth → dist/auth}/interface.js.map +0 -0
  163. /package/{auth → dist/auth}/utils.js +0 -0
  164. /package/{auth → dist/auth}/utils.js.map +0 -0
  165. /package/{config.js → dist/config.js} +0 -0
  166. /package/{config.js.map → dist/config.js.map} +0 -0
  167. /package/{constants.js → dist/constants.js} +0 -0
  168. /package/{constants.js.map → dist/constants.js.map} +0 -0
  169. /package/{i18n → dist/i18n}/countries.js +0 -0
  170. /package/{i18n → dist/i18n}/countries.js.map +0 -0
  171. /package/{i18n → dist/i18n}/index.js +0 -0
  172. /package/{i18n → dist/i18n}/index.js.map +0 -0
  173. /package/{i18n → dist/i18n}/injectIntlWithShim.js +0 -0
  174. /package/{i18n → dist/i18n}/injectIntlWithShim.js.map +0 -0
  175. /package/{i18n → dist/i18n}/languages.js +0 -0
  176. /package/{i18n → dist/i18n}/languages.js.map +0 -0
  177. /package/{i18n → dist/i18n}/lib.js +0 -0
  178. /package/{i18n → dist/i18n}/lib.js.map +0 -0
  179. /package/{i18n → dist/i18n}/scripts/README.md +0 -0
  180. /package/{i18n → dist/i18n}/scripts/intl-imports.js +0 -0
  181. /package/{i18n → dist/i18n}/scripts/intl-imports.js.map +0 -0
  182. /package/{i18n → dist/i18n}/scripts/transifex-utils.js +0 -0
  183. /package/{i18n → dist/i18n}/scripts/transifex-utils.js.map +0 -0
  184. /package/{index.js → dist/index.js} +0 -0
  185. /package/{index.js.map → dist/index.js.map} +0 -0
  186. /package/{initialize.js → dist/initialize.js} +0 -0
  187. /package/{initialize.js.map → dist/initialize.js.map} +0 -0
  188. /package/{logging → dist/logging}/MockLoggingService.js +0 -0
  189. /package/{logging → dist/logging}/MockLoggingService.js.map +0 -0
  190. /package/{logging → dist/logging}/NewRelicLoggingService.js +0 -0
  191. /package/{logging → dist/logging}/NewRelicLoggingService.js.map +0 -0
  192. /package/{logging → dist/logging}/index.js +0 -0
  193. /package/{logging → dist/logging}/index.js.map +0 -0
  194. /package/{logging → dist/logging}/interface.js +0 -0
  195. /package/{logging → dist/logging}/interface.js.map +0 -0
  196. /package/{pubSub.js → dist/pubSub.js} +0 -0
  197. /package/{pubSub.js.map → dist/pubSub.js.map} +0 -0
  198. /package/{react → dist/react}/AppContext.js +0 -0
  199. /package/{react → dist/react}/AppContext.js.map +0 -0
  200. /package/{react → dist/react}/AppProvider.js +0 -0
  201. /package/{react → dist/react}/AppProvider.js.map +0 -0
  202. /package/{react → dist/react}/AuthenticatedPageRoute.js +0 -0
  203. /package/{react → dist/react}/AuthenticatedPageRoute.js.map +0 -0
  204. /package/{react → dist/react}/ErrorBoundary.js +0 -0
  205. /package/{react → dist/react}/ErrorBoundary.js.map +0 -0
  206. /package/{react → dist/react}/ErrorPage.js +0 -0
  207. /package/{react → dist/react}/ErrorPage.js.map +0 -0
  208. /package/{react → dist/react}/LoginRedirect.js +0 -0
  209. /package/{react → dist/react}/LoginRedirect.js.map +0 -0
  210. /package/{react → dist/react}/OptionalReduxProvider.js +0 -0
  211. /package/{react → dist/react}/OptionalReduxProvider.js.map +0 -0
  212. /package/{react → dist/react}/PageRoute.js +0 -0
  213. /package/{react → dist/react}/PageRoute.js.map +0 -0
  214. /package/{react → dist/react}/hooks.js +0 -0
  215. /package/{react → dist/react}/hooks.js.map +0 -0
  216. /package/{react → dist/react}/index.js +0 -0
  217. /package/{react → dist/react}/index.js.map +0 -0
  218. /package/{scripts → dist/scripts}/GoogleAnalyticsLoader.js +0 -0
  219. /package/{scripts → dist/scripts}/GoogleAnalyticsLoader.js.map +0 -0
  220. /package/{scripts → dist/scripts}/index.js +0 -0
  221. /package/{scripts → dist/scripts}/index.js.map +0 -0
  222. /package/{testing → dist/testing}/index.js +0 -0
  223. /package/{testing → dist/testing}/index.js.map +0 -0
  224. /package/{testing → dist/testing}/initializeMockApp.js +0 -0
  225. /package/{testing → dist/testing}/initializeMockApp.js.map +0 -0
  226. /package/{testing → dist/testing}/mockMessages.js +0 -0
  227. /package/{testing → dist/testing}/mockMessages.js.map +0 -0
  228. /package/{utils.js → dist/utils.js} +0 -0
  229. /package/{utils.js.map → dist/utils.js.map} +0 -0
package/src/config.js ADDED
@@ -0,0 +1,327 @@
1
+ /**
2
+ * #### Import members from **@edx/frontend-platform**
3
+ *
4
+ * The configuration module provides utilities for working with an application's configuration
5
+ * document (ConfigDocument). Configuration variables can be supplied to the
6
+ * application in four different ways. They are applied in the following order:
7
+ *
8
+ * - Build-time Configuration
9
+ * - Environment Variables
10
+ * - JavaScript File
11
+ * - Runtime Configuration
12
+ *
13
+ * Last one in wins. Variables with the same name defined via the later methods will override any
14
+ * defined using an earlier method. i.e., if a variable is defined in Runtime Configuration, that
15
+ * will override the same variable defined in either Build-time Configuration method (environment
16
+ * variables or JS file). Configuration defined in a JS file will override environment variables.
17
+ *
18
+ * ##### Build-time Configuration
19
+ *
20
+ * Build-time configuration methods add config variables into the app when it is built by webpack.
21
+ * This saves the app an API call and means it has all the information it needs to initialize right
22
+ * away. There are two methods of supplying build-time configuration: environment variables and a
23
+ * JavaScript file.
24
+ *
25
+ * ###### Environment Variables
26
+ *
27
+ * A set list of required config variables can be supplied as
28
+ * command-line environment variables during the build process.
29
+ *
30
+ * As a simple example, these are supplied on the command-line before invoking `npm run build`:
31
+ *
32
+ * ```
33
+ * LMS_BASE_URL=http://localhost:18000 npm run build
34
+ * ```
35
+ *
36
+ * Note that additional variables _cannot_ be supplied via this method without using the `config`
37
+ * initialization handler. The app won't pick them up and they'll appear `undefined`.
38
+ *
39
+ * This configuration method is being deprecated in favor of JavaScript File Configuration.
40
+ *
41
+ * ###### JavaScript File Configuration
42
+ *
43
+ * Configuration variables can be supplied in an optional file named env.config.js. This file must
44
+ * export either an Object containing configuration variables or a function. The function must
45
+ * return an Object containing configuration variables or, alternately, a promise which resolves to
46
+ * an Object.
47
+ *
48
+ * Using a function or async function allows the configuration to be resolved at runtime (because
49
+ * the function will be executed at runtime). This is not common, and the capability is included
50
+ * for the sake of flexibility.
51
+ *
52
+ * JavaScript File Configuration is well-suited to extensibility use cases or component overrides,
53
+ * in that the configuration file can depend on any installed JavaScript module. It is also the
54
+ * preferred way of doing build-time configuration if runtime configuration isn't used by your
55
+ * deployment of the platform.
56
+ *
57
+ * Exporting a config object:
58
+ * ```
59
+ * const config = {
60
+ * LMS_BASE_URL: 'http://localhost:18000'
61
+ * };
62
+ *
63
+ * export default config;
64
+ * ```
65
+ *
66
+ * Exporting a function that returns an object:
67
+ * ```
68
+ * function getConfig() {
69
+ * return {
70
+ * LMS_BASE_URL: 'http://localhost:18000'
71
+ * };
72
+ * }
73
+ * ```
74
+ *
75
+ * Exporting a function that returns a promise that resolves to an object:
76
+ * ```
77
+ * function getAsyncConfig() {
78
+ * return new Promise((resolve, reject) => {
79
+ * resolve({
80
+ * LMS_BASE_URL: 'http://localhost:18000'
81
+ * });
82
+ * });
83
+ * }
84
+ *
85
+ * export default getAsyncConfig;
86
+ * ```
87
+ *
88
+ * ##### Runtime Configuration
89
+ *
90
+ * Configuration variables can also be supplied using the "runtime configuration" method, taking
91
+ * advantage of the Micro-frontend Config API in edx-platform. More information on this API can be
92
+ * found in the ADR which introduced it:
93
+ *
94
+ * https://github.com/openedx/edx-platform/blob/master/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst
95
+ *
96
+ * The runtime configuration method can be enabled by supplying a MFE_CONFIG_API_URL via one of the other
97
+ * two configuration methods above.
98
+ *
99
+ * Runtime configuration is particularly useful if you need to supply different configurations to
100
+ * a single deployment of a micro-frontend, for instance. It is also a perfectly valid alternative
101
+ * to build-time configuration, though it introduces an additional API call to edx-platform on MFE
102
+ * initialization.
103
+ *
104
+ * ##### Initialization Config Handler
105
+ *
106
+ * The configuration document can be extended by
107
+ * applications at run-time using a `config` initialization handler. Please see the Initialization
108
+ * documentation for more information on handlers and initialization phases.
109
+ *
110
+ * ```
111
+ * initialize({
112
+ * handlers: {
113
+ * config: () => {
114
+ * mergeConfig({
115
+ * CUSTOM_VARIABLE: 'custom value',
116
+ * LMS_BASE_URL: 'http://localhost:18001' // You can override variables, but this is uncommon.
117
+ * }, 'App config override handler');
118
+ * },
119
+ * },
120
+ * });
121
+ * ```
122
+ *
123
+ * @module Config
124
+ */
125
+
126
+ import { APP_CONFIG_INITIALIZED, CONFIG_CHANGED } from './constants';
127
+
128
+ import { publish, subscribe } from './pubSub';
129
+ import { ensureDefinedConfig } from './utils';
130
+
131
+ function extractRegex(envVar) {
132
+ // Convert the environment variable string to a regex, while guarding
133
+ // against a non-string and an empty/whitespace-only string.
134
+ if (typeof envVar === 'string' && envVar.trim() !== '') {
135
+ return new RegExp(envVar);
136
+ }
137
+ return undefined;
138
+ }
139
+
140
+ const ENVIRONMENT = process.env.NODE_ENV;
141
+ let config = {
142
+ ACCESS_TOKEN_COOKIE_NAME: process.env.ACCESS_TOKEN_COOKIE_NAME,
143
+ ACCOUNT_PROFILE_URL: process.env.ACCOUNT_PROFILE_URL,
144
+ ACCOUNT_SETTINGS_URL: process.env.ACCOUNT_SETTINGS_URL,
145
+ BASE_URL: process.env.BASE_URL,
146
+ PUBLIC_PATH: process.env.PUBLIC_PATH || '/',
147
+ CREDENTIALS_BASE_URL: process.env.CREDENTIALS_BASE_URL,
148
+ CSRF_TOKEN_API_PATH: process.env.CSRF_TOKEN_API_PATH,
149
+ DISCOVERY_API_BASE_URL: process.env.DISCOVERY_API_BASE_URL,
150
+ PUBLISHER_BASE_URL: process.env.PUBLISHER_BASE_URL,
151
+ ECOMMERCE_BASE_URL: process.env.ECOMMERCE_BASE_URL,
152
+ ENVIRONMENT,
153
+ IGNORED_ERROR_REGEX: extractRegex(process.env.IGNORED_ERROR_REGEX),
154
+ LANGUAGE_PREFERENCE_COOKIE_NAME: process.env.LANGUAGE_PREFERENCE_COOKIE_NAME,
155
+ LEARNING_BASE_URL: process.env.LEARNING_BASE_URL,
156
+ LMS_BASE_URL: process.env.LMS_BASE_URL,
157
+ LOGIN_URL: process.env.LOGIN_URL,
158
+ LOGOUT_URL: process.env.LOGOUT_URL,
159
+ STUDIO_BASE_URL: process.env.STUDIO_BASE_URL,
160
+ MARKETING_SITE_BASE_URL: process.env.MARKETING_SITE_BASE_URL,
161
+ ORDER_HISTORY_URL: process.env.ORDER_HISTORY_URL,
162
+ REFRESH_ACCESS_TOKEN_ENDPOINT: process.env.REFRESH_ACCESS_TOKEN_ENDPOINT,
163
+ SECURE_COOKIES: ENVIRONMENT !== 'development',
164
+ SEGMENT_KEY: process.env.SEGMENT_KEY,
165
+ SITE_NAME: process.env.SITE_NAME,
166
+ USER_INFO_COOKIE_NAME: process.env.USER_INFO_COOKIE_NAME,
167
+ LOGO_URL: process.env.LOGO_URL,
168
+ LOGO_TRADEMARK_URL: process.env.LOGO_TRADEMARK_URL,
169
+ LOGO_WHITE_URL: process.env.LOGO_WHITE_URL,
170
+ FAVICON_URL: process.env.FAVICON_URL,
171
+ MFE_CONFIG_API_URL: process.env.MFE_CONFIG_API_URL,
172
+ APP_ID: process.env.APP_ID,
173
+ SUPPORT_URL: process.env.SUPPORT_URL,
174
+ };
175
+
176
+ /**
177
+ * Getter for the application configuration document. This is synchronous and merely returns a
178
+ * reference to an existing object, and is thus safe to call as often as desired.
179
+ *
180
+ * Example:
181
+ *
182
+ * ```
183
+ * import { getConfig } from '@edx/frontend-platform';
184
+ *
185
+ * const {
186
+ * LMS_BASE_URL,
187
+ * } = getConfig();
188
+ * ```
189
+ *
190
+ * @returns {ConfigDocument}
191
+ */
192
+ export function getConfig() {
193
+ return config;
194
+ }
195
+
196
+ /**
197
+ * Replaces the existing ConfigDocument. This is not commonly used, but can be helpful for tests.
198
+ *
199
+ * The supplied config document will be tested with `ensureDefinedConfig` to ensure it does not
200
+ * have any `undefined` keys.
201
+ *
202
+ * Example:
203
+ *
204
+ * ```
205
+ * import { setConfig } from '@edx/frontend-platform';
206
+ *
207
+ * setConfig({
208
+ * LMS_BASE_URL, // This is overriding the ENTIRE document - this is not merged in!
209
+ * });
210
+ * ```
211
+ *
212
+ * @param {ConfigDocument} newConfig
213
+ */
214
+ export function setConfig(newConfig) {
215
+ ensureDefinedConfig(config, 'config');
216
+ config = newConfig;
217
+ publish(CONFIG_CHANGED);
218
+ }
219
+
220
+ /**
221
+ * Merges additional configuration values into the ConfigDocument returned by `getConfig`. Will
222
+ * override any values that exist with the same keys.
223
+ *
224
+ * ```
225
+ * mergeConfig({
226
+ * NEW_KEY: 'new value',
227
+ * OTHER_NEW_KEY: 'other new value',
228
+ * });
229
+ *
230
+ * If any of the key values are `undefined`, an error will be logged to 'warn'.
231
+ *
232
+ * @param {Object} newConfig
233
+ */
234
+ export function mergeConfig(newConfig) {
235
+ ensureDefinedConfig(newConfig, 'ProcessEnvConfigService');
236
+ config = Object.assign(config, newConfig);
237
+ publish(CONFIG_CHANGED);
238
+ }
239
+
240
+ /**
241
+ * A method allowing application code to indicate that particular ConfigDocument keys are required
242
+ * for them to function. This is useful for diagnosing development/deployment issues, primarily,
243
+ * by surfacing misconfigurations early. For instance, if the build process fails to supply an
244
+ * environment variable on the command-line, it's possible that one of the `process.env` variables
245
+ * will be undefined. Should be used in conjunction with `mergeConfig` for custom `ConfigDocument`
246
+ * properties. Requester is for informational/error reporting purposes only.
247
+ *
248
+ * ```
249
+ * ensureConfig(['LMS_BASE_URL', 'LOGIN_URL'], 'MySpecialComponent');
250
+ *
251
+ * // Will log a warning with:
252
+ * // "App configuration error: LOGIN_URL is required by MySpecialComponent."
253
+ * // if LOGIN_URL is undefined, for example.
254
+ * ```
255
+ *
256
+ * *NOTE*: `ensureConfig` waits until `APP_CONFIG_INITIALIZED` is published to verify the existence
257
+ * of the specified properties. This means that this function is compatible with custom `config`
258
+ * phase handlers responsible for loading additional configuration data in the initialization
259
+ * sequence.
260
+ *
261
+ * @param {Array} keys
262
+ * @param {string} [requester='unspecified application code']
263
+ */
264
+ export function ensureConfig(keys, requester = 'unspecified application code') {
265
+ subscribe(APP_CONFIG_INITIALIZED, () => {
266
+ keys.forEach((key) => {
267
+ if (config[key] === undefined) {
268
+ // eslint-disable-next-line no-console
269
+ console.warn(`App configuration error: ${key} is required by ${requester}.`);
270
+ }
271
+ });
272
+ });
273
+ }
274
+
275
+ /**
276
+ * An object describing the current application configuration.
277
+ *
278
+ * In its most basic form, the initialization process loads this document via `process.env`
279
+ * variables. There are other ways to add configuration variables to the ConfigDocument as
280
+ * documented above (JavaScript File Configuration, Runtime Configuration, and the Initialization
281
+ * Config Handler)
282
+ *
283
+ * ```
284
+ * {
285
+ * BASE_URL: process.env.BASE_URL,
286
+ * // ... other vars
287
+ * }
288
+ * ```
289
+ *
290
+ * When using Webpack (i.e., normal usage), the build process is responsible for supplying these
291
+ * variables via command-line environment variables. That means they must be supplied at build
292
+ * time.
293
+ *
294
+ * @name ConfigDocument
295
+ * @memberof module:Config
296
+ * @property {string} ACCESS_TOKEN_COOKIE_NAME
297
+ * @property {string} ACCOUNT_PROFILE_URL
298
+ * @property {string} ACCOUNT_SETTINGS_URL
299
+ * @property {string} BASE_URL The URL of the current application.
300
+ * @property {string} CREDENTIALS_BASE_URL
301
+ * @property {string} CSRF_TOKEN_API_PATH
302
+ * @property {string} DISCOVERY_API_BASE_URL
303
+ * @property {string} PUBLISHER_BASE_URL
304
+ * @property {string} ECOMMERCE_BASE_URL
305
+ * @property {string} ENVIRONMENT This is one of: development, production, or test.
306
+ * @property {string} IGNORED_ERROR_REGEX
307
+ * @property {string} LANGUAGE_PREFERENCE_COOKIE_NAME
308
+ * @property {string} LEARNING_BASE_URL
309
+ * @property {string} LMS_BASE_URL
310
+ * @property {string} LOGIN_URL
311
+ * @property {string} LOGOUT_URL
312
+ * @property {string} STUDIO_BASE_URL
313
+ * @property {string} MARKETING_SITE_BASE_URL
314
+ * @property {string} ORDER_HISTORY_URL
315
+ * @property {string} REFRESH_ACCESS_TOKEN_ENDPOINT
316
+ * @property {boolean} SECURE_COOKIES
317
+ * @property {string} SEGMENT_KEY
318
+ * @property {string} SITE_NAME
319
+ * @property {string} USER_INFO_COOKIE_NAME
320
+ * @property {string} LOGO_URL
321
+ * @property {string} LOGO_TRADEMARK_URL
322
+ * @property {string} LOGO_WHITE_URL
323
+ * @property {string} FAVICON_URL
324
+ * @property {string} MFE_CONFIG_API_URL
325
+ * @property {string} APP_ID
326
+ * @property {string} SUPPORT_URL
327
+ */
@@ -0,0 +1,66 @@
1
+ /** @constant */
2
+ export const APP_TOPIC = 'APP';
3
+
4
+ export const APP_PUBSUB_INITIALIZED = `${APP_TOPIC}.PUBSUB_INITIALIZED`;
5
+
6
+ /**
7
+ * Event published when the application initialization sequence has finished loading any dynamic
8
+ * configuration setup in a custom config handler.
9
+ *
10
+ * @event
11
+ */
12
+ export const APP_CONFIG_INITIALIZED = `${APP_TOPIC}.CONFIG_INITIALIZED`;
13
+
14
+ /**
15
+ * Event published when the application initialization sequence has finished determining the user's
16
+ * authentication state, creating an authenticated API client, and executing auth handlers.
17
+ *
18
+ * @event
19
+ */
20
+ export const APP_AUTH_INITIALIZED = `${APP_TOPIC}.AUTH_INITIALIZED`;
21
+
22
+ /**
23
+ * Event published when the application initialization sequence has finished initializing
24
+ * internationalization and executing any i18n handlers.
25
+ *
26
+ * @event
27
+ */
28
+ export const APP_I18N_INITIALIZED = `${APP_TOPIC}.I18N_INITIALIZED`;
29
+
30
+ /**
31
+ * Event published when the application initialization sequence has finished initializing the
32
+ * logging service and executing any logging handlers.
33
+ *
34
+ * @event
35
+ */
36
+ export const APP_LOGGING_INITIALIZED = `${APP_TOPIC}.LOGGING_INITIALIZED`;
37
+
38
+ /**
39
+ * Event published when the application initialization sequence has finished initializing the
40
+ * analytics service and executing any analytics handlers.
41
+ *
42
+ * @event
43
+ */
44
+ export const APP_ANALYTICS_INITIALIZED = `${APP_TOPIC}.ANALYTICS_INITIALIZED`;
45
+
46
+ /**
47
+ * Event published when the application initialization sequence has finished. Applications should
48
+ * subscribe to this event and start rendering the UI when it has fired.
49
+ *
50
+ * @event
51
+ */
52
+ export const APP_READY = `${APP_TOPIC}.READY`;
53
+
54
+ /**
55
+ * Event published when the application initialization sequence has aborted. This is frequently
56
+ * used to show an error page when an initialization error has occurred.
57
+ *
58
+ * @see {@link module:React~ErrorPage}
59
+ * @event
60
+ */
61
+ export const APP_INIT_ERROR = `${APP_TOPIC}.INIT_ERROR`;
62
+
63
+ /** @constant */
64
+ export const CONFIG_TOPIC = 'CONFIG';
65
+
66
+ export const CONFIG_CHANGED = `${CONFIG_TOPIC}.CHANGED`;
@@ -0,0 +1,57 @@
1
+ /* eslint-disable import/extensions */
2
+ import COUNTRIES, { langs as countryLangs } from 'i18n-iso-countries';
3
+
4
+ import { getPrimaryLanguageSubtag } from './lib';
5
+
6
+ /*
7
+ * COUNTRY LISTS
8
+ *
9
+ * Lists of country names localized in supported languages.
10
+ *
11
+ * TODO: When we start dynamically loading translations only for the current locale, change this.
12
+ */
13
+
14
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/ar.json'));
15
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/en.json'));
16
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/es.json'));
17
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/fr.json'));
18
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/zh.json'));
19
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/ca.json'));
20
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/he.json'));
21
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/id.json'));
22
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/ko.json'));
23
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/pl.json'));
24
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/pt.json'));
25
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/ru.json'));
26
+ // COUNTRIES.registerLocale(require('i18n-iso-countries/langs/th.json')); // Doesn't exist in lib.
27
+ COUNTRIES.registerLocale(require('i18n-iso-countries/langs/uk.json'));
28
+
29
+ /**
30
+ * Provides a lookup table of country IDs to country names for the current locale.
31
+ *
32
+ * @memberof module:I18n
33
+ */
34
+ export function getCountryMessages(locale) {
35
+ const primaryLanguageSubtag = getPrimaryLanguageSubtag(locale);
36
+ const languageCode = countryLangs().includes(primaryLanguageSubtag) ? primaryLanguageSubtag : 'en';
37
+
38
+ return COUNTRIES.getNames(languageCode);
39
+ }
40
+
41
+ /**
42
+ * Provides a list of countries represented as objects of the following shape:
43
+ *
44
+ * {
45
+ * key, // The ID of the country
46
+ * name // The localized name of the country
47
+ * }
48
+ *
49
+ * TODO: ARCH-878: The list should be sorted alphabetically in the current locale.
50
+ * This is useful for populating dropdowns.
51
+ *
52
+ * @memberof module:I18n
53
+ */
54
+ export function getCountryList(locale) {
55
+ const countryMessages = getCountryMessages(locale);
56
+ return Object.entries(countryMessages).map(([code, name]) => ({ code, name }));
57
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * #### Import members from **@edx/frontend-platform/i18n**
3
+ * The i18n module relies on react-intl and re-exports all of that package's exports.
4
+ *
5
+ * For each locale we want to support, react-intl needs 1) the locale-data, which includes
6
+ * information about how to format numbers, handle plurals, etc., and 2) the translations, as an
7
+ * object holding message id / translated string pairs. A locale string and the messages object are
8
+ * passed into the IntlProvider element that wraps your element hierarchy.
9
+ *
10
+ * Note that react-intl has no way of checking if the translations you give it actually have
11
+ * anything to do with the locale you pass it; it will happily use whatever messages object you pass
12
+ * in. However, if the locale data for the locale you passed into the IntlProvider was not
13
+ * correctly installed with addLocaleData, all of your translations will fall back to the default
14
+ * (in our case English), *even if you gave IntlProvider the correct messages object for that
15
+ * locale*.
16
+ *
17
+ * Messages are provided to this module via the configure() function below.
18
+ *
19
+ *
20
+ * @module Internationalization
21
+ * @see {@link https://github.com/openedx/frontend-platform/blob/master/docs/how_tos/i18n.rst}
22
+ * @see {@link https://formatjs.io/docs/react-intl/components/ Intl} for components exported from this module.
23
+ *
24
+ */
25
+
26
+ /**
27
+ * @name createIntl
28
+ * @kind function
29
+ * @see {@link https://formatjs.io/docs/react-intl/api#createIntl Intl}
30
+ */
31
+
32
+ /**
33
+ * @name FormattedDate
34
+ * @kind class
35
+ * @see {@link https://formatjs.io/docs/react-intl/components/#formatteddate Intl}
36
+ */
37
+
38
+ /**
39
+ * @name FormattedTime
40
+ * @kind class
41
+ * @see {@link https://formatjs.io/docs/react-intl/components/#formattedtime Intl}
42
+ */
43
+
44
+ /**
45
+ * @name FormattedRelativeTime
46
+ * @kind class
47
+ * @see {@link https://formatjs.io/docs/react-intl/components/#formattedrelativetime Intl}
48
+ */
49
+
50
+ /**
51
+ * @name FormattedNumber
52
+ * @kind class
53
+ * @see {@link https://formatjs.io/docs/react-intl/components/#formattednumber Intl}
54
+ */
55
+
56
+ /**
57
+ * @name FormattedPlural
58
+ * @kind class
59
+ * @see {@link https://formatjs.io/docs/react-intl/components/#formattedplural Intl}
60
+ */
61
+
62
+ /**
63
+ * @name FormattedMessage
64
+ * @kind class
65
+ * @see {@link https://formatjs.io/docs/react-intl/components/#formattedmessage Intl}
66
+ */
67
+
68
+ /**
69
+ * @name IntlProvider
70
+ * @kind class
71
+ * @see {@link https://formatjs.io/docs/react-intl/components/#intlprovider Intl}
72
+ */
73
+
74
+ /**
75
+ * @name defineMessages
76
+ * @kind function
77
+ * @see {@link https://formatjs.io/docs/react-intl/api#definemessagesdefinemessage Intl}
78
+ */
79
+
80
+ /**
81
+ * @name useIntl
82
+ * @kind function
83
+ * @see {@link https://formatjs.io/docs/react-intl/api#useIntl Intl}
84
+ */
85
+
86
+ export {
87
+ createIntl,
88
+ FormattedDate,
89
+ FormattedTime,
90
+ FormattedRelativeTime,
91
+ FormattedNumber,
92
+ FormattedPlural,
93
+ FormattedMessage,
94
+ defineMessages,
95
+ IntlProvider,
96
+ useIntl,
97
+ } from 'react-intl';
98
+
99
+ export {
100
+ intlShape,
101
+ configure,
102
+ getPrimaryLanguageSubtag,
103
+ getLocale,
104
+ getMessages,
105
+ isRtl,
106
+ handleRtl,
107
+ LOCALE_CHANGED,
108
+ LOCALE_TOPIC,
109
+ } from './lib';
110
+
111
+ export {
112
+ default as injectIntl,
113
+ } from './injectIntlWithShim';
114
+
115
+ export {
116
+ getCountryList,
117
+ getCountryMessages,
118
+ } from './countries';
119
+
120
+ export {
121
+ getLanguageList,
122
+ getLanguageMessages,
123
+ } from './languages';
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { injectIntl } from 'react-intl';
3
+ import { getLoggingService, intlShape } from './lib';
4
+
5
+ /**
6
+ * This function wraps react-intl's injectIntl function in order to add error logging to the intl
7
+ * property's formatMessage function.
8
+ *
9
+ * @memberof I18n
10
+ */
11
+ const injectIntlWithShim = (WrappedComponent) => {
12
+ class ShimmedIntlComponent extends React.Component {
13
+ constructor(props) {
14
+ super(props);
15
+ this.shimmedIntl = Object.create(this.props.intl, {
16
+ formatMessage: {
17
+ value: (definition, ...args) => {
18
+ if (definition === undefined || definition.id === undefined) {
19
+ const error = new Error('i18n error: An undefined message was supplied to intl.formatMessage.');
20
+ if (process.env.NODE_ENV !== 'production') {
21
+ console.error(error); // eslint-disable-line no-console
22
+ return '!!! Missing message supplied to intl.formatMessage !!!';
23
+ }
24
+ getLoggingService().logError(error);
25
+ return ''; // Fail silently in production
26
+ }
27
+ return this.props.intl.formatMessage(definition, ...args);
28
+ },
29
+ },
30
+ });
31
+ }
32
+
33
+ render() {
34
+ return <WrappedComponent {...this.props} intl={this.shimmedIntl} />;
35
+ }
36
+ }
37
+
38
+ ShimmedIntlComponent.propTypes = {
39
+ intl: intlShape.isRequired,
40
+ };
41
+
42
+ return injectIntl(ShimmedIntlComponent);
43
+ };
44
+
45
+ export default injectIntlWithShim;
@@ -0,0 +1,60 @@
1
+ /* eslint-disable import/extensions */
2
+ import LANGUAGES, { langs as languageLangs } from '@cospired/i18n-iso-languages';
3
+
4
+ import { getPrimaryLanguageSubtag } from './lib';
5
+
6
+ /*
7
+ * LANGUAGE LISTS
8
+ *
9
+ * Lists of language names localized in supported languages.
10
+ *
11
+ * TODO: When we start dynamically loading translations only for the current locale, change this.
12
+ * TODO: Also note that a bunch of languages are missing here. They're present but commented out
13
+ * for reference. That's because they're not implemented in this library. If you read this and it's
14
+ * been a while, go check and see if that's changed!
15
+ */
16
+
17
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/ar.json'));
18
+ LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/en.json'));
19
+ LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/es.json'));
20
+ LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/fr.json'));
21
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/zh.json'));
22
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/ca.json'));
23
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/he.json'));
24
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/id.json'));
25
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/ko.json'));
26
+ LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/pl.json'));
27
+ LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/pt.json'));
28
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/ru.json'));
29
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/th.json'));
30
+ // LANGUAGES.registerLocale(require('@cospired/i18n-iso-languages/langs/uk.json'));
31
+
32
+ /**
33
+ * Provides a lookup table of language IDs to language names for the current locale.
34
+ *
35
+ * @memberof I18n
36
+ */
37
+ export const getLanguageMessages = (locale) => {
38
+ const primaryLanguageSubtag = getPrimaryLanguageSubtag(locale);
39
+ const languageCode = languageLangs().includes(primaryLanguageSubtag) ? primaryLanguageSubtag : 'en';
40
+
41
+ return LANGUAGES.getNames(languageCode);
42
+ };
43
+
44
+ /**
45
+ * Provides a list of languages represented as objects of the following shape:
46
+ *
47
+ * {
48
+ * key, // The ID of the language
49
+ * name // The localized name of the language
50
+ * }
51
+ *
52
+ * TODO: ARCH-878: The list should be sorted alphabetically in the current locale.
53
+ * This is useful for populating dropdowns.
54
+ *
55
+ * @memberof I18n
56
+ */
57
+ export const getLanguageList = (locale) => {
58
+ const languageMessages = getLanguageMessages(locale);
59
+ return Object.entries(languageMessages).map(([code, name]) => ({ code, name }));
60
+ };