-
Notifications
You must be signed in to change notification settings - Fork 212
Expand file tree
/
Copy pathsite-utils.js
More file actions
253 lines (227 loc) · 8.46 KB
/
site-utils.js
File metadata and controls
253 lines (227 loc) · 8.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
/*
* Copyright (c) 2021, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import {getConfig} from '@salesforce/pwa-kit-runtime/utils/ssr-config'
import {getRouterBasePath} from '@salesforce/pwa-kit-react-sdk/ssr/universal/utils'
/**
* This functions takes an url and returns a site object,
* an error will be thrown if no url is passed in or no site is found
* @param {string} url
* @returns {object} site - a site object
*/
export const resolveSiteFromUrl = (url) => {
if (!url) {
throw new Error('URL is required to find a site object.')
}
const {pathname, search} = getPathnameAndSearch(url)
const path = `${pathname}${search}`
let site
// get the site identifier from the url
const {siteRef} = getParamsFromPath(path)
const sites = getSites()
// step 1: use the siteRef to look for the site from the sites in the app config
// since alias is optional, make sure it is defined before the equality check
site = sites.find((site) => site.id === siteRef || (site.alias && site.alias === siteRef))
if (site) {
return site
}
//Step 2: if step 1 does not work, use the defaultSite value to get the default site
site = getDefaultSite()
// Step 3: throw an error if site can't be found by any of the above steps
if (!site) {
throw new Error(
"Can't find a matching default site. Please check your sites configuration."
)
}
return site
}
/**
* Returns the default site based on the defaultSite value from the app config
* @returns {object} site - a site object from app config
*/
export const getDefaultSite = () => {
const {app} = getConfig()
const sites = getSites()
if (sites.length === 1) {
return sites[0]
}
return sites.find((site) => site.id === app.defaultSite)
}
/**
* Return the list of sites that has included their respective aliases
* @return {array} sites - list of sites including their aliases
*/
export const getSites = () => {
const {sites = [], siteAliases = {}} = getConfig().app || {}
if (!sites.length) {
throw new Error("Can't find any sites from the config. Please check your configuration")
}
return sites.map((site) => {
const alias = siteAliases[site.id]
return {
...site,
...(alias ? {alias} : {})
}
})
}
/**
* Given a site reference, return the site object
* @param siteRef - site reference to look for the site object
* @returns {object | undefined} found site object or default site object
*/
export const getSiteByReference = (siteRef) => {
const defaultSite = getDefaultSite()
const sites = getSites()
return (
sites.find((site) => {
return site.alias === siteRef || site.id === siteRef
}) || defaultSite
)
}
/**
* Remove the base path from a path string only when path equals basePath or path starts with basePath + '/'.
* @param {string} path - the path to strip
* @param {string} basePath - the base path to remove
* @returns {string} the path with base path removed, or the original path
*/
export const removeBasePathFromPath = (path, basePath) => {
if (!basePath) return path
if (path.startsWith(basePath + '/') || path === basePath) {
return path.substring(basePath.length) || '/'
}
return path
}
/**
* This function return the identifiers (site and locale) from the given url
* The site will always go before locale if both of them are presented in the pathname
* @param path {string}
* @returns {{siteRef: string, localeRef: string}} - site and locale reference (it could either be id or alias)
*/
export const getParamsFromPath = (path) => {
let {pathname, search} = getPathnameAndSearch(path)
// Remove the base path from the pathname if present since
// it shifts the location of the site and locale in the pathname
const basePath = getRouterBasePath()
pathname = removeBasePathFromPath(pathname, basePath)
const config = getConfig()
const {pathMatcher, searchMatcherForSite, searchMatcherForLocale} = getConfigMatcher(config)
const pathMatch = pathname.match(pathMatcher)
const searchMatchForSite = search.match(searchMatcherForSite)
const searchMatchForLocale = search.match(searchMatcherForLocale)
// the value can only either in the path or search query param, there will be no overridden
const siteRef = pathMatch?.groups.site || searchMatchForSite?.groups.site
const localeRef = pathMatch?.groups.locale || searchMatchForLocale?.groups.locale
return {siteRef, localeRef}
}
/**
* This function returns the url config from the current configuration
* @return {object} - url config
*/
export const getUrlConfig = () => {
const {app} = getConfig()
if (!app.url) {
throw new Error('Cannot find `url` key. Please check your configuration file.')
}
return app.url
}
/**
* Given your application's configuration this function returns a set of regular expressions used to match the site
* and locale references from an url.
* @param config
* @return {{searchMatcherForSite: RegExp, searchMatcherForLocale: RegExp, pathMatcher: RegExp}}
*/
export const getConfigMatcher = (config) => {
if (!config) {
throw new Error('Config is not defined.')
}
const allSites = getSites()
const siteIds = []
const siteAliases = []
const localesIds = []
const localeAliases = []
allSites.forEach((site) => {
siteAliases.push(site.alias)
siteIds.push(site.id)
const {l10n} = site
l10n.supportedLocales.forEach((locale) => {
localesIds.push(locale.id)
localeAliases.push(locale.alias)
})
})
const sites = [...siteIds, ...siteAliases].filter(Boolean)
const locales = [...localesIds, ...localeAliases].filter(Boolean)
// prettier-ignore
const searchPatternForSite = `site=(?<site>${sites.join('|')})`
// prettier-ignore
// eslint-disable-next-line
const pathPattern = `(?:\/(?<site>${sites.join('|')}))?(?:\/(?<locale>${locales.join("|")}))?(?!\\w)`
// prettier-ignore
const searchPatternForLocale = `locale=(?<locale>${locales.join('|')})`
const pathMatcher = new RegExp(pathPattern)
const searchMatcherForSite = new RegExp(searchPatternForSite)
const searchMatcherForLocale = new RegExp(searchPatternForLocale)
return {
pathMatcher,
searchMatcherForSite,
searchMatcherForLocale
}
}
/**
* Given a site and a locale reference, return the locale object
* @param site - site to look for the locale
* @param localeRef - the locale ref to look for in site supported locales
* @return {object|undefined}
*/
export const getLocaleByReference = (site, localeRef) => {
if (!site) {
throw new Error('Site is not defined. It is required to look for locale object')
}
return site.l10n.supportedLocales.find(
(locale) => locale.id === localeRef || locale.alias === localeRef
)
}
/**
* Determine the locale object from an url
* If the localeRef is not found from the url, set it to default locale of the current site
* and use it to find the locale object
*
* @param url
* @return {Object} locale object
*/
export const resolveLocaleFromUrl = (url) => {
if (!url) {
throw new Error('URL is required to look for the locale object')
}
let {localeRef} = getParamsFromPath(url)
const site = resolveSiteFromUrl(url)
const {supportedLocales} = site.l10n
// if no localeRef is found, use the default value of the current site
if (!localeRef) {
localeRef = site.l10n.defaultLocale
}
const locale = supportedLocales.find(
(locale) => locale.alias === localeRef || locale.id === localeRef
)
if (locale) {
return locale
}
// if locale is not defined, use default locale as fallback value
const defaultLocale = site.l10n.defaultLocale
return supportedLocales.find(
(locale) => locale.alias === defaultLocale || locale.id === defaultLocale
)
}
/**
* Extract pathname and search params from a given url
* @private
* @param url
* @returns {{search: (string|string), pathname: string}}
*/
function getPathnameAndSearch(url) {
// since url is a partial url, we pass in a dummy domain to create a validate url to pass into URL constructor
const {pathname, search} = new URL(url, 'https://www.some-domain.com')
return {pathname, search}
}