Skip to content

Commit 9d42e01

Browse files
[Next.js][Multi-site] Dynamic site resolver (#1271)
* [Next.js][Multi-site] Dynamic site resolver * add changes * Add unit tests, parse pattern * etc * simplify reg exp
1 parent 946a6c5 commit 9d42e01

File tree

3 files changed

+220
-0
lines changed

3 files changed

+220
-0
lines changed

packages/sitecore-jss/src/site/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ export {
2424
GraphQLErrorPagesService,
2525
GraphQLErrorPagesServiceConfig,
2626
} from './graphql-error-pages-service';
27+
28+
export { SiteResolver, HostInfo } from './site-resolver';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
import { expect } from 'chai';
2+
import { HostInfo, SiteResolver } from './site-resolver';
3+
4+
describe.only('SiteResolver', () => {
5+
const hostInfo: HostInfo = {
6+
hostName: 'foo.com',
7+
};
8+
9+
const hostInfoWithLanguage: HostInfo = {
10+
...hostInfo,
11+
language: 'en',
12+
};
13+
14+
describe('resolve', () => {
15+
describe('fallback site name', () => {
16+
it('should return default when sites info is empty', () => {
17+
const siteName = SiteResolver.resolve(hostInfo, []);
18+
19+
expect(siteName).to.equal('website');
20+
});
21+
22+
it('should return default when there is no appropriate site info', () => {
23+
const siteName = SiteResolver.resolve(hostInfo, [
24+
{ hostName: 'bar.com', language: '', name: 'bar' },
25+
{ hostName: 'var.com', language: '', name: 'var' },
26+
]);
27+
28+
expect(siteName).to.equal('website');
29+
});
30+
31+
it('should return custom when sites info is empty', () => {
32+
const siteName = SiteResolver.resolve(hostInfo, [], 'sample');
33+
34+
expect(siteName).to.equal('sample');
35+
});
36+
37+
it('should return custom when there is no appropriate site info', () => {
38+
const siteName = SiteResolver.resolve(
39+
hostInfo,
40+
[
41+
{ hostName: 'bar.com', language: '', name: 'bar' },
42+
{ hostName: 'var.com', language: '', name: 'var' },
43+
],
44+
'sample'
45+
);
46+
47+
expect(siteName).to.equal('sample');
48+
});
49+
});
50+
51+
it('should return site name when only hostname is provided', () => {
52+
const siteName = SiteResolver.resolve(hostInfo, [
53+
{ hostName: 'bar.net', language: '', name: 'bar' },
54+
{ hostName: 'foo.com', language: '', name: 'foo' },
55+
{ hostName: 'var.com', language: '', name: 'var' },
56+
]);
57+
58+
expect(siteName).to.equal('foo');
59+
});
60+
61+
it('should return site name when hostname includes wildcard', () => {
62+
const siteName = SiteResolver.resolve(hostInfo, [
63+
{ hostName: 'var.com', language: 'da-DK', name: 'var' },
64+
{ hostName: 'bar.net', language: 'en', name: 'bar' },
65+
{ hostName: '*.com', language: '', name: 'foo' },
66+
{ hostName: 'foo.com', language: 'en', name: 'foo-en' },
67+
]);
68+
69+
expect(siteName).to.equal('foo');
70+
});
71+
72+
it('should return site name when wildcard is provided', () => {
73+
const siteName = SiteResolver.resolve(hostInfo, [
74+
{ hostName: 'bar.net', language: '', name: 'bar' },
75+
{ hostName: '*', language: '', name: 'wildcard' },
76+
{ hostName: 'foo.com', language: '', name: 'foo' },
77+
]);
78+
79+
expect(siteName).to.equal('wildcard');
80+
});
81+
82+
it('should return site name when language is provided', () => {
83+
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
84+
{ hostName: 'foo.com', language: 'ca', name: 'foo-ca' },
85+
{ hostName: 'var.com', language: 'da-DK', name: 'var' },
86+
{ hostName: 'bar.net', language: 'en', name: 'bar' },
87+
{ hostName: 'foo.com', language: 'en', name: 'foo-en' },
88+
]);
89+
90+
expect(siteName).to.equal('foo-en');
91+
});
92+
93+
it('should return site name when language is omit', () => {
94+
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
95+
{ hostName: 'var.com', language: 'da-DK', name: 'var' },
96+
{ hostName: 'bar.net', language: 'en', name: 'bar' },
97+
{ hostName: 'foo.com', language: '', name: 'foo' },
98+
{ hostName: 'foo.com', language: 'en', name: 'foo-en' },
99+
]);
100+
101+
expect(siteName).to.equal('foo');
102+
});
103+
104+
describe('should return site name when multi-value hostnames are provided', () => {
105+
it('hostnames include wildcard characters', () => {
106+
expect(
107+
SiteResolver.resolve({ hostName: 'test.foo.bar.com' }, [
108+
{ hostName: '*.bat.com|foo.bar.com', language: '', name: 'bar' },
109+
{ hostName: 'test.com|*.foo.*.com|foo.com', language: '', name: 'foo' },
110+
])
111+
).to.equal('foo');
112+
113+
expect(
114+
SiteResolver.resolve({ hostName: 'xfoo.bar.com.en' }, [
115+
{ hostName: 'foo.bar.com', language: '', name: 'bar' },
116+
{ hostName: 'test.com|*foo.*.com*|foo.com', language: '', name: 'foo' },
117+
])
118+
).to.equal('foo');
119+
});
120+
121+
it('hostname contains whitespaces', () => {
122+
const siteName = SiteResolver.resolve(hostInfo, [
123+
{ hostName: 'bar.net', language: '', name: 'bar' },
124+
{ hostName: 'test.com; foo.net | foo.com', language: '', name: 'foo' },
125+
{ hostName: 'var.com', language: '', name: 'var' },
126+
]);
127+
128+
expect(siteName).to.equal('foo');
129+
});
130+
131+
it('comma delimiter is used', () => {
132+
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
133+
{ hostName: 'bar.net', language: '', name: 'bar' },
134+
{ hostName: 'test.com,foo.net,foo.com', language: 'en', name: 'foo' },
135+
{ hostName: 'var.com', language: '', name: 'var' },
136+
]);
137+
138+
expect(siteName).to.equal('foo');
139+
});
140+
141+
it('semicolon delimiter is used', () => {
142+
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
143+
{ hostName: 'bar.net', language: '', name: 'bar' },
144+
{ hostName: 'test.com;foo.net;foo.com', language: 'en', name: 'foo' },
145+
{ hostName: 'var.com', language: '', name: 'var' },
146+
]);
147+
148+
expect(siteName).to.equal('foo');
149+
});
150+
151+
it('pipe delimiter is used', () => {
152+
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
153+
{ hostName: 'bar.net', language: '', name: 'bar' },
154+
{ hostName: 'test.com|foo.net|foo.com', language: 'en', name: 'foo' },
155+
{ hostName: 'var.com', language: '', name: 'var' },
156+
]);
157+
158+
expect(siteName).to.equal('foo');
159+
});
160+
});
161+
});
162+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { SiteInfo } from './graphql-siteinfo-service';
2+
3+
/**
4+
* Information about the current host
5+
*/
6+
export type HostInfo = {
7+
hostName: string;
8+
language?: string;
9+
};
10+
11+
// Delimiters for multi-value hostnames
12+
const DELIMITERS = /\||,|;/g;
13+
14+
/**
15+
* Determines site name based on the provided host information
16+
*/
17+
export class SiteResolver {
18+
/**
19+
* Resolve siteName by host information
20+
* @param {HostInfo} hostInfo information about current host
21+
* @param {SiteInfo[]} sitesInfo list of available sites
22+
* @param {string} [fallbackSiteName] siteName to be returned in case siteName is not found
23+
* @returns {string} siteName resolved site name
24+
*/
25+
static resolve = (
26+
hostInfo: HostInfo,
27+
sitesInfo: SiteInfo[],
28+
fallbackSiteName = 'website'
29+
): string => {
30+
const siteInfo = sitesInfo.find((info) => {
31+
const hostnames = info.hostName.replace(/\s/g, '').split(DELIMITERS);
32+
33+
const languageMatches =
34+
info.language === '' || !hostInfo.language || hostInfo.language === info.language;
35+
36+
return hostnames.some(
37+
(hostname) =>
38+
languageMatches &&
39+
(hostInfo.hostName === hostname ||
40+
SiteResolver.matchesPattern(hostInfo.hostName, hostname))
41+
);
42+
});
43+
44+
return siteInfo?.name || fallbackSiteName;
45+
};
46+
47+
private static matchesPattern(hostname: string, pattern: string): boolean {
48+
// dots should be treated as chars
49+
// stars should be treated as wildcards
50+
const regExpPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');
51+
52+
const regExp = new RegExp(`^${regExpPattern}$`, 'g');
53+
54+
return !!hostname.match(regExp);
55+
}
56+
}

0 commit comments

Comments
 (0)