diff --git a/.eslintrc b/.eslintrc index 31d1ab2..d80e57a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,6 +21,7 @@ "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-argument": "off" + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/ban-ts-comment": "off" } } diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b9a3daf..382788a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -13,7 +13,7 @@ jobs: node-version: '16' cache: 'yarn' - name: Install dependecies - run: yarn + run: yarn install --frozen-lockfile - name: Run lint script run: yarn lint build: @@ -25,7 +25,7 @@ jobs: node-version: '16' cache: 'yarn' - name: Install dependecies - run: yarn + run: yarn install --frozen-lockfile - name: Run build script run: yarn build test: @@ -37,7 +37,7 @@ jobs: node-version: '16' cache: 'yarn' - name: Install dependecies - run: yarn + run: yarn install --frozen-lockfile - name: Run test script run: yarn test e2e: @@ -49,6 +49,6 @@ jobs: node-version: '16' cache: 'yarn' - name: Install dependecies - run: yarn + run: yarn install --frozen-lockfile - name: Start examples and run e2e tests run: yarn e2e diff --git a/packages/widget-core/src/__tests__/create-widget.spec.ts b/packages/widget-core/src/__tests__/create-widget.spec.ts index 922d817..5973cf6 100644 --- a/packages/widget-core/src/__tests__/create-widget.spec.ts +++ b/packages/widget-core/src/__tests__/create-widget.spec.ts @@ -163,4 +163,38 @@ describe('createWidget', () => { expect(mockAssignCustomerData).toBeCalledWith({ name: 'foo' }) }) + + it('should prevent from multiple initializations', () => { + const widget: WidgetInstance = createWidget(widgetConfig) + + widget.init() + widget.init() + + expect(mockLiveChatWidget.init).toBeCalledTimes(1) + }) + + it('should prevent destroying widget while it is loading', () => { + const widget: WidgetInstance = createWidget(widgetConfig) + + widget.init() + widget.destroy() + + expect(mockLiveChatWidget.call).not.toBeCalledWith('destroy') + }) + + it('should handle `on_after_load` callback', () => { + const widget: WidgetInstance = createWidget(widgetConfig) + widget.destroy = jest.fn(widget.destroy) + + widget.init() + + expect(window.LC_API?.on_after_load).toBeDefined() + + window.LC_API?.on_after_load?.() + expect(widget.destroy).not.toBeCalled() + + widget.destroy() + window.LC_API?.on_after_load?.() + expect(widget.destroy).toBeCalledTimes(2) + }) }) diff --git a/packages/widget-core/src/create-js-api.ts b/packages/widget-core/src/create-js-api.ts index 6339053..f8c54f7 100644 --- a/packages/widget-core/src/create-js-api.ts +++ b/packages/widget-core/src/create-js-api.ts @@ -3,7 +3,9 @@ import type { ExtendedWindow } from './types' declare const window: ExtendedWindow -export function createJSApi(): void { +const scriptRef: { current: HTMLScriptElement | null } = { current: null } + +export function createJSApi() { const { slice } = Array.prototype /* istanbul ignore next */ @@ -40,7 +42,13 @@ export function createJSApi(): void { script.type = 'text/javascript' script.src = 'https://cdn.livechatinc.com/tracking.js' document.head.appendChild(script) + scriptRef.current = script }, } + + scriptRef.current?.remove() + window.LiveChatWidget = window.LiveChatWidget || api + + return scriptRef } diff --git a/packages/widget-core/src/create-widget.ts b/packages/widget-core/src/create-widget.ts index 1751c67..1f133ff 100644 --- a/packages/widget-core/src/create-widget.ts +++ b/packages/widget-core/src/create-widget.ts @@ -33,11 +33,15 @@ export type WidgetInstance = { } type State = { + isLoading: boolean currentEventHandlers: EventHandlers + desiredState: 'loaded' | 'destroyed' | 'unknown' } export function createWidget(config: WidgetConfig): WidgetInstance { const state: State = { + isLoading: false, + desiredState: 'unknown', currentEventHandlers: { onReady: config.onReady, onNewEvent: config.onNewEvent, @@ -52,7 +56,7 @@ export function createWidget(config: WidgetConfig): WidgetInstance { }, } - createJSApi() + const scriptRef = createJSApi() assignConfiguration(config) assignVisibility(config.visibility) assignEventHandlers('on', state.currentEventHandlers) @@ -64,31 +68,58 @@ export function createWidget(config: WidgetConfig): WidgetInstance { window.__lc.integration_name = process.env.PACKAGE_NAME return { - init: () => { + init() { + state.desiredState = 'loaded' + if (state.isLoading) { + return + } + + window.LC_API = window.LC_API || {} + window.LC_API.on_after_load = () => { + state.isLoading = false + if (state.desiredState === 'destroyed') { + this.destroy() + } + state.desiredState = 'unknown' + } + lifecycleEmit('init') + state.isLoading = true window.LiveChatWidget.init() }, - destroy: () => { + + destroy() { + state.desiredState = 'destroyed' + if (state.isLoading) { + return + } + lifecycleEmit('destroy') + scriptRef.current?.remove() window.LiveChatWidget.call('destroy') }, - updateVisibility: (visibility) => { + + updateVisibility(visibility) { assignVisibility(visibility) }, - updateEventHandlers: (eventHabndlers) => { + + updateEventHandlers(eventHabndlers) { assignEventHandlers('off', state.currentEventHandlers) assignEventHandlers('on', eventHabndlers) state.currentEventHandlers = { ...eventHabndlers } }, - updateSessionVariables: (sessionVariables) => { + + updateSessionVariables(sessionVariables) { if (sessionVariables) { window.LiveChatWidget.call('update_session_variables', sessionVariables) } }, - hideGreeting: () => { + + hideGreeting() { window.LiveChatWidget.call('hide_greeting') }, - updateCustomerData: (customerData) => { + + updateCustomerData(customerData) { assignCustomerData(customerData) }, } diff --git a/packages/widget-core/src/types.ts b/packages/widget-core/src/types.ts index c483a6a..de32a5f 100644 --- a/packages/widget-core/src/types.ts +++ b/packages/widget-core/src/types.ts @@ -14,6 +14,9 @@ export type ExtendedWindow = Window & { call: typeof call init: VoidFunction } + LC_API?: { + on_after_load?: VoidFunction + } } declare function on(name: 'ready', handler: EventHandlers['onReady']): void diff --git a/scripts/test.mjs b/scripts/test.mjs index b0f36c0..3916742 100644 --- a/scripts/test.mjs +++ b/scripts/test.mjs @@ -1,11 +1,5 @@ -import isCI from 'is-ci' - if (!fs.existsSync('packages/widget-core/dist')) { await $`lerna run build --scope @livechat/widget-core` } -if (isCI) { - await $`lerna run coverage` -} else { - await $`lerna run test` -} +await $`lerna run test`