Skip to content

Commit

Permalink
[Next.js][Multi-site] Dynamic site resolver (#1271)
Browse files Browse the repository at this point in the history
* [Next.js][Multi-site] Dynamic site resolver

* add changes

* Add unit tests, parse pattern

* etc

* simplify reg exp
  • Loading branch information
illiakovalenko authored Jan 3, 2023
1 parent 946a6c5 commit 9d42e01
Show file tree
Hide file tree
Showing 3 changed files with 220 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/sitecore-jss/src/site/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ export {
GraphQLErrorPagesService,
GraphQLErrorPagesServiceConfig,
} from './graphql-error-pages-service';

export { SiteResolver, HostInfo } from './site-resolver';
162 changes: 162 additions & 0 deletions packages/sitecore-jss/src/site/site-resolver.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import { expect } from 'chai';
import { HostInfo, SiteResolver } from './site-resolver';

describe.only('SiteResolver', () => {
const hostInfo: HostInfo = {
hostName: 'foo.com',
};

const hostInfoWithLanguage: HostInfo = {
...hostInfo,
language: 'en',
};

describe('resolve', () => {
describe('fallback site name', () => {
it('should return default when sites info is empty', () => {
const siteName = SiteResolver.resolve(hostInfo, []);

expect(siteName).to.equal('website');
});

it('should return default when there is no appropriate site info', () => {
const siteName = SiteResolver.resolve(hostInfo, [
{ hostName: 'bar.com', language: '', name: 'bar' },
{ hostName: 'var.com', language: '', name: 'var' },
]);

expect(siteName).to.equal('website');
});

it('should return custom when sites info is empty', () => {
const siteName = SiteResolver.resolve(hostInfo, [], 'sample');

expect(siteName).to.equal('sample');
});

it('should return custom when there is no appropriate site info', () => {
const siteName = SiteResolver.resolve(
hostInfo,
[
{ hostName: 'bar.com', language: '', name: 'bar' },
{ hostName: 'var.com', language: '', name: 'var' },
],
'sample'
);

expect(siteName).to.equal('sample');
});
});

it('should return site name when only hostname is provided', () => {
const siteName = SiteResolver.resolve(hostInfo, [
{ hostName: 'bar.net', language: '', name: 'bar' },
{ hostName: 'foo.com', language: '', name: 'foo' },
{ hostName: 'var.com', language: '', name: 'var' },
]);

expect(siteName).to.equal('foo');
});

it('should return site name when hostname includes wildcard', () => {
const siteName = SiteResolver.resolve(hostInfo, [
{ hostName: 'var.com', language: 'da-DK', name: 'var' },
{ hostName: 'bar.net', language: 'en', name: 'bar' },
{ hostName: '*.com', language: '', name: 'foo' },
{ hostName: 'foo.com', language: 'en', name: 'foo-en' },
]);

expect(siteName).to.equal('foo');
});

it('should return site name when wildcard is provided', () => {
const siteName = SiteResolver.resolve(hostInfo, [
{ hostName: 'bar.net', language: '', name: 'bar' },
{ hostName: '*', language: '', name: 'wildcard' },
{ hostName: 'foo.com', language: '', name: 'foo' },
]);

expect(siteName).to.equal('wildcard');
});

it('should return site name when language is provided', () => {
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
{ hostName: 'foo.com', language: 'ca', name: 'foo-ca' },
{ hostName: 'var.com', language: 'da-DK', name: 'var' },
{ hostName: 'bar.net', language: 'en', name: 'bar' },
{ hostName: 'foo.com', language: 'en', name: 'foo-en' },
]);

expect(siteName).to.equal('foo-en');
});

it('should return site name when language is omit', () => {
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
{ hostName: 'var.com', language: 'da-DK', name: 'var' },
{ hostName: 'bar.net', language: 'en', name: 'bar' },
{ hostName: 'foo.com', language: '', name: 'foo' },
{ hostName: 'foo.com', language: 'en', name: 'foo-en' },
]);

expect(siteName).to.equal('foo');
});

describe('should return site name when multi-value hostnames are provided', () => {
it('hostnames include wildcard characters', () => {
expect(
SiteResolver.resolve({ hostName: 'test.foo.bar.com' }, [
{ hostName: '*.bat.com|foo.bar.com', language: '', name: 'bar' },
{ hostName: 'test.com|*.foo.*.com|foo.com', language: '', name: 'foo' },
])
).to.equal('foo');

expect(
SiteResolver.resolve({ hostName: 'xfoo.bar.com.en' }, [
{ hostName: 'foo.bar.com', language: '', name: 'bar' },
{ hostName: 'test.com|*foo.*.com*|foo.com', language: '', name: 'foo' },
])
).to.equal('foo');
});

it('hostname contains whitespaces', () => {
const siteName = SiteResolver.resolve(hostInfo, [
{ hostName: 'bar.net', language: '', name: 'bar' },
{ hostName: 'test.com; foo.net | foo.com', language: '', name: 'foo' },
{ hostName: 'var.com', language: '', name: 'var' },
]);

expect(siteName).to.equal('foo');
});

it('comma delimiter is used', () => {
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
{ hostName: 'bar.net', language: '', name: 'bar' },
{ hostName: 'test.com,foo.net,foo.com', language: 'en', name: 'foo' },
{ hostName: 'var.com', language: '', name: 'var' },
]);

expect(siteName).to.equal('foo');
});

it('semicolon delimiter is used', () => {
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
{ hostName: 'bar.net', language: '', name: 'bar' },
{ hostName: 'test.com;foo.net;foo.com', language: 'en', name: 'foo' },
{ hostName: 'var.com', language: '', name: 'var' },
]);

expect(siteName).to.equal('foo');
});

it('pipe delimiter is used', () => {
const siteName = SiteResolver.resolve(hostInfoWithLanguage, [
{ hostName: 'bar.net', language: '', name: 'bar' },
{ hostName: 'test.com|foo.net|foo.com', language: 'en', name: 'foo' },
{ hostName: 'var.com', language: '', name: 'var' },
]);

expect(siteName).to.equal('foo');
});
});
});
});
56 changes: 56 additions & 0 deletions packages/sitecore-jss/src/site/site-resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { SiteInfo } from './graphql-siteinfo-service';

/**
* Information about the current host
*/
export type HostInfo = {
hostName: string;
language?: string;
};

// Delimiters for multi-value hostnames
const DELIMITERS = /\||,|;/g;

/**
* Determines site name based on the provided host information
*/
export class SiteResolver {
/**
* Resolve siteName by host information
* @param {HostInfo} hostInfo information about current host
* @param {SiteInfo[]} sitesInfo list of available sites
* @param {string} [fallbackSiteName] siteName to be returned in case siteName is not found
* @returns {string} siteName resolved site name
*/
static resolve = (
hostInfo: HostInfo,
sitesInfo: SiteInfo[],
fallbackSiteName = 'website'
): string => {
const siteInfo = sitesInfo.find((info) => {
const hostnames = info.hostName.replace(/\s/g, '').split(DELIMITERS);

const languageMatches =
info.language === '' || !hostInfo.language || hostInfo.language === info.language;

return hostnames.some(
(hostname) =>
languageMatches &&
(hostInfo.hostName === hostname ||
SiteResolver.matchesPattern(hostInfo.hostName, hostname))
);
});

return siteInfo?.name || fallbackSiteName;
};

private static matchesPattern(hostname: string, pattern: string): boolean {
// dots should be treated as chars
// stars should be treated as wildcards
const regExpPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*');

const regExp = new RegExp(`^${regExpPattern}$`, 'g');

return !!hostname.match(regExp);
}
}

0 comments on commit 9d42e01

Please sign in to comment.