@salesforcedevs/arch-components 1.20.17-alpha2 → 1.20.17-alpha4
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.
- package/package.json +1 -1
- package/src/modules/common/context/context.ts +14 -14
- package/src/modules/common/effectAdapter/__tests__/effectAdapter.test.ts +4 -4
- package/src/modules/common/effectAdapter/effectAdapter.ts +13 -7
- package/src/modules/common/reflectedElement/__tests__/reflectedElement.test.ts +5 -5
- package/src/modules/common/reflectedElement/reflectedElement.ts +1 -1
- package/src/modules/common/slot/__tests__/slot.test.ts +12 -12
- package/src/modules/common/slot/slot.ts +4 -3
- package/src/modules/sa/coverage/coverage.ts +14 -18
- package/src/modules/sa/coverage/types.d.ts +12 -12
- package/src/modules/sa/explorer/explorer.ts +25 -25
- package/src/modules/sa/gallery/gallery.ts +11 -11
- package/src/modules/shared/a11y/a11y.ts +5 -5
- package/src/modules/shared/color/color.ts +13 -13
- package/src/modules/shared/fetch/fetch.ts +6 -6
- package/src/modules/shared/helpers/helpers.ts +6 -6
- package/src/modules/shared/i18n/i18n.ts +1 -1
- package/src/modules/shared/labels/__tests__/helpers.test.ts +3 -3
- package/src/modules/shared/labels/__tests__/pointHelpers.test.ts +3 -3
- package/src/modules/shared/labels/__tests__/timeHelpers.test.ts +8 -8
- package/src/modules/shared/labels/helpers.ts +4 -4
- package/src/modules/shared/labels/pointHelpers.ts +2 -2
- package/src/modules/shared/labels/timeHelpers.ts +3 -3
- package/src/modules/shared/menu/menu.ts +22 -22
- package/src/modules/shared/polling-request.ts +2 -2
- package/src/modules/shared/testutils.ts +4 -4
- package/src/modules/shared/useEffectAttr.ts +5 -5
- package/src/modules/tds/appLauncher/__tests__/appLauncher.test.ts +8 -8
- package/src/modules/tds/appLauncher/appLauncher.ts +6 -6
- package/src/modules/tds/avatar/__tests__/avatar.test.ts +1 -1
- package/src/modules/tds/button/__tests__/button.test.ts +5 -5
- package/src/modules/tds/buttonIcon/__tests__/buttonIcon.test.ts +1 -1
- package/src/modules/tds/buttonLink/__tests__/buttonLink.test.ts +1 -1
- package/src/modules/tds/childSummary/childSummary.ts +2 -2
- package/src/modules/tds/contentChildListItem/__tests__/contentChildListItem.test.ts +2 -2
- package/src/modules/tds/contentIcon/contentIcon.ts +1 -1
- package/src/modules/tds/footerLinks/__tests__/footerLinks.test.ts +2 -2
- package/src/modules/tds/headerAvatar/__tests__/headerAvatar.test.ts +4 -4
- package/src/modules/tds/headerAvatar/headerAvatar.ts +5 -5
- package/src/modules/tds/headerHelpButton/__tests__/headerHelpButton.test.ts +2 -2
- package/src/modules/tds/heading/__tests__/heading.test.ts +7 -7
- package/src/modules/tds/icon/icon.ts +11 -11
- package/src/modules/tds/radio/__tests__/radio.test.ts +1 -1
- package/src/modules/tds/search/__tests__/search.test.ts +17 -17
- package/src/modules/tds/search/lib/__tests__/listbox.test.ts +16 -16
- package/src/modules/tds/search/lib/listbox.ts +16 -16
- package/src/modules/tds/search/search.ts +10 -10
- package/src/modules/tds/select/__tests__/select.test.ts +1 -1
- package/src/modules/tds/summary/__tests__/summary.test.ts +16 -16
- package/src/modules/tds/summary/summary.ts +2 -2
- package/src/modules/tds/themeProvider/themeProvider.ts +4 -4
- package/src/modules/th/favoriteButton/__tests__/favoriteButton.test.ts +1 -1
- package/src/modules/th/favoriteButton/mocks/index.ts +2 -2
- package/src/modules/th/search/__tests__/search.test.ts +2 -2
- package/src/modules/th/search/mocks/index.ts +1 -1
- package/src/modules/th/search/search.ts +21 -21
- package/src/modules/tm/card/card.ts +15 -15
- package/src/modules/tm/content/content.ts +11 -11
- package/src/modules/tm/endCapA/__tests__/endCapA.test.ts +2 -2
- package/src/modules/tm/eventsA/eventsA.ts +1 -1
- package/src/modules/tm/faqA/faqA.ts +4 -4
- package/src/modules/tm/featureGridA/__tests__/featureGridA.test.ts +2 -2
- package/src/modules/tm/footnote/footnote.ts +1 -1
- package/src/modules/tm/heroA/__tests__/heroA.test.ts +2 -2
- package/src/modules/tm/heroA/heroA.ts +3 -3
- package/src/modules/tm/promoA/__tests__/promoA.test.ts +3 -3
- package/src/modules/tm/promoA/promoA.ts +4 -4
- package/src/modules/tm/sectionA/sectionA.ts +1 -1
- package/src/modules/tm/utils/utils.ts +5 -5
- package/src/modules/tm/youtube/youtube.ts +2 -2
- package/src/modules/ui/focusTrap/focusTrap.ts +7 -7
- package/src/modules/ui/focusVisible/focusVisible.ts +7 -7
- package/src/modules/th/tbid/__tests__/__snapshots__/tbid.test.ts.snap +0 -3
- package/src/modules/th/tbid/__tests__/tbid.test.ts +0 -242
- package/src/modules/th/tbid/tbid.html +0 -1
- package/src/modules/th/tbid/tbid.stories.mdx +0 -25
- package/src/modules/th/tbid/tbid.ts +0 -215
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export function waitForPageSettled() {
|
|
2
2
|
return new Promise<void>((resolve) => {
|
|
3
3
|
let prevH: number = 0;
|
|
4
|
-
|
|
4
|
+
const prevY = window.scrollY;
|
|
5
5
|
function check() {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
const nextH = window.innerHeight + document.body.scrollHeight;
|
|
7
|
+
const nextY = window.scrollY;
|
|
8
|
+
const settled = nextH === prevH;
|
|
9
9
|
if (settled) {
|
|
10
|
-
if (nextY === prevY) resolve();
|
|
10
|
+
if (nextY === prevY) {resolve();}
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
prevH = nextH;
|
|
@@ -17,8 +17,8 @@ export default class extends LightningElement {
|
|
|
17
17
|
if (!this.didSetSize) {
|
|
18
18
|
this.didSetSize = true;
|
|
19
19
|
waitForPageSettled().then(() => {
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const w = this.template.host.offsetWidth;
|
|
21
|
+
const ratio = 0.5625;
|
|
22
22
|
this.height = w * ratio;
|
|
23
23
|
this.width = w;
|
|
24
24
|
});
|
|
@@ -26,8 +26,8 @@ export default class extends LightningElement {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
renderedCallback() {
|
|
29
|
-
if (this.init) return;
|
|
30
|
-
|
|
29
|
+
if (this.init) {return;}
|
|
30
|
+
this.init = true;
|
|
31
31
|
|
|
32
32
|
this.$start = this.template.querySelector(
|
|
33
33
|
`[data-id="start"]`
|
|
@@ -52,7 +52,7 @@ export default class extends LightningElement {
|
|
|
52
52
|
this.removeEventListener('focusin', this.onFocusIn);
|
|
53
53
|
this.removeEventListener('focusout', this.onFocusOut);
|
|
54
54
|
if (this.$activeElement && this.$activeElement instanceof HTMLElement)
|
|
55
|
-
this.$activeElement.focus();
|
|
55
|
+
{this.$activeElement.focus();}
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
private focusFirstElement = () => {
|
|
@@ -79,14 +79,14 @@ export default class extends LightningElement {
|
|
|
79
79
|
|
|
80
80
|
private getFocusableElements(): HTMLElement[] {
|
|
81
81
|
// @ts-ignore
|
|
82
|
-
|
|
82
|
+
const children = this.template.querySelector('slot').assignedElements();
|
|
83
83
|
// @ts-ignore
|
|
84
84
|
return queryShadowRoot({ children }, isHidden, isFocusable);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
private trapFocus(trapToEnd?: boolean) {
|
|
88
|
-
if (this.inactive) return;
|
|
89
|
-
|
|
88
|
+
if (this.inactive) {return;}
|
|
89
|
+
const focusableElements = this.getFocusableElements();
|
|
90
90
|
if (focusableElements.length > 0) {
|
|
91
91
|
if (trapToEnd) {
|
|
92
92
|
focusableElements[focusableElements.length - 1].focus();
|
|
@@ -99,6 +99,6 @@ export default class extends LightningElement {
|
|
|
99
99
|
|
|
100
100
|
function getActiveElement(element: Element | null): Element | null {
|
|
101
101
|
if (element && element.shadowRoot)
|
|
102
|
-
return getActiveElement(element.shadowRoot.activeElement);
|
|
102
|
+
{return getActiveElement(element.shadowRoot.activeElement);}
|
|
103
103
|
return element;
|
|
104
104
|
}
|
|
@@ -23,7 +23,7 @@ export default class extends LightningElement {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
private onChange = () => {
|
|
26
|
-
for (
|
|
26
|
+
for (const node of Array.from(this.template.host.children)) {
|
|
27
27
|
node.setAttribute('data-focus-visible', String(focusVisible));
|
|
28
28
|
}
|
|
29
29
|
};
|
|
@@ -34,13 +34,13 @@ document.addEventListener('mousemove', onMouse, { passive: true });
|
|
|
34
34
|
document.addEventListener('keydown', onKeyboard, { passive: true });
|
|
35
35
|
|
|
36
36
|
function onMouse(event: MouseEvent) {
|
|
37
|
-
if (!focusVisible) return;
|
|
38
|
-
|
|
39
|
-
for (
|
|
37
|
+
if (!focusVisible) {return;}
|
|
38
|
+
focusVisible = false;
|
|
39
|
+
for (const handler of handlers) {handler();}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function onKeyboard(event: KeyboardEvent) {
|
|
43
|
-
if (focusVisible) return;
|
|
44
|
-
|
|
45
|
-
for (
|
|
43
|
+
if (focusVisible) {return;}
|
|
44
|
+
focusVisible = true;
|
|
45
|
+
for (const handler of handlers) {handler();}
|
|
46
46
|
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`undefined: toMatchHTMLSnapshot 1`] = `"<head><meta name="salesforce-cache-max-age" content="0"><meta name="salesforce-brand" content="trailhead"><meta name="salesforce-community" content="/tbid-community-uri"><meta name="salesforce-use-min-js" content="false"><meta name="salesforce-init-handler" content="__tbid_init"><meta name="salesforce-login-handler" content="__tbid_login"><meta name="salesforce-logout-handler" content="__tbid_logout"><meta name="salesforce-mode" content="authprovider"><meta name="salesforce-redirect-uri" content="/tbid-redirect-uri"><meta name="salesforce-authprovider-login" content="/tbid-auth-provider-login-path"><meta name="salesforce-authprovider-signup" content="/tbid-auth-provider-signup-path"><script src="/tbid-community-uri/resource/authProviderEmbeddedLogin_v1_3"></script></head>"`;
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import fetchMock from 'jest-fetch-mock';
|
|
2
|
-
|
|
3
|
-
import { html, renderIntoBody, nextTick } from 'shared/testutils';
|
|
4
|
-
|
|
5
|
-
import Tbid, {
|
|
6
|
-
EVENT_LOGIN,
|
|
7
|
-
EVENT_LOGOUT,
|
|
8
|
-
TRAILHEAD_EVENT_LOGIN,
|
|
9
|
-
TRAILHEAD_EVENT_LOGOUT,
|
|
10
|
-
TRAILHEAD_EVENT_SIGNUP,
|
|
11
|
-
TBID_EVENT_INIT,
|
|
12
|
-
TBID_EVENT_LOGIN,
|
|
13
|
-
TBID_EVENT_LOGOUT,
|
|
14
|
-
TBID_INIT,
|
|
15
|
-
TBID_LOGIN,
|
|
16
|
-
TBID_LOGOUT,
|
|
17
|
-
TBID_LOGOUT_PATH,
|
|
18
|
-
Lock,
|
|
19
|
-
LOCK_KEY,
|
|
20
|
-
LOCK_TIMEOUT
|
|
21
|
-
} from '../tbid';
|
|
22
|
-
|
|
23
|
-
customElements.define('th-tbid', Tbid.CustomElementConstructor);
|
|
24
|
-
|
|
25
|
-
const render = ({
|
|
26
|
-
authenticated = 'false',
|
|
27
|
-
loginTried = 'false'
|
|
28
|
-
}: { authenticated?: string; loginTried?: string } = {}) =>
|
|
29
|
-
renderIntoBody(
|
|
30
|
-
html`
|
|
31
|
-
<th-tbid
|
|
32
|
-
authenticated=${authenticated}
|
|
33
|
-
csrf-token="token"
|
|
34
|
-
login-redirect-path="/login-redirect-path"
|
|
35
|
-
login-status-path="/login-status-path"
|
|
36
|
-
login-tried=${loginTried}
|
|
37
|
-
logout-redirect-path="/logout-redirect-path"
|
|
38
|
-
tbid-auth-provider-login-path="/tbid-auth-provider-login-path"
|
|
39
|
-
tbid-auth-provider-signup-path="/tbid-auth-provider-signup-path"
|
|
40
|
-
tbid-community-uri="/tbid-community-uri"
|
|
41
|
-
tbid-redirect-uri="/tbid-redirect-uri"
|
|
42
|
-
></th-tbid>
|
|
43
|
-
`
|
|
44
|
-
) as unknown as Tbid;
|
|
45
|
-
|
|
46
|
-
let location = {};
|
|
47
|
-
|
|
48
|
-
Object.defineProperty(window, 'location', {
|
|
49
|
-
get: () => location
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
beforeEach(() => {
|
|
53
|
-
window.location.href = '/';
|
|
54
|
-
window.sessionStorage.removeItem(LOCK_KEY);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('th-tbid', () => {
|
|
58
|
-
afterEach(() => {
|
|
59
|
-
document.head.querySelectorAll('meta').forEach((n) => n.remove());
|
|
60
|
-
});
|
|
61
|
-
it('sets the correct meta tags', () => {
|
|
62
|
-
render();
|
|
63
|
-
expect(document.head.outerHTML).toMatchHTMLSnapshot();
|
|
64
|
-
});
|
|
65
|
-
it('sets the window globals', () => {
|
|
66
|
-
render();
|
|
67
|
-
// @ts-ignore
|
|
68
|
-
expect(window[TBID_INIT]).toBeInstanceOf(Function);
|
|
69
|
-
// @ts-ignore
|
|
70
|
-
expect(window[TBID_LOGIN]).toBeInstanceOf(Function);
|
|
71
|
-
// @ts-ignore
|
|
72
|
-
expect(window[TBID_LOGOUT]).toBeInstanceOf(Function);
|
|
73
|
-
});
|
|
74
|
-
it(`handles the ${TRAILHEAD_EVENT_LOGIN} event`, () => {
|
|
75
|
-
render();
|
|
76
|
-
let fn = jest.fn();
|
|
77
|
-
Object.defineProperty(window, 'SFIDWidget', {
|
|
78
|
-
value: { login: fn },
|
|
79
|
-
writable: true
|
|
80
|
-
});
|
|
81
|
-
document.dispatchEvent(new CustomEvent(TRAILHEAD_EVENT_LOGIN));
|
|
82
|
-
expect(fn).toHaveBeenCalled();
|
|
83
|
-
});
|
|
84
|
-
it(`handles the ${TRAILHEAD_EVENT_LOGOUT} event`, async () => {
|
|
85
|
-
render();
|
|
86
|
-
let fn = jest.fn();
|
|
87
|
-
Object.defineProperty(window, 'SFIDWidget', {
|
|
88
|
-
value: { logout: fn },
|
|
89
|
-
writable: true
|
|
90
|
-
});
|
|
91
|
-
document.dispatchEvent(new CustomEvent(TRAILHEAD_EVENT_LOGOUT));
|
|
92
|
-
let iframe = document.body.querySelector(
|
|
93
|
-
`iframe[src="/tbid-community-uri${TBID_LOGOUT_PATH}"]`
|
|
94
|
-
);
|
|
95
|
-
expect(iframe).toBeInTheDocument();
|
|
96
|
-
iframe!.dispatchEvent(new CustomEvent('load'));
|
|
97
|
-
await nextTick();
|
|
98
|
-
expect(fn).toHaveBeenCalled();
|
|
99
|
-
});
|
|
100
|
-
it(`handles the ${TRAILHEAD_EVENT_SIGNUP}`, () => {
|
|
101
|
-
render();
|
|
102
|
-
let fn = jest.fn();
|
|
103
|
-
Object.defineProperty(window, 'SFIDWidget', {
|
|
104
|
-
value: { signup: fn },
|
|
105
|
-
writable: true
|
|
106
|
-
});
|
|
107
|
-
document.dispatchEvent(new CustomEvent(TRAILHEAD_EVENT_SIGNUP));
|
|
108
|
-
expect(fn).toHaveBeenCalled();
|
|
109
|
-
});
|
|
110
|
-
describe('onLogin', () => {
|
|
111
|
-
it('redirects if user is not_logged_in & login-tried != true', async () => {
|
|
112
|
-
fetchMock.doMockOnceIf(
|
|
113
|
-
(r) => r.url === '/login-status-path',
|
|
114
|
-
JSON.stringify({ user: 'not_logged_in' })
|
|
115
|
-
);
|
|
116
|
-
render();
|
|
117
|
-
// @ts-ignore
|
|
118
|
-
await window[TBID_LOGIN]();
|
|
119
|
-
expect(window.location.href).toEqual('/login-redirect-path');
|
|
120
|
-
});
|
|
121
|
-
it('retries if there is a lock', async () => {
|
|
122
|
-
jest.useFakeTimers();
|
|
123
|
-
fetchMock.doMockIf(
|
|
124
|
-
(r) => r.url === '/login-status-path',
|
|
125
|
-
JSON.stringify({ user: 'not_logged_in' })
|
|
126
|
-
);
|
|
127
|
-
let element = render();
|
|
128
|
-
let onLogin = jest.fn();
|
|
129
|
-
element.addEventListener(TBID_EVENT_LOGIN, onLogin);
|
|
130
|
-
// A: No lock
|
|
131
|
-
// @ts-ignore
|
|
132
|
-
await window[TBID_LOGIN]();
|
|
133
|
-
expect(onLogin).toHaveBeenCalledTimes(1);
|
|
134
|
-
expect(window.location.href).toEqual('/login-redirect-path');
|
|
135
|
-
window.location.href = '/';
|
|
136
|
-
// B: Lock
|
|
137
|
-
// @ts-ignore
|
|
138
|
-
await window[TBID_LOGIN]();
|
|
139
|
-
expect(onLogin).toHaveBeenCalledTimes(2);
|
|
140
|
-
expect(window.location.href).toEqual('/');
|
|
141
|
-
// C: Lock expired
|
|
142
|
-
let now = Date.now();
|
|
143
|
-
let nowMock = jest
|
|
144
|
-
.spyOn(Date, 'now')
|
|
145
|
-
.mockImplementation(() => now + LOCK_TIMEOUT);
|
|
146
|
-
jest.runAllTimers();
|
|
147
|
-
jest.useRealTimers();
|
|
148
|
-
await nextTick();
|
|
149
|
-
expect(onLogin).toHaveBeenCalledTimes(3);
|
|
150
|
-
nowMock.mockRestore();
|
|
151
|
-
fetchMock.resetMocks();
|
|
152
|
-
});
|
|
153
|
-
it('calls onInit if user is logged_in and authenticated', async () => {
|
|
154
|
-
fetchMock.doMockOnceIf(
|
|
155
|
-
(r) => r.url === '/login-status-path',
|
|
156
|
-
JSON.stringify({ user: 'logged_in' })
|
|
157
|
-
);
|
|
158
|
-
let element = render({ authenticated: 'true' });
|
|
159
|
-
let onInit = jest.fn();
|
|
160
|
-
element.addEventListener(TBID_EVENT_INIT, onInit);
|
|
161
|
-
// @ts-ignore
|
|
162
|
-
await window[TBID_LOGIN]();
|
|
163
|
-
expect(onInit).toHaveBeenCalled();
|
|
164
|
-
expect(window.sessionStorage.getItem(LOCK_KEY)).toBeNull();
|
|
165
|
-
});
|
|
166
|
-
it('calls onInit if user is not_logged_in and login-tried', async () => {
|
|
167
|
-
fetchMock.doMockOnceIf(
|
|
168
|
-
(r) => r.url === '/login-status-path',
|
|
169
|
-
JSON.stringify({ user: 'not_logged_in' })
|
|
170
|
-
);
|
|
171
|
-
let element = render({ loginTried: 'true' });
|
|
172
|
-
let onInit = jest.fn();
|
|
173
|
-
element.addEventListener(TBID_EVENT_INIT, onInit);
|
|
174
|
-
// @ts-ignore
|
|
175
|
-
await window[TBID_LOGIN]();
|
|
176
|
-
expect(onInit).toHaveBeenCalled();
|
|
177
|
-
expect(window.sessionStorage.getItem(LOCK_KEY)).toBeNull();
|
|
178
|
-
});
|
|
179
|
-
it('dispatches login', () => {
|
|
180
|
-
fetchMock.doMockOnceIf(
|
|
181
|
-
(r) => r.url === '/login-status-path',
|
|
182
|
-
JSON.stringify({ user: 'not_logged_in' })
|
|
183
|
-
);
|
|
184
|
-
let element = render();
|
|
185
|
-
let fn = jest.fn();
|
|
186
|
-
element.addEventListener(EVENT_LOGIN, fn);
|
|
187
|
-
// @ts-ignore
|
|
188
|
-
window[TBID_LOGIN]();
|
|
189
|
-
expect(fn).toHaveBeenCalled();
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
describe('onLogout', () => {
|
|
193
|
-
it('calls SFIDWidget.init()', () => {
|
|
194
|
-
render();
|
|
195
|
-
let init = jest.fn();
|
|
196
|
-
Object.defineProperty(window, 'SFIDWidget', {
|
|
197
|
-
value: { init },
|
|
198
|
-
writable: true
|
|
199
|
-
});
|
|
200
|
-
// @ts-ignore
|
|
201
|
-
window[TBID_LOGOUT]();
|
|
202
|
-
expect(init).toHaveBeenCalled();
|
|
203
|
-
});
|
|
204
|
-
it('redirects', () => {
|
|
205
|
-
render();
|
|
206
|
-
// @ts-ignore
|
|
207
|
-
window[TBID_LOGOUT]();
|
|
208
|
-
expect(window.location.href).toEqual('/logout-redirect-path');
|
|
209
|
-
});
|
|
210
|
-
it('dispatches logout', () => {
|
|
211
|
-
let element = render();
|
|
212
|
-
let fn = jest.fn();
|
|
213
|
-
element.addEventListener(EVENT_LOGOUT, fn);
|
|
214
|
-
// @ts-ignore
|
|
215
|
-
window[TBID_LOGOUT]();
|
|
216
|
-
expect(fn).toHaveBeenCalled();
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
describe('Lock', () => {
|
|
220
|
-
afterEach(() => {
|
|
221
|
-
window.sessionStorage.removeItem('tbid');
|
|
222
|
-
});
|
|
223
|
-
it('acquire', async () => {
|
|
224
|
-
let now = Date.now();
|
|
225
|
-
let lock = new Lock('tbid', 1000);
|
|
226
|
-
expect(lock.aquire()).toBe(true);
|
|
227
|
-
expect(lock.aquire()).toBe(false);
|
|
228
|
-
let mock = jest
|
|
229
|
-
.spyOn(Date, 'now')
|
|
230
|
-
.mockImplementation(() => now + 2000);
|
|
231
|
-
expect(lock.aquire()).toBe(true);
|
|
232
|
-
mock.mockRestore();
|
|
233
|
-
});
|
|
234
|
-
it('release', async () => {
|
|
235
|
-
let lock = new Lock('tbid', 1000);
|
|
236
|
-
expect(lock.aquire()).toBe(true);
|
|
237
|
-
expect(lock.aquire()).toBe(false);
|
|
238
|
-
lock.release();
|
|
239
|
-
expect(lock.aquire()).toBe(true);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<template></template>
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Meta } from "@storybook/addon-docs/blocks";
|
|
2
|
-
|
|
3
|
-
<Meta title="th/th-tbid" />
|
|
4
|
-
|
|
5
|
-
# Usage
|
|
6
|
-
|
|
7
|
-
```html
|
|
8
|
-
<th-tbid
|
|
9
|
-
authenticated="false"
|
|
10
|
-
csrf-token="token"
|
|
11
|
-
login-redirect-path="/login-redirect-path"
|
|
12
|
-
login-status-path="/login-status-path"
|
|
13
|
-
login-tried="false"
|
|
14
|
-
logout-redirect-path="/logout-path"
|
|
15
|
-
tbid-auth-provider-login-path="/tbid-auth-provider-login-path"
|
|
16
|
-
tbid-auth-provider-signup-path="/tbid-auth-provider-signup-path"
|
|
17
|
-
tbid-community-uri="/tbid-community-uri"
|
|
18
|
-
tbid-redirect-uri="/tbid-redirect-uri"
|
|
19
|
-
></th-tbid>
|
|
20
|
-
|
|
21
|
-
<script
|
|
22
|
-
src="https://trailhead.salesforce.com/ui/modules/th-tbid.js"
|
|
23
|
-
type="module"
|
|
24
|
-
></script>
|
|
25
|
-
```
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import { api, LightningElement } from 'lwc';
|
|
2
|
-
|
|
3
|
-
import { getJSON, csrfHeader } from 'shared/fetch';
|
|
4
|
-
|
|
5
|
-
export const EVENT_LOGIN = 'login';
|
|
6
|
-
export const EVENT_LOGOUT = 'logout';
|
|
7
|
-
|
|
8
|
-
export const TRAILHEAD_EVENT_LOGIN = 'trailhead_login';
|
|
9
|
-
export const TRAILHEAD_EVENT_LOGOUT = 'trailhead_logout';
|
|
10
|
-
export const TRAILHEAD_EVENT_SIGNUP = 'trailhead_signup';
|
|
11
|
-
|
|
12
|
-
export const TBID_EVENT_INIT = 'tbid_will_init';
|
|
13
|
-
export const TBID_EVENT_LOGIN = 'tbid_will_login';
|
|
14
|
-
export const TBID_EVENT_LOGOUT = 'tbid_will_logout';
|
|
15
|
-
|
|
16
|
-
export const TBID_INIT = '__tbid_init';
|
|
17
|
-
export const TBID_LOGIN = '__tbid_login';
|
|
18
|
-
export const TBID_LOGOUT = '__tbid_logout';
|
|
19
|
-
|
|
20
|
-
export const TBID_SCRIPT_PATH = '/resource/authProviderEmbeddedLogin_v1_3';
|
|
21
|
-
export const TBID_LOGOUT_PATH = '/services/auth/rp/oidc/logout';
|
|
22
|
-
|
|
23
|
-
export const LOCK_KEY = 'tbid.tabAuthLock';
|
|
24
|
-
export const LOCK_TIMEOUT = 5000;
|
|
25
|
-
|
|
26
|
-
declare var SFIDWidget: {
|
|
27
|
-
init: () => void;
|
|
28
|
-
login: () => void;
|
|
29
|
-
logout: () => void;
|
|
30
|
-
signup: () => void;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export default class extends LightningElement {
|
|
34
|
-
@api authenticated: string | null = null;
|
|
35
|
-
|
|
36
|
-
@api csrfToken: string = '';
|
|
37
|
-
|
|
38
|
-
@api loginRedirectPath: string = null!;
|
|
39
|
-
@api loginStatusPath: string = null!;
|
|
40
|
-
@api loginTried: string | null = null;
|
|
41
|
-
@api logoutRedirectPath: string = null!;
|
|
42
|
-
|
|
43
|
-
@api tbidAuthProviderLoginPath: string = null!;
|
|
44
|
-
@api tbidAuthProviderSignupPath: string = null!;
|
|
45
|
-
@api tbidCommunityUri: string = null!;
|
|
46
|
-
@api tbidRedirectUri: string = null!;
|
|
47
|
-
|
|
48
|
-
private loadSFID: Promise<unknown> = null!;
|
|
49
|
-
|
|
50
|
-
private get scriptSrc() {
|
|
51
|
-
return `${this.tbidCommunityUri}${TBID_SCRIPT_PATH}`;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
private get logoutIframeSrc() {
|
|
55
|
-
return `${this.tbidCommunityUri}${TBID_LOGOUT_PATH}`;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
connectedCallback() {
|
|
59
|
-
this.appendMetaTag('salesforce-cache-max-age', '0');
|
|
60
|
-
this.appendMetaTag('salesforce-brand', 'trailhead');
|
|
61
|
-
this.appendMetaTag('salesforce-community', this.tbidCommunityUri);
|
|
62
|
-
this.appendMetaTag('salesforce-use-min-js', 'false');
|
|
63
|
-
|
|
64
|
-
this.appendMetaTag('salesforce-init-handler', TBID_INIT);
|
|
65
|
-
this.appendMetaTag('salesforce-login-handler', TBID_LOGIN);
|
|
66
|
-
this.appendMetaTag('salesforce-logout-handler', TBID_LOGOUT);
|
|
67
|
-
|
|
68
|
-
this.appendMetaTag('salesforce-mode', 'authprovider');
|
|
69
|
-
this.appendMetaTag('salesforce-redirect-uri', this.tbidRedirectUri);
|
|
70
|
-
|
|
71
|
-
this.appendMetaTag(
|
|
72
|
-
'salesforce-authprovider-login',
|
|
73
|
-
this.tbidAuthProviderLoginPath
|
|
74
|
-
);
|
|
75
|
-
this.appendMetaTag(
|
|
76
|
-
'salesforce-authprovider-signup',
|
|
77
|
-
this.tbidAuthProviderSignupPath
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
Object.defineProperty(window, TBID_INIT, {
|
|
81
|
-
value: () => this.onInit(),
|
|
82
|
-
writable: process.env.NODE_ENV === 'test'
|
|
83
|
-
});
|
|
84
|
-
Object.defineProperty(window, TBID_LOGIN, {
|
|
85
|
-
value: () => this.onLogin(),
|
|
86
|
-
writable: process.env.NODE_ENV === 'test'
|
|
87
|
-
});
|
|
88
|
-
Object.defineProperty(window, TBID_LOGOUT, {
|
|
89
|
-
value: () => this.onLogout(),
|
|
90
|
-
writable: process.env.NODE_ENV === 'test'
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
this.loadSFID = loadSFID(this.scriptSrc).catch((error) => {
|
|
94
|
-
this.dispatchEvent(
|
|
95
|
-
new CustomEvent('error', {
|
|
96
|
-
detail: { error }
|
|
97
|
-
})
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
document.addEventListener(TRAILHEAD_EVENT_LOGIN, this);
|
|
102
|
-
document.addEventListener(TRAILHEAD_EVENT_LOGOUT, this);
|
|
103
|
-
document.addEventListener(TRAILHEAD_EVENT_SIGNUP, this);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
disconnectedCallback() {
|
|
107
|
-
document.removeEventListener(TRAILHEAD_EVENT_LOGIN, this);
|
|
108
|
-
document.removeEventListener(TRAILHEAD_EVENT_LOGOUT, this);
|
|
109
|
-
document.removeEventListener(TRAILHEAD_EVENT_SIGNUP, this);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
handleEvent(e: Event) {
|
|
113
|
-
switch (e.type) {
|
|
114
|
-
case TRAILHEAD_EVENT_LOGIN:
|
|
115
|
-
SFIDWidget.login();
|
|
116
|
-
break;
|
|
117
|
-
case TRAILHEAD_EVENT_LOGOUT:
|
|
118
|
-
loadIframe(this.logoutIframeSrc).then(() => {
|
|
119
|
-
SFIDWidget.logout();
|
|
120
|
-
});
|
|
121
|
-
break;
|
|
122
|
-
case TRAILHEAD_EVENT_SIGNUP:
|
|
123
|
-
SFIDWidget.signup();
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private appendMetaTag(name: string, content: string) {
|
|
129
|
-
let tag = document.createElement('meta');
|
|
130
|
-
tag.name = name;
|
|
131
|
-
tag.content = content;
|
|
132
|
-
document.head.appendChild(tag);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
private onInit() {
|
|
136
|
-
this.dispatchEvent(new CustomEvent(TBID_EVENT_INIT));
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
private async onLogin() {
|
|
140
|
-
this.dispatchEvent(new CustomEvent(TBID_EVENT_LOGIN));
|
|
141
|
-
this.dispatchEvent(new CustomEvent(EVENT_LOGIN));
|
|
142
|
-
let lock = new Lock(LOCK_KEY, LOCK_TIMEOUT);
|
|
143
|
-
let { user } = await getJSON<{ user: string }>(this.loginStatusPath, {
|
|
144
|
-
headers: csrfHeader(this.csrfToken)
|
|
145
|
-
});
|
|
146
|
-
let newSession =
|
|
147
|
-
(user === 'logged_in' && this.authenticated !== 'true') ||
|
|
148
|
-
(user === 'not_logged_in' && this.loginTried !== 'true');
|
|
149
|
-
if (newSession) {
|
|
150
|
-
if (lock.aquire()) window.location.href = this.loginRedirectPath;
|
|
151
|
-
else setTimeout(this.onLogin.bind(this), LOCK_TIMEOUT);
|
|
152
|
-
} else {
|
|
153
|
-
lock.release();
|
|
154
|
-
this.onInit();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private onLogout() {
|
|
159
|
-
this.dispatchEvent(new CustomEvent(TBID_EVENT_LOGOUT));
|
|
160
|
-
this.dispatchEvent(new CustomEvent(EVENT_LOGOUT));
|
|
161
|
-
SFIDWidget.init();
|
|
162
|
-
window.location.href = this.logoutRedirectPath;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export class Lock {
|
|
167
|
-
constructor(private key: string, private timeout: number) {}
|
|
168
|
-
|
|
169
|
-
aquire() {
|
|
170
|
-
if (this.locked) return false;
|
|
171
|
-
sessionStorage.setItem(this.key, String(Date.now() + this.timeout));
|
|
172
|
-
return true;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
release() {
|
|
176
|
-
sessionStorage.removeItem(this.key);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private get existingLock() {
|
|
180
|
-
let storageValue = sessionStorage.getItem(this.key) || '';
|
|
181
|
-
let value = parseInt(storageValue, 10);
|
|
182
|
-
return Number.isNaN(value) ? 0 : value;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
private get locked() {
|
|
186
|
-
return this.existingLock > Date.now();
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function loadSFID(src: string) {
|
|
191
|
-
let load = new Promise<void>((resolve, reject) => {
|
|
192
|
-
let script = document.createElement('script');
|
|
193
|
-
script.onload = () => resolve(undefined);
|
|
194
|
-
script.onerror = () => reject(new Error('TH_TBID_SFID_LOAD'));
|
|
195
|
-
script.async = false;
|
|
196
|
-
script.src = src;
|
|
197
|
-
document.head.appendChild(script);
|
|
198
|
-
});
|
|
199
|
-
let timeout = new Promise((_, reject) =>
|
|
200
|
-
setTimeout(reject, 5000, new Error('TH_TBID_SFID_TIMEOUT'))
|
|
201
|
-
);
|
|
202
|
-
return Promise.race([load, timeout]);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function loadIframe(url: string) {
|
|
206
|
-
return new Promise((resolve, reject) => {
|
|
207
|
-
const iframe = document.createElement('iframe');
|
|
208
|
-
iframe.setAttribute('sandbox', 'allow-scripts');
|
|
209
|
-
iframe.style.display = 'none';
|
|
210
|
-
iframe.addEventListener('load', resolve, { once: true });
|
|
211
|
-
iframe.addEventListener('error', reject, { once: true });
|
|
212
|
-
iframe.src = url;
|
|
213
|
-
document.body.appendChild(iframe);
|
|
214
|
-
});
|
|
215
|
-
}
|