Skip to content

Commit

Permalink
feat: init can be called at any time (#1319)
Browse files Browse the repository at this point in the history
When `init` is called and no `connect` message has been received at that point in time, an `init` message is sent to the parent window.
  • Loading branch information
andipaetzold authored Aug 1, 2022
1 parent 0092882 commit d95d441
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 28 deletions.
22 changes: 20 additions & 2 deletions lib/channel.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Signal } from './signal'
import { ConnectMessage } from './types'

export default function connect(
export function connect(
currentGlobal: typeof globalThis,
onConnect: (channel: Channel, message: ConnectMessage, messageQueue: unknown[]) => void
) {
Expand All @@ -25,7 +25,13 @@ function waitForConnect(currentGlobal: typeof globalThis, onConnect: Function) {

export class Channel {
private _messageHandlers: { [method: string]: Signal } = {}
private _responseHandlers: { [method: string]: any } = {}
private _responseHandlers: {
[method: string]: {
resolve: (value: any) => void
reject: (reason?: any) => void
}
} = {}

private _send: ReturnType<typeof createSender>

constructor(sourceId: string, currentGlobal: typeof globalThis) {
Expand Down Expand Up @@ -107,3 +113,15 @@ function createSender(sourceId: string, targetWindow: Window) {
return messageId
}
}

export function sendInitMessage(currentGlobal: typeof globalThis) {
const targetWindow = currentGlobal.parent

// The app is not connected yet so we can't provide an `id` or `source`
targetWindow.postMessage(
{
method: 'init',
},
'*'
)
}
2 changes: 1 addition & 1 deletion lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import createInitializer from './initialize'
import { createInitializer } from './initialize'
import createAPI from './api'
import { KnownSDK } from './types'

Expand Down
37 changes: 16 additions & 21 deletions lib/initialize.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ConnectMessage, KnownSDK } from './types'
import connect, { Channel } from './channel'
import { connect, Channel, sendInitMessage } from './channel'
import { createDeferred } from './utils/deferred'

export default function createInitializer(
export function createInitializer(
currentGlobal: typeof globalThis,
apiCreator: (channel: Channel, data: ConnectMessage, currentGlobal: typeof globalThis) => KnownSDK
) {
Expand Down Expand Up @@ -36,13 +37,8 @@ export default function createInitializer(
supressIframeWarning: false,
}
) {
if (!supressIframeWarning && currentGlobal.self === currentGlobal.top) {
console.error(`Cannot use App SDK outside of Contenful:
In order for the App SDK to function correctly, your app needs to be run in an iframe in the Contentful Web App, Compose or Launch.
Learn more about local development with the App SDK here:
https://www.contentful.com/developers/docs/extensibility/ui-extensions/faq/#how-can-i-develop-with-the-ui-extension-sdk-locally`)
if (!supressIframeWarning) {
warnIfOutsideOfContentful(currentGlobal)
}

if (!initializedSdks) {
Expand All @@ -64,6 +60,10 @@ Learn more about local development with the App SDK here:

return [api, customApi]
})

if (!connectDeferred.isFulfilled) {
sendInitMessage(currentGlobal)
}
}

initializedSdks.then(([sdk, customSdk]) =>
Expand All @@ -73,18 +73,13 @@ Learn more about local development with the App SDK here:
}
}

function createDeferred<T = any>() {
const deferred: {
promise: Promise<T>
resolve: (value: T | PromiseLike<T>) => void
} = {
promise: null as any,
resolve: null as any,
}
function warnIfOutsideOfContentful(currentGlobal: typeof globalThis) {
if (currentGlobal.self === currentGlobal.top) {
console.error(`Cannot use App SDK outside of Contenful:
deferred.promise = new Promise<T>((resolve) => {
deferred.resolve = resolve
})
In order for the App SDK to function correctly, your app needs to be run in an iframe in the Contentful Web App, Compose or Launch.
return deferred
Learn more about local development with the App SDK here:
https://www.contentful.com/developers/docs/extensibility/ui-extensions/faq/#how-can-i-develop-with-the-ui-extension-sdk-locally`)
}
}
26 changes: 26 additions & 0 deletions lib/utils/deferred.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface Deferred<T> {
promise: Promise<T>
resolve: (value: T | PromiseLike<T>) => void
isFulfilled: boolean
}

export function createDeferred<T = unknown>(): Deferred<T> {
const deferred: Deferred<T> = {
// @ts-expect-error Immediately set below
promise: null,

// @ts-expect-error Promise executor is immdiately executed and sets `resolve`
resolve: null,

isFulfilled: false,
}

deferred.promise = new Promise<T>((resolve) => {
deferred.resolve = (...args) => {
deferred.isFulfilled = true
resolve(...args)
}
})

return deferred
}
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"@types/cross-spawn": "6.0.2",
"@types/fs-extra": "9.0.13",
"@types/jsdom": "20.0.0",
"@types/mocha": "9.1.1",
"@types/nanoid": "3.0.0",
"@types/sinon": "^10.0.0",
"@types/sinon-chai": "^3.2.5",
Expand Down
2 changes: 1 addition & 1 deletion test/unit/channel.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { sinon, makeDOM, expect } from '../helpers'

import connect from '../../lib/channel'
import { connect } from '../../lib/channel'

describe('channel connect', function () {
beforeEach(function () {
Expand Down
6 changes: 3 additions & 3 deletions test/unit/initialize.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { sinon, makeDOM, expect } from '../helpers'

import initializeApi from '../../lib/initialize'
import { createInitializer } from '../../lib/initialize'
import { Channel } from '../../lib/channel'

describe('initializeApi(currentGlobal, apiCreator)', function () {
beforeEach(function () {
this.dom = makeDOM()
this.apiCreator = sinon.stub().returns({})
const init = initializeApi(this.dom.window, (...args) => this.apiCreator(...args))
const init = createInitializer(this.dom.window, (...args) => this.apiCreator(...args))
this.initialize = function () {
return new Promise((resolve) => init(resolve))
}
Expand All @@ -17,7 +17,7 @@ describe('initializeApi(currentGlobal, apiCreator)', function () {
beforeEach(function () {
this.api = {}
this.apiCreator = sinon.stub().returns(this.api)
this.init = initializeApi(this.dom.window, this.apiCreator)
this.init = createInitializer(this.dom.window, this.apiCreator)
})

it('is not invoked before connecting', function () {
Expand Down
15 changes: 15 additions & 0 deletions test/unit/utils/deferred.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect } from 'chai'
import { createDeferred } from '../../../lib/utils/deferred'

describe('isFulfilled', () => {
it('should be `false` before calling `resolve`', () => {
const deferred = createDeferred()
expect(deferred).to.eq(false)
})

it('should be `true` after calling `resolve`', () => {
const deferred = createDeferred()
deferred.resolve('yolo')
expect(deferred).to.eq(true)
})
})

0 comments on commit d95d441

Please sign in to comment.