@openedx/frontend-base 1.0.0-alpha.0

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 (356) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +112 -0
  3. package/config/babel/babel.config.js +28 -0
  4. package/config/config-helpers/createConfig.js +13 -0
  5. package/config/config-helpers/createLintConfig.js +16 -0
  6. package/config/config-helpers/getBaseConfig.js +12 -0
  7. package/config/defaultConfigPaths.js +35 -0
  8. package/config/eslint/base.eslint.config.js +113 -0
  9. package/config/index.js +12 -0
  10. package/config/jest/jest.config.js +30 -0
  11. package/config/tsconfig.json +32 -0
  12. package/config/types.js +25 -0
  13. package/config/webpack/common-config/all/getCodeRules.js +52 -0
  14. package/config/webpack/common-config/all/getFileLoaderRules.js +26 -0
  15. package/config/webpack/common-config/all/getIgnoreWarnings.js +14 -0
  16. package/config/webpack/common-config/all/getImageMinimizer.js +25 -0
  17. package/config/webpack/common-config/all/getStylesheetRule.js +112 -0
  18. package/config/webpack/common-config/dev/getDevServer.js +38 -0
  19. package/config/webpack/common-config/index.js +18 -0
  20. package/config/webpack/common-config/site/getHtmlWebpackPlugin.js +16 -0
  21. package/config/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.js +91 -0
  22. package/config/webpack/plugins/html-webpack-new-relic-plugin/index.js +7 -0
  23. package/config/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +3 -0
  24. package/config/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +108 -0
  25. package/config/webpack/plugins/paragon-webpack-plugin/index.js +7 -0
  26. package/config/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +64 -0
  27. package/config/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +53 -0
  28. package/config/webpack/plugins/paragon-webpack-plugin/utils/index.js +9 -0
  29. package/config/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +114 -0
  30. package/config/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +146 -0
  31. package/config/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +126 -0
  32. package/config/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +57 -0
  33. package/config/webpack/types.js +2 -0
  34. package/config/webpack/utils/getLocalAliases.js +65 -0
  35. package/config/webpack/utils/getPublicPath.js +6 -0
  36. package/config/webpack/utils/getResolvedSiteConfigPath.js +32 -0
  37. package/config/webpack/utils/paragonUtils.js +138 -0
  38. package/config/webpack/webpack.config.build.js +80 -0
  39. package/config/webpack/webpack.config.dev.js +76 -0
  40. package/config/webpack/webpack.config.dev.shell.js +110 -0
  41. package/eslint.config.js +18 -0
  42. package/frontend-base.d.ts +8 -0
  43. package/index.ts +7 -0
  44. package/jest.config.js +7 -0
  45. package/openedx-frontend-base.tgz +0 -0
  46. package/package.json +149 -0
  47. package/runtime/analytics/MockAnalyticsService.js +71 -0
  48. package/runtime/analytics/SegmentAnalyticsService.js +243 -0
  49. package/runtime/analytics/index.ts +12 -0
  50. package/runtime/analytics/interface.js +145 -0
  51. package/runtime/auth/AxiosCsrfTokenService.js +60 -0
  52. package/runtime/auth/AxiosJwtAuthService.js +363 -0
  53. package/runtime/auth/AxiosJwtTokenService.js +134 -0
  54. package/runtime/auth/LocalForageCache.js +76 -0
  55. package/runtime/auth/MockAuthService.js +278 -0
  56. package/runtime/auth/index.ts +19 -0
  57. package/runtime/auth/interceptors/createCsrfTokenProviderInterceptor.js +36 -0
  58. package/runtime/auth/interceptors/createJwtTokenProviderInterceptor.js +37 -0
  59. package/runtime/auth/interceptors/createProcessAxiosRequestErrorInterceptor.js +20 -0
  60. package/runtime/auth/interceptors/createRetryInterceptor.js +74 -0
  61. package/runtime/auth/interface.js +309 -0
  62. package/runtime/auth/utils.js +105 -0
  63. package/runtime/babel.config.js +3 -0
  64. package/runtime/config/index.ts +295 -0
  65. package/runtime/constants.ts +68 -0
  66. package/runtime/i18n/index.js +118 -0
  67. package/runtime/i18n/injectIntlWithShim.jsx +48 -0
  68. package/runtime/i18n/lib.ts +272 -0
  69. package/runtime/index.ts +134 -0
  70. package/runtime/initialize.js +352 -0
  71. package/runtime/jest.config.js +32 -0
  72. package/runtime/logging/MockLoggingService.js +31 -0
  73. package/runtime/logging/NewRelicLoggingService.js +184 -0
  74. package/runtime/logging/index.ts +9 -0
  75. package/runtime/logging/interface.js +109 -0
  76. package/runtime/logging/types.ts +4 -0
  77. package/runtime/react/AuthenticatedPageRoute.jsx +43 -0
  78. package/runtime/react/CombinedAppProvider.tsx +46 -0
  79. package/runtime/react/CurrentAppContext.tsx +25 -0
  80. package/runtime/react/CurrentAppProvider.tsx +46 -0
  81. package/runtime/react/Divider.tsx +5 -0
  82. package/runtime/react/ErrorBoundary.jsx +47 -0
  83. package/runtime/react/ErrorPage.jsx +72 -0
  84. package/runtime/react/LoginRedirect.jsx +16 -0
  85. package/runtime/react/PageWrap.jsx +24 -0
  86. package/runtime/react/SiteContext.tsx +32 -0
  87. package/runtime/react/SiteProvider.tsx +78 -0
  88. package/runtime/react/hooks.ts +106 -0
  89. package/runtime/react/index.ts +19 -0
  90. package/runtime/routing/index.ts +1 -0
  91. package/runtime/routing/utils.ts +34 -0
  92. package/runtime/scripts/GoogleAnalyticsLoader.ts +59 -0
  93. package/runtime/scripts/index.ts +1 -0
  94. package/runtime/setupTest.js +49 -0
  95. package/runtime/slots/Slot.tsx +32 -0
  96. package/runtime/slots/SlotContext.tsx +8 -0
  97. package/runtime/slots/hooks.ts +35 -0
  98. package/runtime/slots/index.ts +7 -0
  99. package/runtime/slots/layout/DefaultSlotLayout.tsx +9 -0
  100. package/runtime/slots/layout/hooks.ts +65 -0
  101. package/runtime/slots/layout/index.ts +5 -0
  102. package/runtime/slots/layout/types.ts +45 -0
  103. package/runtime/slots/layout/utils.ts +14 -0
  104. package/runtime/slots/types.ts +23 -0
  105. package/runtime/slots/utils.ts +59 -0
  106. package/runtime/slots/widget/WidgetContext.tsx +9 -0
  107. package/runtime/slots/widget/WidgetProvider.tsx +30 -0
  108. package/runtime/slots/widget/hooks.ts +105 -0
  109. package/runtime/slots/widget/iframe/IFrameContentWrapper.messages.tsx +16 -0
  110. package/runtime/slots/widget/iframe/IFrameContentWrapper.tsx +84 -0
  111. package/runtime/slots/widget/iframe/IFrameWidget.tsx +59 -0
  112. package/runtime/slots/widget/iframe/constants.ts +19 -0
  113. package/runtime/slots/widget/iframe/hooks.ts +179 -0
  114. package/runtime/slots/widget/iframe/index.ts +6 -0
  115. package/runtime/slots/widget/iframe/types.ts +7 -0
  116. package/runtime/slots/widget/index.ts +6 -0
  117. package/runtime/slots/widget/types.ts +134 -0
  118. package/runtime/slots/widget/utils.tsx +201 -0
  119. package/runtime/subscriptions.ts +60 -0
  120. package/runtime/testing/index.ts +9 -0
  121. package/runtime/testing/initializeMockApp.ts +81 -0
  122. package/runtime/testing/mockMessages.ts +23 -0
  123. package/runtime/utils.js +178 -0
  124. package/shell/DefaultLayout.tsx +18 -0
  125. package/shell/DefaultMain.tsx +7 -0
  126. package/shell/Logo.tsx +28 -0
  127. package/shell/Shell.messages.ts +61 -0
  128. package/shell/Shell.tsx +18 -0
  129. package/shell/app.scss +149 -0
  130. package/shell/app.ts +24 -0
  131. package/shell/babel.config.js +3 -0
  132. package/shell/dev/devFooter/app.tsx +43 -0
  133. package/shell/dev/devFooter/index.ts +1 -0
  134. package/shell/dev/devHeader/BarContext.tsx +13 -0
  135. package/shell/dev/devHeader/BarLink.tsx +16 -0
  136. package/shell/dev/devHeader/BarProvider.tsx +25 -0
  137. package/shell/dev/devHeader/CoursesLink.tsx +16 -0
  138. package/shell/dev/devHeader/FooContext.tsx +13 -0
  139. package/shell/dev/devHeader/FooLink.tsx +16 -0
  140. package/shell/dev/devHeader/FooProvider.tsx +25 -0
  141. package/shell/dev/devHeader/app.tsx +53 -0
  142. package/shell/dev/devHeader/index.ts +1 -0
  143. package/shell/dev/devHeader/providers.tsx +11 -0
  144. package/shell/dev/devHome/HomePage.tsx +28 -0
  145. package/shell/dev/devHome/app.ts +18 -0
  146. package/shell/dev/devHome/i18n/index.ts +27 -0
  147. package/shell/dev/devHome/index.ts +1 -0
  148. package/shell/dev/devHome/messages.ts +11 -0
  149. package/shell/dev/devUser/app.tsx +24 -0
  150. package/shell/dev/devUser/index.ts +1 -0
  151. package/shell/dev/index.ts +5 -0
  152. package/shell/dev/slotShowcase/HorizontalSlotLayout.tsx +11 -0
  153. package/shell/dev/slotShowcase/LayoutWithOptions.tsx +17 -0
  154. package/shell/dev/slotShowcase/SlotShowcasePage.tsx +66 -0
  155. package/shell/dev/slotShowcase/WidgetWithOptions.tsx +11 -0
  156. package/shell/dev/slotShowcase/app.tsx +373 -0
  157. package/shell/dev/slotShowcase/index.ts +1 -0
  158. package/shell/footer/CenterLinks.tsx +11 -0
  159. package/shell/footer/CopyrightNotice.tsx +36 -0
  160. package/shell/footer/Footer.tsx +34 -0
  161. package/shell/footer/LabeledLinkColumn.tsx +19 -0
  162. package/shell/footer/LanguageMenu.tsx +35 -0
  163. package/shell/footer/LanguageMenuItem.tsx +23 -0
  164. package/shell/footer/LeftLinks.tsx +11 -0
  165. package/shell/footer/LegalNotices.tsx +17 -0
  166. package/shell/footer/PoweredBy.tsx +17 -0
  167. package/shell/footer/RevealLinks.tsx +43 -0
  168. package/shell/footer/RightLinks.tsx +11 -0
  169. package/shell/footer/app.tsx +73 -0
  170. package/shell/footer/data/api.ts +48 -0
  171. package/shell/footer/index.ts +2 -0
  172. package/shell/header/AuthenticatedMenu.tsx +32 -0
  173. package/shell/header/Header.tsx +17 -0
  174. package/shell/header/anonymous-menu/AnonymousMenu.tsx +14 -0
  175. package/shell/header/anonymous-menu/LoginButton.tsx +14 -0
  176. package/shell/header/anonymous-menu/RegisterButton.tsx +15 -0
  177. package/shell/header/app.tsx +142 -0
  178. package/shell/header/desktop/DesktopLayout.tsx +22 -0
  179. package/shell/header/desktop/PrimaryNavLinks.tsx +10 -0
  180. package/shell/header/desktop/SecondaryNavLinks.tsx +10 -0
  181. package/shell/header/index.ts +2 -0
  182. package/shell/header/mobile/MobileLayout.tsx +47 -0
  183. package/shell/header/mobile/MobileNavLinks.tsx +10 -0
  184. package/shell/i18n/index.ts +25 -0
  185. package/shell/index.ts +7 -0
  186. package/shell/jest.config.js +30 -0
  187. package/shell/menus/LinkMenuItem.tsx +64 -0
  188. package/shell/menus/NavDropdownMenuSlot.tsx +29 -0
  189. package/shell/menus/ProfileLinkMenuItem.tsx +33 -0
  190. package/shell/menus/data/utils.ts +19 -0
  191. package/shell/public/index.html +10 -0
  192. package/shell/router/createRouter.ts +17 -0
  193. package/shell/router/getAppRoutes.ts +21 -0
  194. package/shell/setupTest.js +48 -0
  195. package/shell/site.config.dev.tsx +49 -0
  196. package/shell/site.tsx +41 -0
  197. package/test-site/app.d.ts +15 -0
  198. package/test-site/dist/176.436443549ebb858db483.js +2 -0
  199. package/test-site/dist/176.436443549ebb858db483.js.map +1 -0
  200. package/test-site/dist/362.536eff787d2380fe246c.js +2 -0
  201. package/test-site/dist/362.536eff787d2380fe246c.js.map +1 -0
  202. package/test-site/dist/653.486966b108d224551296.js +2 -0
  203. package/test-site/dist/653.486966b108d224551296.js.map +1 -0
  204. package/test-site/dist/74e025d3fe9a7b7f8503054e2563b353.jpg +0 -0
  205. package/test-site/dist/806.323cf6496ad0a7fe73a7.js +3 -0
  206. package/test-site/dist/806.323cf6496ad0a7fe73a7.js.LICENSE.txt +106 -0
  207. package/test-site/dist/806.323cf6496ad0a7fe73a7.js.map +1 -0
  208. package/test-site/dist/95ec738c0b7faac5b5c9126794446bbd.svg +4 -0
  209. package/test-site/dist/app.612058b36c74787759ac.css +61 -0
  210. package/test-site/dist/app.612058b36c74787759ac.css.map +1 -0
  211. package/test-site/dist/app.612058b36c74787759ac.js +2 -0
  212. package/test-site/dist/app.612058b36c74787759ac.js.map +1 -0
  213. package/test-site/dist/cb28cdb1468c915e27e5cec9af64f22f.svg +1 -0
  214. package/test-site/dist/index.html +1 -0
  215. package/test-site/dist/report.html +39 -0
  216. package/test-site/dist/runtime.c7aeaf7b967496cb076f.js +2 -0
  217. package/test-site/dist/runtime.c7aeaf7b967496cb076f.js.map +1 -0
  218. package/test-site/eslint.config.js +12 -0
  219. package/test-site/package-lock.json +19226 -0
  220. package/test-site/package.json +29 -0
  221. package/test-site/public/index.html +10 -0
  222. package/test-site/site.config.build.tsx +27 -0
  223. package/test-site/site.config.dev.tsx +27 -0
  224. package/test-site/src/authenticated-page/AuthenticatedPage.tsx +18 -0
  225. package/test-site/src/authenticated-page/i18n/index.ts +27 -0
  226. package/test-site/src/authenticated-page/index.tsx +28 -0
  227. package/test-site/src/example-page/ExamplePage.tsx +79 -0
  228. package/test-site/src/example-page/Image.tsx +11 -0
  229. package/test-site/src/example-page/ParagonPreview.jsx +66 -0
  230. package/test-site/src/example-page/apple.jpg +0 -0
  231. package/test-site/src/example-page/apple.svg +1 -0
  232. package/test-site/src/example-page/index.ts +16 -0
  233. package/test-site/src/i18n/README.md +3 -0
  234. package/test-site/src/i18n/messages/frontend-app-sample/ar.json +4 -0
  235. package/test-site/src/i18n/messages/frontend-app-sample/eo.json +1 -0
  236. package/test-site/src/i18n/messages/frontend-app-sample/es_419.json +4 -0
  237. package/test-site/src/i18n/messages/frontend-component-emptylangs/ar.json +1 -0
  238. package/test-site/src/i18n/messages/frontend-component-singlelang/ar.json +3 -0
  239. package/test-site/src/iframe-widget/IframeWidget.tsx +14 -0
  240. package/test-site/src/iframe-widget/index.ts +16 -0
  241. package/test-site/src/index.tsx +3 -0
  242. package/test-site/src/messages.js +11 -0
  243. package/test-site/src/site.scss +11 -0
  244. package/test-site/tsconfig.json +14 -0
  245. package/tools/babel/babel.config.js +27 -0
  246. package/tools/babel.config.js +3 -0
  247. package/tools/cli/README.md +29 -0
  248. package/tools/cli/commands/pack.ts +9 -0
  249. package/tools/cli/commands/release.ts +27 -0
  250. package/tools/cli/commands/serve.ts +43 -0
  251. package/tools/cli/intl-imports.ts +274 -0
  252. package/tools/cli/openedx.ts +101 -0
  253. package/tools/cli/transifex-utils.ts +75 -0
  254. package/tools/cli/utils/ensureConfigFilenameOption.ts +40 -0
  255. package/tools/cli/utils/formatter.ts +10 -0
  256. package/tools/cli/utils/getResolvedConfigPath.ts +23 -0
  257. package/tools/cli/utils/prettyPrintTitle.ts +15 -0
  258. package/tools/cli/utils/printUsage.ts +53 -0
  259. package/tools/config-helpers/createConfig.ts +8 -0
  260. package/tools/config-helpers/createLintConfig.ts +14 -0
  261. package/tools/config-helpers/getBaseConfig.ts +11 -0
  262. package/tools/defaultConfigPaths.ts +30 -0
  263. package/tools/dist/babel/babel.config.js +28 -0
  264. package/tools/dist/cli/commands/pack.js +14 -0
  265. package/tools/dist/cli/commands/release.js +28 -0
  266. package/tools/dist/cli/commands/serve.js +44 -0
  267. package/tools/dist/cli/intl-imports.js +233 -0
  268. package/tools/dist/cli/openedx.js +100 -0
  269. package/tools/dist/cli/transifex-utils.js +68 -0
  270. package/tools/dist/cli/utils/ensureConfigFilenameOption.js +42 -0
  271. package/tools/dist/cli/utils/formatter.js +10 -0
  272. package/tools/dist/cli/utils/getResolvedConfigPath.js +28 -0
  273. package/tools/dist/cli/utils/prettyPrintTitle.js +17 -0
  274. package/tools/dist/cli/utils/printUsage.js +48 -0
  275. package/tools/dist/config-helpers/createConfig.js +13 -0
  276. package/tools/dist/config-helpers/createLintConfig.js +16 -0
  277. package/tools/dist/config-helpers/getBaseConfig.js +12 -0
  278. package/tools/dist/defaultConfigPaths.js +35 -0
  279. package/tools/dist/eslint/base.eslint.config.js +113 -0
  280. package/tools/dist/eslint.config.js +11 -0
  281. package/tools/dist/index.js +12 -0
  282. package/tools/dist/jest/jest.config.js +30 -0
  283. package/tools/dist/jest.config.js +20 -0
  284. package/tools/dist/types.js +25 -0
  285. package/tools/dist/typescript/tsconfig.json +32 -0
  286. package/tools/dist/webpack/common-config/all/getCodeRules.js +52 -0
  287. package/tools/dist/webpack/common-config/all/getFileLoaderRules.js +26 -0
  288. package/tools/dist/webpack/common-config/all/getIgnoreWarnings.js +14 -0
  289. package/tools/dist/webpack/common-config/all/getImageMinimizer.js +25 -0
  290. package/tools/dist/webpack/common-config/all/getStylesheetRule.js +112 -0
  291. package/tools/dist/webpack/common-config/dev/getDevServer.js +38 -0
  292. package/tools/dist/webpack/common-config/index.js +18 -0
  293. package/tools/dist/webpack/common-config/site/getHtmlWebpackPlugin.js +16 -0
  294. package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.js +91 -0
  295. package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/index.js +7 -0
  296. package/tools/dist/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +3 -0
  297. package/tools/dist/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.js +108 -0
  298. package/tools/dist/webpack/plugins/paragon-webpack-plugin/index.js +7 -0
  299. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.js +64 -0
  300. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.js +53 -0
  301. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/index.js +9 -0
  302. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.js +114 -0
  303. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.js +146 -0
  304. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.js +126 -0
  305. package/tools/dist/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.js +57 -0
  306. package/tools/dist/webpack/types.js +2 -0
  307. package/tools/dist/webpack/utils/getLocalAliases.js +65 -0
  308. package/tools/dist/webpack/utils/getPublicPath.js +6 -0
  309. package/tools/dist/webpack/utils/getResolvedSiteConfigPath.js +32 -0
  310. package/tools/dist/webpack/utils/paragonUtils.js +138 -0
  311. package/tools/dist/webpack/webpack.config.build.js +80 -0
  312. package/tools/dist/webpack/webpack.config.dev.js +76 -0
  313. package/tools/dist/webpack/webpack.config.dev.shell.js +110 -0
  314. package/tools/eslint/base.eslint.config.js +124 -0
  315. package/tools/eslint/modules.d.ts +5 -0
  316. package/tools/eslint.config.js +15 -0
  317. package/tools/index.ts +3 -0
  318. package/tools/jest/jest.config.js +30 -0
  319. package/tools/jest.config.js +19 -0
  320. package/tools/tsconfig.json +24 -0
  321. package/tools/types.ts +21 -0
  322. package/tools/typescript/tsconfig.json +32 -0
  323. package/tools/webpack/common-config/README.md +15 -0
  324. package/tools/webpack/common-config/all/getCodeRules.ts +51 -0
  325. package/tools/webpack/common-config/all/getFileLoaderRules.ts +23 -0
  326. package/tools/webpack/common-config/all/getIgnoreWarnings.ts +13 -0
  327. package/tools/webpack/common-config/all/getImageMinimizer.ts +26 -0
  328. package/tools/webpack/common-config/all/getStylesheetRule.ts +111 -0
  329. package/tools/webpack/common-config/dev/getDevServer.ts +35 -0
  330. package/tools/webpack/common-config/index.ts +6 -0
  331. package/tools/webpack/common-config/site/getHtmlWebpackPlugin.ts +11 -0
  332. package/tools/webpack/modules.d.ts +6 -0
  333. package/tools/webpack/plugins/html-webpack-new-relic-plugin/HtmlWebpackNewRelicPlugin.ts +102 -0
  334. package/tools/webpack/plugins/html-webpack-new-relic-plugin/LICENSE +21 -0
  335. package/tools/webpack/plugins/html-webpack-new-relic-plugin/README.md +7 -0
  336. package/tools/webpack/plugins/html-webpack-new-relic-plugin/index.js +3 -0
  337. package/tools/webpack/plugins/html-webpack-new-relic-plugin/test/fixtures/entry.js +1 -0
  338. package/tools/webpack/plugins/paragon-webpack-plugin/ParagonWebpackPlugin.ts +134 -0
  339. package/tools/webpack/plugins/paragon-webpack-plugin/index.ts +3 -0
  340. package/tools/webpack/plugins/paragon-webpack-plugin/utils/assetUtils.ts +71 -0
  341. package/tools/webpack/plugins/paragon-webpack-plugin/utils/htmlUtils.ts +72 -0
  342. package/tools/webpack/plugins/paragon-webpack-plugin/utils/index.ts +6 -0
  343. package/tools/webpack/plugins/paragon-webpack-plugin/utils/paragonStylesheetUtils.ts +131 -0
  344. package/tools/webpack/plugins/paragon-webpack-plugin/utils/scriptUtils.ts +144 -0
  345. package/tools/webpack/plugins/paragon-webpack-plugin/utils/stylesheetUtils.ts +106 -0
  346. package/tools/webpack/plugins/paragon-webpack-plugin/utils/tagUtils.ts +54 -0
  347. package/tools/webpack/types.ts +69 -0
  348. package/tools/webpack/utils/getLocalAliases.ts +65 -0
  349. package/tools/webpack/utils/getPublicPath.ts +3 -0
  350. package/tools/webpack/utils/getResolvedSiteConfigPath.ts +28 -0
  351. package/tools/webpack/utils/paragonUtils.ts +152 -0
  352. package/tools/webpack/webpack.config.build.ts +93 -0
  353. package/tools/webpack/webpack.config.dev.shell.ts +122 -0
  354. package/tools/webpack/webpack.config.dev.ts +90 -0
  355. package/tsconfig.json +23 -0
  356. package/types.ts +99 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * #### Import members from **@openedx/frontend-base**
3
+ *
4
+ * Simplifies the process of making authenticated API requests to backend edX services by providing
5
+ * common authN/authZ client code that enables the login/logout flow and handles ensuring the
6
+ * presence of a valid [JWT cookie](https://github.com/openedx/edx-platform/blob/master/openedx/core/djangoapps/oauth_dispatch/docs/decisions/0009-jwt-in-session-cookie.rst).
7
+ *
8
+ * The `initialize` function performs much of the auth configuration for you. If, however, you're
9
+ * not using the `initialize` function, an authenticated API client can be created via:
10
+ *
11
+ * ```
12
+ * import {
13
+ * configureAuth,
14
+ * fetchAuthenticatedUser,
15
+ * getAuthenticatedHttpClient,
16
+ * getSiteConfig,
17
+ * getLoggingService
18
+ * } from '@openedx/frontend-base';
19
+ *
20
+ * configureAuth({
21
+ * loggingService: getLoggingService(),
22
+ * config: getSiteConfig(),
23
+ * });
24
+ *
25
+ * const authenticatedUser = await fetchAuthenticatedUser(); // validates and decodes JWT token
26
+ * const authenticatedHttpClient = getAuthenticatedHttpClient();
27
+ * const response = await getAuthenticatedHttpClient().get(`https://example.com/api/user/data/${authenticatedUser.username}`); // fetching from an authenticated API using user data
28
+ * ```
29
+ *
30
+ * As shown in this example, auth depends on the configuration document and logging.
31
+ *
32
+ * NOTE: The documentation for AxiosJwtAuthService is nearly the same as that for the top-level
33
+ * auth interface, except that it contains some Axios-specific details.
34
+ *
35
+ * @module Auth
36
+ */
37
+ import PropTypes from 'prop-types';
38
+ import { publish } from '../subscriptions';
39
+
40
+ /**
41
+ * @constant
42
+ * @private
43
+ */
44
+ export const AUTHENTICATED_USER_TOPIC = 'AUTHENTICATED_USER';
45
+
46
+ /**
47
+ * Published when the authenticated user data changes. This can happen when the authentication
48
+ * service determines that the user is authenticated or anonymous, as well as when we fetch
49
+ * additional user account data if the `hydrateAuthenticatedUser` flag has been set in the
50
+ * `initialize` function.
51
+ *
52
+ * @event
53
+ * @see {@link module:Initialization~initialize}
54
+ */
55
+ export const AUTHENTICATED_USER_CHANGED = `${AUTHENTICATED_USER_TOPIC}.CHANGED`;
56
+
57
+ const optionsShape = {
58
+ config: PropTypes.shape({
59
+ baseUrl: PropTypes.string.isRequired,
60
+ lmsBaseUrl: PropTypes.string.isRequired,
61
+ loginUrl: PropTypes.string.isRequired,
62
+ logoutUrl: PropTypes.string.isRequired,
63
+ refreshAccessTokenApiPath: PropTypes.string.isRequired,
64
+ accessTokenCookieName: PropTypes.string.isRequired,
65
+ csrfTokenApiPath: PropTypes.string.isRequired,
66
+ }).isRequired,
67
+ loggingService: PropTypes.shape({
68
+ logError: PropTypes.func.isRequired,
69
+ logInfo: PropTypes.func.isRequired,
70
+ }).isRequired,
71
+ };
72
+
73
+ const serviceShape = {
74
+ getAuthenticatedHttpClient: PropTypes.func.isRequired,
75
+ getHttpClient: PropTypes.func.isRequired,
76
+ getLoginRedirectUrl: PropTypes.func.isRequired,
77
+ redirectToLogin: PropTypes.func.isRequired,
78
+ getLogoutRedirectUrl: PropTypes.func.isRequired,
79
+ redirectToLogout: PropTypes.func.isRequired,
80
+ getAuthenticatedUser: PropTypes.func.isRequired,
81
+ setAuthenticatedUser: PropTypes.func.isRequired,
82
+ fetchAuthenticatedUser: PropTypes.func.isRequired,
83
+ ensureAuthenticatedUser: PropTypes.func.isRequired,
84
+ hydrateAuthenticatedUser: PropTypes.func.isRequired,
85
+ };
86
+
87
+ let service;
88
+
89
+ /**
90
+ *
91
+ * @param {class} AuthService
92
+ * @param {*} options
93
+ * @returns {AuthService}
94
+ */
95
+ export function configureAuth(AuthService, options) {
96
+ PropTypes.checkPropTypes(optionsShape, options, 'property', 'Auth');
97
+ service = new AuthService(options);
98
+ PropTypes.checkPropTypes(serviceShape, service, 'property', 'AuthService');
99
+ return service;
100
+ }
101
+
102
+ /**
103
+ *
104
+ *
105
+ * @returns {AuthService}
106
+ */
107
+ export function getAuthService() {
108
+ if (!service) {
109
+ throw Error('You must first configure the auth service.');
110
+ }
111
+
112
+ return service;
113
+ }
114
+
115
+ /**
116
+ *
117
+ */
118
+ export function resetAuthService() {
119
+ service = null;
120
+ }
121
+
122
+ /**
123
+ * Gets the authenticated HTTP client for the service.
124
+ *
125
+ * @param {Object} [options] Optional options for how to configure the authenticated HTTP client
126
+ * @param {boolean} [options.useCache] Whether to use front end caching for all requests made with the returned client
127
+ *
128
+ * @returns {HttpClient}
129
+ */
130
+ export function getAuthenticatedHttpClient(options = {}) {
131
+ return service.getAuthenticatedHttpClient(options);
132
+ }
133
+
134
+ /**
135
+ * Gets the unauthenticated HTTP client for the service.
136
+ *
137
+ * @param {Object} [options] Optional options for how to configure the authenticated HTTP client
138
+ * @param {boolean} [options.useCache] Whether to use front end caching for all requests made with the returned client
139
+ *
140
+ * @returns {HttpClient}
141
+ */
142
+ export function getHttpClient(options = {}) {
143
+ return service.getHttpClient(options);
144
+ }
145
+
146
+ /**
147
+ * Builds a URL to the login page with a post-login redirect URL attached as a query parameter.
148
+ *
149
+ * ```
150
+ * const url = getLoginRedirectUrl('http://localhost/mypage');
151
+ * console.log(url); // http://localhost/login?next=http%3A%2F%2Flocalhost%2Fmypage
152
+ * ```
153
+ *
154
+ * @param {string} redirectUrl The URL the user should be redirected to after logging in.
155
+ */
156
+ export function getLoginRedirectUrl(redirectUrl) {
157
+ return service.getLoginRedirectUrl(redirectUrl);
158
+ }
159
+
160
+ /**
161
+ * Redirects the user to the login page.
162
+ *
163
+ * @param {string} redirectUrl The URL the user should be redirected to after logging in.
164
+ */
165
+ export function redirectToLogin(redirectUrl) {
166
+ return service.redirectToLogin(redirectUrl);
167
+ }
168
+
169
+ /**
170
+ * Builds a URL to the logout page with a post-logout redirect URL attached as a query parameter.
171
+ *
172
+ * ```
173
+ * const url = getLogoutRedirectUrl('http://localhost/mypage');
174
+ * console.log(url); // http://localhost/logout?redirect_url=http%3A%2F%2Flocalhost%2Fmypage
175
+ * ```
176
+ *
177
+ * @param {string} redirectUrl The URL the user should be redirected to after logging out.
178
+ */
179
+ export function getLogoutRedirectUrl(redirectUrl) {
180
+ return service.getLogoutRedirectUrl(redirectUrl);
181
+ }
182
+
183
+ /**
184
+ * Redirects the user to the logout page.
185
+ *
186
+ * @param {string} redirectUrl The URL the user should be redirected to after logging out.
187
+ */
188
+ export function redirectToLogout(redirectUrl) {
189
+ return service.redirectToLogout(redirectUrl);
190
+ }
191
+
192
+ /**
193
+ * If it exists, returns the user data representing the currently authenticated user. If the
194
+ * user is anonymous, returns null.
195
+ *
196
+ * @returns {UserData|null}
197
+ */
198
+ export function getAuthenticatedUser() {
199
+ return service.getAuthenticatedUser();
200
+ }
201
+
202
+ /**
203
+ * Sets the authenticated user to the provided value.
204
+ *
205
+ * @param {UserData} authUser
206
+ * @emits AUTHENTICATED_USER_CHANGED
207
+ */
208
+ export function setAuthenticatedUser(authUser) {
209
+ service.setAuthenticatedUser(authUser);
210
+ publish(AUTHENTICATED_USER_CHANGED);
211
+ }
212
+
213
+ /**
214
+ * Reads the authenticated user's access token. Resolves to null if the user is
215
+ * unauthenticated.
216
+ *
217
+ * @returns {Promise<UserData>|Promise<null>} Resolves to the user's access token if they are
218
+ * logged in.
219
+ */
220
+ export async function fetchAuthenticatedUser(options = {}) {
221
+ return service.fetchAuthenticatedUser(options);
222
+ }
223
+
224
+ /**
225
+ * Ensures a user is authenticated. It will redirect to login when not
226
+ * authenticated.
227
+ *
228
+ * @param {string} [redirectUrl=config.baseUrl] to return user after login when not
229
+ * authenticated.
230
+ * @returns {Promise<UserData>}
231
+ */
232
+ export async function ensureAuthenticatedUser(redirectUrl) {
233
+ return service.ensureAuthenticatedUser(redirectUrl);
234
+ }
235
+
236
+ /**
237
+ * Fetches additional user account information for the authenticated user and merges it into the
238
+ * existing authenticatedUser object, available via getAuthenticatedUser().
239
+ *
240
+ * ```
241
+ * console.log(authenticatedUser); // Will be sparse and only contain basic information.
242
+ * await hydrateAuthenticatedUser()
243
+ * const authenticatedUser = getAuthenticatedUser();
244
+ * console.log(authenticatedUser); // Will contain additional user information
245
+ * ```
246
+ *
247
+ * @emits AUTHENTICATED_USER_CHANGED
248
+ */
249
+ export async function hydrateAuthenticatedUser() {
250
+ await service.hydrateAuthenticatedUser();
251
+ publish(AUTHENTICATED_USER_CHANGED);
252
+ }
253
+
254
+ /**
255
+ * @name AuthService
256
+ * @interface
257
+ * @memberof module:Auth
258
+ * @property {function} getAuthenticatedHttpClient
259
+ * @property {function} getHttpClient
260
+ * @property {function} getLoginRedirectUrl
261
+ * @property {function} redirectToLogin
262
+ * @property {function} getLogoutRedirectUrl
263
+ * @property {function} redirectToLogout
264
+ * @property {function} getAuthenticatedUser
265
+ * @property {function} setAuthenticatedUser
266
+ * @property {function} fetchAuthenticatedUser
267
+ * @property {function} ensureAuthenticatedUser
268
+ * @property {function} hydrateAuthenticatedUser
269
+ */
270
+
271
+ /**
272
+ * A configured axios client. See axios docs for more
273
+ * info https://github.com/axios/axios. All the functions
274
+ * below accept isPublic and isCsrfExempt in the request
275
+ * config options. Setting these to true will prevent this
276
+ * client from attempting to refresh the jwt access token
277
+ * or a csrf token respectively.
278
+ *
279
+ * ```
280
+ * // A public endpoint (no jwt token refresh)
281
+ * apiClient.get('/path/to/endpoint', { isPublic: true });
282
+ * ```
283
+ *
284
+ * ```
285
+ * // A csrf exempt endpoint
286
+ * apiClient.post('/path/to/endpoint', { data }, { isCsrfExempt: true });
287
+ * ```
288
+ *
289
+ * @name HttpClient
290
+ * @interface
291
+ * @memberof module:Auth
292
+ * @property {function} get
293
+ * @property {function} head
294
+ * @property {function} options
295
+ * @property {function} delete (csrf protected)
296
+ * @property {function} post (csrf protected)
297
+ * @property {function} put (csrf protected)
298
+ * @property {function} patch (csrf protected)
299
+ */
300
+
301
+ /**
302
+ * @name UserData
303
+ * @interface
304
+ * @memberof module:Auth
305
+ * @property {string} userId
306
+ * @property {string} username
307
+ * @property {Array} roles
308
+ * @property {boolean} administrator
309
+ */
@@ -0,0 +1,105 @@
1
+ // Lifted from here: https://regexr.com/3ok5o
2
+ const urlRegex = /([a-z]{1,2}tps?):\/\/((?:(?!(?:\/|#|\?|&)).)+)(?:(\/(?:(?:(?:(?!(?:#|\?|&)).)+\/))?))?(?:((?:(?!(?:\.|$|\?|#)).)+))?(?:(\.(?:(?!(?:\?|$|#)).)+))?(?:(\?(?:(?!(?:$|#)).)+))?(?:(#.+))?/;
3
+ const getUrlParts = (url) => {
4
+ const found = url.match(urlRegex);
5
+ try {
6
+ const [
7
+ fullUrl,
8
+ protocol,
9
+ domain,
10
+ path,
11
+ endFilename,
12
+ endFileExtension,
13
+ query,
14
+ hash,
15
+ ] = found;
16
+
17
+ return {
18
+ fullUrl,
19
+ protocol,
20
+ domain,
21
+ path,
22
+ endFilename,
23
+ endFileExtension,
24
+ query,
25
+ hash,
26
+ };
27
+ } catch (e) {
28
+ throw new Error(`Could not find url parts from ${url}.`);
29
+ }
30
+ };
31
+
32
+ const logFrontendAuthError = (loggingService, error) => {
33
+ const prefixedMessageError = Object.create(error);
34
+ prefixedMessageError.message = `[frontend-auth] ${error.message}`;
35
+ loggingService.logError(prefixedMessageError, prefixedMessageError.customAttributes);
36
+ };
37
+
38
+ const processAxiosError = (axiosErrorObject) => {
39
+ const error = Object.create(axiosErrorObject);
40
+ const { request, response, config } = error;
41
+
42
+ if (!config) {
43
+ error.customAttributes = {
44
+ ...error.customAttributes,
45
+ httpErrorType: 'unknown-api-request-error',
46
+ };
47
+ return error;
48
+ }
49
+
50
+ const {
51
+ url: httpErrorRequestUrl,
52
+ method: httpErrorRequestMethod,
53
+ } = config;
54
+ /* istanbul ignore else: difficult to enter the request-only error case in a unit test */
55
+ if (response) {
56
+ const { status, data } = response;
57
+ const stringifiedData = JSON.stringify(data) || '(empty response)';
58
+ const responseIsHTML = stringifiedData.includes('<!DOCTYPE html>');
59
+ // Don't include data if it is just an HTML document, like a 500 error page.
60
+ /* istanbul ignore next */
61
+ const httpErrorResponseData = responseIsHTML ? '<Response is HTML>' : stringifiedData;
62
+ error.customAttributes = {
63
+ ...error.customAttributes,
64
+ httpErrorType: 'api-response-error',
65
+ httpErrorStatus: status,
66
+ httpErrorResponseData,
67
+ httpErrorRequestUrl,
68
+ httpErrorRequestMethod,
69
+ };
70
+ error.message = `Axios Error (Response): ${status} - See custom attributes for details.`;
71
+ } else if (request) {
72
+ error.customAttributes = {
73
+ ...error.customAttributes,
74
+ httpErrorType: 'api-request-error',
75
+ httpErrorMessage: error.message,
76
+ httpErrorRequestUrl,
77
+ httpErrorRequestMethod,
78
+ };
79
+ // This case occurs most likely because of intermittent internet connection issues
80
+ // but it also, though less often, catches CORS or server configuration problems.
81
+ error.message = 'Axios Error (Request): (Possible local connectivity issue.) See custom attributes for details.';
82
+ } else {
83
+ error.customAttributes = {
84
+ ...error.customAttributes,
85
+ httpErrorType: 'api-request-config-error',
86
+ httpErrorMessage: error.message,
87
+ httpErrorRequestUrl,
88
+ httpErrorRequestMethod,
89
+ };
90
+ error.message = 'Axios Error (Config): See custom attributes for details.';
91
+ }
92
+
93
+ return error;
94
+ };
95
+
96
+ const processAxiosErrorAndThrow = (axiosErrorObject) => {
97
+ throw processAxiosError(axiosErrorObject);
98
+ };
99
+
100
+ export {
101
+ getUrlParts,
102
+ logFrontendAuthError,
103
+ processAxiosError,
104
+ processAxiosErrorAndThrow
105
+ };
@@ -0,0 +1,3 @@
1
+ const config = require('../tools/babel/babel.config');
2
+
3
+ module.exports = config;
@@ -0,0 +1,295 @@
1
+ /**
2
+ * #### Import members from **@edx/frontend-base**
3
+ *
4
+ * The configuration module provides utilities for working with an application's configuration
5
+ * document (SiteConfig). Configuration variables can be supplied to the
6
+ * application in three different ways. They are applied in the following order:
7
+ *
8
+ * - Site Configuration File (site.config.tsx)
9
+ * - Initialization Config Handler
10
+ * - Runtime Configuration
11
+ *
12
+ * Last one in wins, and are deep merged together. Variables with the same name defined via the
13
+ * later methods will override any defined using an earlier method. i.e., if a variable is defined
14
+ * in Runtime Configuration, that will override the same variable defined in either of the earlier
15
+ * methods. Configuration defined in a JS file will override any default values below.
16
+ *
17
+ * ##### Site Configuration File
18
+ *
19
+ * Configuration variables can be supplied in a file named site.config.tsx. This file must
20
+ * export either an Object containing configuration variables or a function. The function must
21
+ * return an Object containing configuration variables or, alternately, a promise which resolves to
22
+ * an Object.
23
+ *
24
+ * Using a function or async function allows the configuration to be resolved at runtime (because
25
+ * the function will be executed at runtime). This is not common, and the capability is included
26
+ * for the sake of flexibility.
27
+ *
28
+ * The Site Configuration File is well-suited to extensibility use cases or component overrides,
29
+ * in that the configuration file can depend on any installed JavaScript module. It is also the
30
+ * preferred way of doing build-time configuration if runtime configuration isn't used by your
31
+ * deployment of the platform.
32
+ *
33
+ * Exporting a config object:
34
+ * ```
35
+ * const siteConfig = {
36
+ * lmsBaseUrl: 'http://localhost:18000'
37
+ * };
38
+ *
39
+ * export default siteConfig;
40
+ * ```
41
+ *
42
+ * Exporting a function that returns an object:
43
+ * ```
44
+ * function getSiteConfig() {
45
+ * return {
46
+ * lmsBaseUrl: 'http://localhost:18000'
47
+ * };
48
+ * }
49
+ * ```
50
+ *
51
+ * Exporting a function that returns a promise that resolves to an object:
52
+ * ```
53
+ * function getAsyncSiteConfig() {
54
+ * return new Promise((resolve, reject) => {
55
+ * resolve({
56
+ * lmsBaseUrl: 'http://localhost:18000'
57
+ * });
58
+ * });
59
+ * }
60
+ *
61
+ * export default getAsyncSiteConfig;
62
+ * ```
63
+ *
64
+ * ##### Initialization Config Handler
65
+ *
66
+ * The configuration document can be extended by
67
+ * applications at run-time using a `config` initialization handler. Please see the Initialization
68
+ * documentation for more information on handlers and initialization phases.
69
+ *
70
+ * ```
71
+ * initialize({
72
+ * handlers: {
73
+ * config: () => {
74
+ * mergeSiteConfig({
75
+ * CUSTOM_VARIABLE: 'custom value',
76
+ * lmsBaseUrl: 'http://localhost:18001' // You can override variables, but this is uncommon.
77
+ * }, 'App config override handler');
78
+ * },
79
+ * },
80
+ * });
81
+ * ```
82
+ *
83
+ * ##### Runtime Configuration
84
+ *
85
+ * Configuration variables can also be supplied using the "runtime configuration" method, taking
86
+ * advantage of the Micro-frontend Config API in edx-platform. More information on this API can be
87
+ * found in the ADR which introduced it:
88
+ *
89
+ * https://github.com/openedx/edx-platform/blob/master/lms/djangoapps/mfe_config_api/docs/decisions/0001-mfe-config-api.rst
90
+ *
91
+ * The runtime configuration method can be enabled by supplying a mfeConfigApiUrl via one of the other
92
+ * two configuration methods above.
93
+ *
94
+ * Runtime configuration is particularly useful if you need to supply different configurations to
95
+ * a single deployment of a micro-frontend, for instance. It is also a perfectly valid alternative
96
+ * to build-time configuration, though it introduces an additional API call to edx-platform on MFE
97
+ * initialization.
98
+ *
99
+ *
100
+ * @module Config
101
+ */
102
+
103
+ import merge from 'lodash.merge';
104
+ import {
105
+ AppConfig,
106
+ EnvironmentTypes,
107
+ SiteConfig
108
+ } from '../../types';
109
+ import { ACTIVE_ROLES_CHANGED, CONFIG_CHANGED } from '../constants';
110
+ import { publish } from '../subscriptions';
111
+
112
+ let siteConfig: SiteConfig = {
113
+ // Required
114
+ siteId: '',
115
+ baseUrl: '',
116
+ siteName: '',
117
+ loginUrl: '',
118
+ logoutUrl: '',
119
+ lmsBaseUrl: '',
120
+
121
+ // Optional
122
+ environment: EnvironmentTypes.PRODUCTION,
123
+ apps: [],
124
+ externalRoutes: [],
125
+ externalLinkUrlOverrides: [],
126
+ mfeConfigApiUrl: null,
127
+ accessTokenCookieName: 'edx-jwt-cookie-header-payload',
128
+ csrfTokenApiPath: '/csrf/api/v1/token',
129
+ ignoredErrorRegex: null,
130
+ languagePreferenceCookieName: 'openedx-language-preference',
131
+ refreshAccessTokenApiPath: '/login_refresh',
132
+ userInfoCookieName: 'edx-user-info',
133
+ segmentKey: null,
134
+ };
135
+
136
+ /**
137
+ * Getter for the application configuration document. This is synchronous and merely returns a
138
+ * reference to an existing object, and is thus safe to call as often as desired.
139
+ *
140
+ * Example:
141
+ *
142
+ * ```
143
+ * import { getSiteConfig } from '@openedx/frontend-base';
144
+ *
145
+ * const {
146
+ * lmsBaseUrl,
147
+ * } = getSiteConfig();
148
+ * ```
149
+ *
150
+ * @returns {SiteConfig}
151
+ */
152
+ export function getSiteConfig() {
153
+ return siteConfig;
154
+ }
155
+
156
+ /**
157
+ * Replaces the existing SiteConfig. This is not commonly used, but can be helpful for tests.
158
+ *
159
+ * Example:
160
+ *
161
+ * ```
162
+ * import { setSiteConfig } from '@openedx/frontend-base';
163
+ *
164
+ * setSiteConfig({
165
+ * lmsBaseUrl, // This is overriding the ENTIRE document - this is not merged in!
166
+ * });
167
+ * ```
168
+ *
169
+ * @param newConfig A replacement SiteConfig which will completely override the current SiteConfig.
170
+ */
171
+ export function setSiteConfig(newSiteConfig: SiteConfig) {
172
+ siteConfig = newSiteConfig;
173
+ publish(CONFIG_CHANGED);
174
+ }
175
+
176
+ /**
177
+ * Merges additional configuration values into the site config returned by `getSiteConfig`. Will
178
+ * override any values that exist with the same keys.
179
+ *
180
+ * ```
181
+ * mergeSiteConfig({
182
+ * NEW_KEY: 'new value',
183
+ * OTHER_NEW_KEY: 'other new value',
184
+ * });
185
+ *
186
+ * This function uses lodash.merge internally to merge configuration objects
187
+ * which means they will be merged recursively. See https://lodash.com/docs/latest#merge for
188
+ * documentation on the exact behavior.
189
+ *
190
+ * @param {Object} newSiteConfig
191
+ */
192
+ export function mergeSiteConfig(newSiteConfig: Partial<SiteConfig>) {
193
+ siteConfig = merge(siteConfig, newSiteConfig);
194
+ publish(CONFIG_CHANGED);
195
+ }
196
+
197
+ const appConfigs: Record<string, AppConfig> = {};
198
+
199
+ /**
200
+ * addAppConfigs finds any AppConfig objects in the apps in SiteConfig and makes their config
201
+ * available to be used by Apps via getAppConfig(appId) or useAppConfig() functions. This is
202
+ * used at initialization time to process any AppConfigs bundled with the site.
203
+ */
204
+ export function addAppConfigs() {
205
+ const { apps } = getSiteConfig();
206
+ if (!apps) return;
207
+
208
+ for (const app of apps) {
209
+ const { appId, config } = app;
210
+ if (config !== undefined) {
211
+ appConfigs[appId] = config;
212
+ }
213
+ }
214
+
215
+ publish(CONFIG_CHANGED);
216
+ }
217
+
218
+ export function getAppConfig(id: string) {
219
+ return appConfigs[id];
220
+ }
221
+
222
+ export function mergeAppConfig(id: string, newAppConfig: AppConfig) {
223
+ appConfigs[id] = merge(appConfigs[id], newAppConfig);
224
+ publish(CONFIG_CHANGED);
225
+ }
226
+
227
+ let activeRouteRoles: string[] = [];
228
+
229
+ export function setActiveRouteRoles(roles: string[]) {
230
+ activeRouteRoles = roles;
231
+ publish(ACTIVE_ROLES_CHANGED);
232
+ }
233
+
234
+ export function getActiveRouteRoles() {
235
+ return activeRouteRoles;
236
+ }
237
+
238
+ const activeWidgetRoles: Record<string, number> = {};
239
+
240
+ export function addActiveWidgetRole(role: string) {
241
+ if (activeWidgetRoles[role] === undefined) {
242
+ activeWidgetRoles[role] = 0;
243
+ }
244
+ activeWidgetRoles[role] += 1;
245
+ publish(ACTIVE_ROLES_CHANGED);
246
+ }
247
+
248
+ export function removeActiveWidgetRole(role: string) {
249
+ if (activeWidgetRoles[role] !== undefined) {
250
+ activeWidgetRoles[role] -= 1;
251
+ }
252
+ if (activeWidgetRoles[role] < 1) {
253
+ delete activeWidgetRoles[role];
254
+ }
255
+ publish(ACTIVE_ROLES_CHANGED);
256
+ }
257
+
258
+ export function getActiveWidgetRoles() {
259
+ return Object.entries(activeWidgetRoles)
260
+ .filter(([, count]: [role: string, count: number]) => count !== undefined && count > 0)
261
+ .map(([role]: [role: string, count: number]) => role);
262
+ }
263
+
264
+ // Gets all active roles from the route roles and widget roles.
265
+ export function getActiveRoles() {
266
+ return [...getActiveRouteRoles(), ...getActiveWidgetRoles()];
267
+ }
268
+
269
+ /**
270
+ * Get an external link URL based on the URL provided. If the passed in URL is overridden in the
271
+ * `externalLinkUrlOverrides` object, it will return the overridden URL. Otherwise, it will return
272
+ * the provided URL.
273
+ *
274
+ *
275
+ * @param {string} url - The default URL.
276
+ * @returns {string} - The external link URL. Defaults to the input URL if not found in the
277
+ * `externalLinkUrlOverrides` object. If the input URL is invalid, '#' is returned.
278
+ *
279
+ * @example
280
+ * import { getExternalLinkUrl } from '@openedx/frontend-base';
281
+ *
282
+ * <Hyperlink
283
+ * destination={getExternalLinkUrl(data.helpLink)}
284
+ * target="_blank"
285
+ * >
286
+ */
287
+ export function getExternalLinkUrl(url: string): string {
288
+ // Guard against whitespace-only strings
289
+ if (typeof url !== 'string' || !url.trim()) {
290
+ return '#';
291
+ }
292
+
293
+ const overriddenLinkUrls = getSiteConfig().externalLinkUrlOverrides ?? {};
294
+ return overriddenLinkUrls[url] ?? url;
295
+ }