diff --git a/CHANGELOG.md b/CHANGELOG.md index ffc79a5..3fa6f3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.10.0] - 2021-04-01 + +### Added + +- Messages to handle MODAL open and close event + ## [1.9.2] - 2021-02-25 ### Fixed diff --git a/docs/events.md b/docs/events.md index dfb73f1..bebf009 100644 --- a/docs/events.md +++ b/docs/events.md @@ -52,7 +52,7 @@ Must be sent on application startup to get initial application context from the } ``` - The property `authToken` can only be accessed by applications and will not be exposed to extensions. Extensions should require an access_token using the auth value. _Also see [REQUIRE_AUTHENTICATION](#REQUIRE_AUTHENTICATION) event_ + The property `authToken` can only be accessed by applications and will not be exposed to extensions. Extensions should require an access*token using the auth value. \_Also see [REQUIRE_AUTHENTICATION](#REQUIRE_AUTHENTICATION) event* REQUIRE_CONTEXT will first return the response payload, then trigger individual ViewState object as describe in the ViewState section. @@ -320,6 +320,43 @@ Request value stored under specified key in cloud storage }); ``` +## Modal specific events + +Applciations can request do display a modal with a specified URL. Events include opening, closing, and confirmation a modal has been closed. + +- ### MODAL.OPEN + + Open a modal using `SHELL_EVENTS.Version1.MODAL.OPEN` event from your application. You can specify `modalSettings` like at title or size. + + ``` + this.sdk.emit(SHELL_EVENTS.Version1.MODAL.OPEN, { + url: 'https://example.com' + modalSettings: { + title: 'My title', + size: 'l'| 'm'|'s', + } + }); + ``` + +- ### MODAL.CLOSE + + Request closing of the open modal using `SHELL_EVENTS.Version1.MODAL.CLOSE` from your application or the opened modal. An object can be passed as parameter to be send back to the application which opened the modal. + + ``` + this.sdk.emit(SHELL_EVENTS.Version1.MODAL.CLOSE, { + [key: string]: any + }); + ``` + + An application can listen to the same event to trigger code on closing. This event is only received if the application emited the OPEN event. + + ```typescript + this.sdk.on(SHELL_EVENTS.Version1.MODAL.CLOSED, (content) => { + // React to the he closing of the app + // If MODAL.CLOSE was passed an argument, it will be provided here. + }); + ``` + ## Extension specific events ShellSdk provide a set of features which are specifically designed to allow communications with extensions running inside an application. diff --git a/package.json b/package.json index 97d2019..ae545e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fsm-shell", - "version": "1.9.2", + "version": "1.10.0", "description": "client library for FSM shell", "main": "release/fsm-shell-client.js", "module": "release/fsm-shell-client.es.js", diff --git a/src/ShellEvents.ts b/src/ShellEvents.ts index cdca3f6..12e39a5 100644 --- a/src/ShellEvents.ts +++ b/src/ShellEvents.ts @@ -19,6 +19,8 @@ export type EventType = | 'V1.OUTLET.REQUEST_CONTEXT' | 'V1.OUTLET.LOADING_SUCCESS' | 'V1.OUTLET.LOADING_FAIL' + | 'V1.MODAL.OPEN' + | 'V1.MODAL.CLOSE' | string; export const SHELL_EVENTS = { @@ -44,6 +46,10 @@ export const SHELL_EVENTS = { LOADING_SUCCESS: 'V1.OUTLET.LOADING_SUCCESS', LOADING_FAIL: 'V1.OUTLET.LOADING_FAIL', }, + MODAL: { + OPEN: 'V1.MODAL.OPEN', + CLOSE: 'V1.MODAL.CLOSE', + }, }, Version2: { GET_STORAGE_ITEM: 'V2.GET_STORAGE_ITEM', diff --git a/src/ShellSdk.ts b/src/ShellSdk.ts index 4f3948d..bbc1a5f 100644 --- a/src/ShellSdk.ts +++ b/src/ShellSdk.ts @@ -306,14 +306,24 @@ export class ShellSdk { return; } + let from = payload.from || []; + // If it come from an outlet if (payload.type === SHELL_EVENTS.Version1.SET_VIEW_STATE) { console.warn( '[ShellSDk] A plugin tried to update viewState using SetViewState which is not allowed for security reason.' ); return; + } else if ( + payload.type === SHELL_EVENTS.Version1.MODAL.OPEN && + from.length === 0 && + !this.allowedOrigins.some((o) => payload.value.url.startsWith(o)) + ) { + // If we are not root and first to receive OPEN, we block request opening a modal which has a different + // origin than the one allowed by the outlet + console.warn('[ShellSDk] MODAL OPEN url is not in allowedList.'); + return; } - let from = payload.from || []; // If we receive from outlet request_context to fetch plugin from target, we return LOADING_FAIL // if too many depth exchanges if ( diff --git a/src/ShellVersionInfo.ts b/src/ShellVersionInfo.ts index cbb6624..205a3f5 100644 --- a/src/ShellVersionInfo.ts +++ b/src/ShellVersionInfo.ts @@ -2,6 +2,6 @@ // MANUAL CHANGES TO THIS FILE WILL BE OVERWRITTEN !!! export const SHELL_VERSION_INFO = { - VERSION: 'Will-Be-Replaced-During-Build', - BUILD_TS: 'Will-Be-Replaced-During-Build', + VERSION: '1.10.0', + BUILD_TS: '2021-03-31T16:14:15.987Z', }; diff --git a/src/index.ts b/src/index.ts index 03dc413..d53bf83 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,6 +22,8 @@ import { AuthResponseType, AuthRequest, AuthResponse, + ModalOpenRequest, + ModalSize, } from './models/index'; export { @@ -42,4 +44,6 @@ export { AuthResponseType, AuthRequest, AuthResponse, + ModalOpenRequest, + ModalSize, }; diff --git a/src/models/index.ts b/src/models/index.ts index 2a446b1..4e917c6 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -6,6 +6,8 @@ export { UiPermissions } from './permissions/ui-permissions.model'; export { GetItemResponse } from './cloud-storage/get-item-response.model'; export { SetItemRequest } from './cloud-storage/set-item-request.model'; +export { ModalSize, ModalOpenRequest } from './modal/modal-open-request.model'; + export { SettingsResponse } from './settings/settings-response.model'; export { GetFeatureFlagRequest } from './feature-flag/get-feature-flag-request.model'; diff --git a/src/models/modal/modal-close-request.model.ts b/src/models/modal/modal-close-request.model.ts new file mode 100644 index 0000000..2089de9 --- /dev/null +++ b/src/models/modal/modal-close-request.model.ts @@ -0,0 +1,3 @@ +export interface ModalCloseRequest { + [key: string]: any; +} diff --git a/src/models/modal/modal-open-request.model.ts b/src/models/modal/modal-open-request.model.ts new file mode 100644 index 0000000..53a6797 --- /dev/null +++ b/src/models/modal/modal-open-request.model.ts @@ -0,0 +1,8 @@ +export type ModalSize = 'l' | 'm' | 's'; +export interface ModalOpenRequest { + url: string; + modalSettings?: { + title?: string; + size?: ModalSize; + }; +} diff --git a/src/models/outlets-delete-assignment/outlets-delete-assignment-response.model.ts b/src/models/outlets-delete-assignment/outlets-delete-assignment-response.model.ts index ab2c308..9b794e4 100644 --- a/src/models/outlets-delete-assignment/outlets-delete-assignment-response.model.ts +++ b/src/models/outlets-delete-assignment/outlets-delete-assignment-response.model.ts @@ -1,5 +1,4 @@ - export interface OutletsDeleteAssignmentResponse { - target: string, + target: string; error?: string; } diff --git a/src/tests/Outlets.spec.ts b/src/tests/Outlets.spec.ts index d405f98..1991e92 100644 --- a/src/tests/Outlets.spec.ts +++ b/src/tests/Outlets.spec.ts @@ -397,4 +397,53 @@ describe('Outlets', () => { expect(postMessageParent.called).toBe(false); expect(postMessageOutlet.called).toBe(true); }); + + it('should only open modals with url from allowedOrigins', () => { + const postMessageParent = sinon.spy(); + sdk = ShellSdk.init( + ({ + postMessage: postMessageParent, + } as any) as Window, + sdkOrigin, + windowMock + ); + sdk.setAllowedOrigins([EXTENSION_ORIGIN]); + + const postMessageOutlet = sinon.spy(); + const iframe = ({ + src: EXTENSION_SRC, + contentWindow: ({ + postMessage: postMessageOutlet, + } as any) as Window, + } as any) as HTMLIFrameElement; + sdk.registerOutlet(iframe); + + const requestContext = sinon.spy(); + + windowMockCallback({ + source: iframe.contentWindow, + origin: EXTENSION_ORIGIN, + data: { + type: SHELL_EVENTS.Version1.MODAL.OPEN, + value: { + url: EXTENSION_ORIGIN + '/my-modal-url/', + }, + }, + }); + expect(postMessageParent.called).toBe(true); + postMessageParent.resetHistory(); + + windowMockCallback({ + source: iframe.contentWindow, + origin: EXTENSION_ORIGIN, + data: { + type: SHELL_EVENTS.Version1.MODAL.OPEN, + value: { + url: 'https://example.com/my-modal-url/', + }, + }, + }); + expect(postMessageParent.called).toBe(false); + postMessageParent.resetHistory(); + }); });