Refactors and tests
This commit is contained in:
parent
983dba1126
commit
d445653c2d
6 changed files with 260 additions and 226 deletions
|
@ -1,4 +1,4 @@
|
||||||
import { Events, EventType, PayloadOfEvent, ThemeType } from "./events";
|
import { ThemeType } from "./events";
|
||||||
|
|
||||||
export type AppBridgeState = {
|
export type AppBridgeState = {
|
||||||
token?: string;
|
token?: string;
|
||||||
|
@ -8,43 +8,6 @@ export type AppBridgeState = {
|
||||||
path: string;
|
path: string;
|
||||||
theme: ThemeType;
|
theme: ThemeType;
|
||||||
};
|
};
|
||||||
type EventCallback<TPayload extends {} = {}> = (data: TPayload) => void;
|
|
||||||
type SubscribeMap = {
|
|
||||||
[type in EventType]: Record<symbol, EventCallback<PayloadOfEvent<type>>>;
|
|
||||||
};
|
|
||||||
|
|
||||||
function reducer(state: AppBridgeState, event: Events) {
|
|
||||||
switch (event.type) {
|
|
||||||
case EventType.handshake: {
|
|
||||||
const newState = {
|
|
||||||
...state,
|
|
||||||
ready: true,
|
|
||||||
token: event.payload.token,
|
|
||||||
};
|
|
||||||
|
|
||||||
return newState;
|
|
||||||
}
|
|
||||||
case EventType.redirect: {
|
|
||||||
const newState = {
|
|
||||||
...state,
|
|
||||||
path: event.payload.path,
|
|
||||||
};
|
|
||||||
|
|
||||||
return newState;
|
|
||||||
}
|
|
||||||
case EventType.theme: {
|
|
||||||
const newState = {
|
|
||||||
...state,
|
|
||||||
theme: event.payload.theme,
|
|
||||||
};
|
|
||||||
|
|
||||||
return newState;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AppBridgeStateContainer {
|
export class AppBridgeStateContainer {
|
||||||
private state: AppBridgeState = {
|
private state: AppBridgeState = {
|
||||||
|
@ -55,96 +18,6 @@ export class AppBridgeStateContainer {
|
||||||
theme: "light",
|
theme: "light",
|
||||||
};
|
};
|
||||||
|
|
||||||
private subscribeMap: SubscribeMap = {
|
|
||||||
handshake: {},
|
|
||||||
response: {},
|
|
||||||
redirect: {},
|
|
||||||
theme: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
private refererOrigin: string | null = null;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
try {
|
|
||||||
this.refererOrigin = new URL(document.referrer).origin;
|
|
||||||
} catch (e) {
|
|
||||||
// TODO probably throw
|
|
||||||
console.warn("document.referrer is empty");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listenOnMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO Move to higher instance
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private listenOnMessages() {
|
|
||||||
window.addEventListener(
|
|
||||||
"message",
|
|
||||||
({ origin, data }: Omit<MessageEvent, "data"> & { data: Events }) => {
|
|
||||||
// check if event origin matches the document referer
|
|
||||||
if (origin !== this.refererOrigin) {
|
|
||||||
// TODO what should happen here - be explicit
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = reducer(this.state, data);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO Validate and warn/throw
|
|
||||||
*/
|
|
||||||
const { type, payload } = data;
|
|
||||||
|
|
||||||
if (EventType[type]) {
|
|
||||||
Object.getOwnPropertySymbols(this.subscribeMap[type]).forEach((key) =>
|
|
||||||
// @ts-ignore fixme
|
|
||||||
this.subscribeMap[type][key](payload)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes to an Event.
|
|
||||||
*
|
|
||||||
* @param eventType - Event type.
|
|
||||||
* @param cb - Callback that executes when Event is registered. Called with Event payload object.
|
|
||||||
* @returns Unsubscribe function. Call to unregister the callback.
|
|
||||||
*/
|
|
||||||
subscribe<TEventType extends EventType, TPayload extends PayloadOfEvent<TEventType>>(
|
|
||||||
eventType: TEventType,
|
|
||||||
cb: EventCallback<TPayload>
|
|
||||||
) {
|
|
||||||
const key = Symbol("Callback token");
|
|
||||||
// @ts-ignore fixme
|
|
||||||
this.subscribeMap[eventType][key] = cb;
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
delete this.subscribeMap[eventType][key];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unsubscribe to all Events of type.
|
|
||||||
* If type not provider, unsubscribe all
|
|
||||||
*
|
|
||||||
* @param eventType - (optional) Event type. If empty, all callbacks will be unsubscribed.
|
|
||||||
*/
|
|
||||||
unsubscribeAll(eventType?: EventType) {
|
|
||||||
if (eventType) {
|
|
||||||
this.subscribeMap[eventType] = {};
|
|
||||||
} else {
|
|
||||||
this.subscribeMap = {
|
|
||||||
handshake: {},
|
|
||||||
response: {},
|
|
||||||
redirect: {},
|
|
||||||
theme: {},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getState() {
|
getState() {
|
||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { fireEvent } from "@testing-library/dom";
|
import { fireEvent } from "@testing-library/dom";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
// mock document.referrer
|
// mock document.referrer
|
||||||
const origin = "http://example.com";
|
const origin = "http://example.com";
|
||||||
|
@ -16,11 +16,23 @@ Object.defineProperty(window, "location", {
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
import { actions, DispatchResponseEvent, createApp } from ".";
|
import { actions, DispatchResponseEvent, createApp, HandshakeEvent } from ".";
|
||||||
|
|
||||||
|
const handshakeEvent: HandshakeEvent = {
|
||||||
|
payload: {
|
||||||
|
token: "mock-token",
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
type: "handshake",
|
||||||
|
};
|
||||||
|
|
||||||
describe("createApp", () => {
|
describe("createApp", () => {
|
||||||
const domain = "saleor.domain.host";
|
const domain = "saleor.domain.host";
|
||||||
const app = createApp(domain);
|
let app = createApp(domain);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
app = createApp(domain);
|
||||||
|
});
|
||||||
|
|
||||||
it("correctly sets the domain", () => {
|
it("correctly sets the domain", () => {
|
||||||
expect(app.getState().domain).toEqual(domain);
|
expect(app.getState().domain).toEqual(domain);
|
||||||
|
@ -42,23 +54,19 @@ describe("createApp", () => {
|
||||||
expect(app.getState().token).toEqual(token);
|
expect(app.getState().token).toEqual(token);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("subscribes to an event and returns unsubcribe function", () => {
|
it("subscribes to an event and returns unsubscribe function", () => {
|
||||||
// subscribe
|
|
||||||
const callback = vi.fn();
|
const callback = vi.fn();
|
||||||
const unsubscribe = app.subscribe("handshake", callback);
|
const unsubscribe = app.subscribe("handshake", callback);
|
||||||
|
|
||||||
expect(callback).not.toHaveBeenCalled();
|
expect(callback).not.toHaveBeenCalled();
|
||||||
|
|
||||||
const token = "fresh-token";
|
const token = "fresh-token";
|
||||||
// correct event
|
|
||||||
const payload = {
|
// First call proper event
|
||||||
token,
|
|
||||||
version: 1,
|
|
||||||
};
|
|
||||||
fireEvent(
|
fireEvent(
|
||||||
window,
|
window,
|
||||||
new MessageEvent("message", {
|
new MessageEvent("message", {
|
||||||
data: { type: "handshake", payload },
|
data: handshakeEvent,
|
||||||
origin,
|
origin,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -82,11 +90,10 @@ describe("createApp", () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(callback).toHaveBeenCalledTimes(1);
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
expect(callback).toHaveBeenCalledWith(payload);
|
expect(callback).toHaveBeenCalledWith(handshakeEvent.payload);
|
||||||
expect(app.getState().token).toEqual(token);
|
expect(app.getState().token).toEqual(handshakeEvent.payload.token);
|
||||||
expect(app.getState().id).toEqual("appid");
|
expect(app.getState().id).toEqual("appid");
|
||||||
|
|
||||||
// unsubscribe
|
|
||||||
unsubscribe();
|
unsubscribe();
|
||||||
|
|
||||||
fireEvent(
|
fireEvent(
|
||||||
|
@ -129,4 +136,36 @@ describe("createApp", () => {
|
||||||
|
|
||||||
it("times out after action response has not been registered", () =>
|
it("times out after action response has not been registered", () =>
|
||||||
expect(app.dispatch(actions.Redirect({ to: "/test" }))).rejects.toBeInstanceOf(Error));
|
expect(app.dispatch(actions.Redirect({ to: "/test" }))).rejects.toBeInstanceOf(Error));
|
||||||
|
|
||||||
|
it("unsubscribes from all listeners", () => {
|
||||||
|
const cb1 = vi.fn();
|
||||||
|
const cb2 = vi.fn();
|
||||||
|
|
||||||
|
app.subscribe("handshake", cb1);
|
||||||
|
app.subscribe("handshake", cb2);
|
||||||
|
|
||||||
|
fireEvent(
|
||||||
|
window,
|
||||||
|
new MessageEvent("message", {
|
||||||
|
data: handshakeEvent,
|
||||||
|
origin,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(cb1).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cb2).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
app.unsubscribeAll();
|
||||||
|
|
||||||
|
fireEvent(
|
||||||
|
window,
|
||||||
|
new MessageEvent("message", {
|
||||||
|
data: handshakeEvent,
|
||||||
|
origin,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(cb1).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cb2).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
200
src/app-bridge/app-bridge.ts
Normal file
200
src/app-bridge/app-bridge.ts
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
import { Actions } from "./actions";
|
||||||
|
import { AppBridgeState, AppBridgeStateContainer } from "./app-bridge-state";
|
||||||
|
import { SSR } from "./constants";
|
||||||
|
import { Events, EventType, PayloadOfEvent, ThemeType } from "./events";
|
||||||
|
|
||||||
|
const DISPATCH_RESPONSE_TIMEOUT = 1000;
|
||||||
|
|
||||||
|
type EventCallback<TPayload extends {} = {}> = (data: TPayload) => void;
|
||||||
|
type SubscribeMap = {
|
||||||
|
[type in EventType]: Record<symbol, EventCallback<PayloadOfEvent<type>>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
function eventStateReducer(state: AppBridgeState, event: Events) {
|
||||||
|
switch (event.type) {
|
||||||
|
case EventType.handshake: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
ready: true,
|
||||||
|
token: event.payload.token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case EventType.redirect: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
path: event.payload.path,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case EventType.theme: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
theme: event.payload.theme,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case EventType.response: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
/**
|
||||||
|
* Event comes from API, so always assume it can be something not covered by TS
|
||||||
|
*/
|
||||||
|
console.warn(`Invalid event received: ${(event as any)?.type}`);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createEmptySubscribeMap = (): SubscribeMap => ({
|
||||||
|
handshake: {},
|
||||||
|
response: {},
|
||||||
|
redirect: {},
|
||||||
|
theme: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export class AppBridge {
|
||||||
|
private state = new AppBridgeStateContainer();
|
||||||
|
|
||||||
|
private refererOrigin = new URL(document.referrer).origin;
|
||||||
|
|
||||||
|
private subscribeMap = createEmptySubscribeMap();
|
||||||
|
|
||||||
|
constructor(private targetDomain?: string) {
|
||||||
|
if (SSR) {
|
||||||
|
throw new Error(
|
||||||
|
"AppBridge detected you're running this app in SSR mode. Make sure to call `new AppBridge()` when window object exists."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetDomain) {
|
||||||
|
this.targetDomain = new URL(window.location.href).searchParams.get("domain") || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.refererOrigin) {
|
||||||
|
// TODO probably throw
|
||||||
|
console.warn("document.referrer is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setInitialState();
|
||||||
|
this.listenOnMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to an Event.
|
||||||
|
*
|
||||||
|
* @param eventType - Event type.
|
||||||
|
* @param cb - Callback that executes when Event is registered. Called with Event payload object.
|
||||||
|
* @returns Unsubscribe function. Call to unregister the callback.
|
||||||
|
*/
|
||||||
|
subscribe<TEventType extends EventType, TPayload extends PayloadOfEvent<TEventType>>(
|
||||||
|
eventType: TEventType,
|
||||||
|
cb: EventCallback<TPayload>
|
||||||
|
) {
|
||||||
|
const key = Symbol("Callback token");
|
||||||
|
// @ts-ignore fixme
|
||||||
|
this.subscribeMap[eventType][key] = cb;
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
delete this.subscribeMap[eventType][key];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe to all Events of type.
|
||||||
|
* If type not provider, unsubscribe all
|
||||||
|
*
|
||||||
|
* @param eventType - (optional) Event type. If empty, all callbacks will be unsubscribed.
|
||||||
|
*/
|
||||||
|
unsubscribeAll(eventType?: EventType) {
|
||||||
|
if (eventType) {
|
||||||
|
this.subscribeMap[eventType] = {};
|
||||||
|
} else {
|
||||||
|
this.subscribeMap = createEmptySubscribeMap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatch event to dashboard
|
||||||
|
*/
|
||||||
|
async dispatch<T extends Actions>(action: T) {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
if (!window.parent) {
|
||||||
|
reject(new Error("Parent window does not exist."));
|
||||||
|
} else {
|
||||||
|
window.parent.postMessage(
|
||||||
|
{
|
||||||
|
type: action.type,
|
||||||
|
payload: action.payload,
|
||||||
|
},
|
||||||
|
"*"
|
||||||
|
);
|
||||||
|
|
||||||
|
let intervalId: number;
|
||||||
|
|
||||||
|
const unsubscribe = this.subscribe(EventType.response, ({ actionId, ok }) => {
|
||||||
|
if (action.payload.actionId === actionId) {
|
||||||
|
unsubscribe();
|
||||||
|
clearInterval(intervalId);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
"Action responded with negative status. This indicates the action method was not used properly."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
intervalId = window.setInterval(() => {
|
||||||
|
unsubscribe();
|
||||||
|
reject(new Error("Action response timed out."));
|
||||||
|
}, DISPATCH_RESPONSE_TIMEOUT);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current state
|
||||||
|
*/
|
||||||
|
getState() {
|
||||||
|
return this.state.getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private setInitialState() {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const id = url.searchParams.get("id") || "";
|
||||||
|
const path = window.location.pathname || "";
|
||||||
|
const theme: ThemeType = url.searchParams.get("theme") === "light" ? "light" : "dark";
|
||||||
|
|
||||||
|
this.state.setState({ domain: this.targetDomain, id, path, theme });
|
||||||
|
}
|
||||||
|
|
||||||
|
private listenOnMessages() {
|
||||||
|
window.addEventListener(
|
||||||
|
"message",
|
||||||
|
({ origin, data }: Omit<MessageEvent, "data"> & { data: Events }) => {
|
||||||
|
if (origin !== this.refererOrigin) {
|
||||||
|
// TODO what should happen here - be explicit
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newState = eventStateReducer(this.state.getState(), data);
|
||||||
|
this.state.setState(newState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO Validate and warn/throw
|
||||||
|
*/
|
||||||
|
const { type, payload } = data;
|
||||||
|
|
||||||
|
if (EventType[type]) {
|
||||||
|
Object.getOwnPropertySymbols(this.subscribeMap[type]).forEach((key) =>
|
||||||
|
// @ts-ignore fixme
|
||||||
|
this.subscribeMap[type][key](payload)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ export const EventType = {
|
||||||
redirect: "redirect",
|
redirect: "redirect",
|
||||||
theme: "theme",
|
theme: "theme",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type EventType = Values<typeof EventType>;
|
export type EventType = Values<typeof EventType>;
|
||||||
|
|
||||||
type Event<Name extends EventType, Payload extends {}> = {
|
type Event<Name extends EventType, Payload extends {}> = {
|
||||||
|
|
|
@ -1,85 +1,4 @@
|
||||||
import { Actions } from "./actions";
|
import { AppBridge } from "./app-bridge";
|
||||||
import { AppBridgeStateContainer } from "./app-bridge-state";
|
|
||||||
import { SSR } from "./constants";
|
|
||||||
import { EventType, ThemeType } from "./events";
|
|
||||||
|
|
||||||
export function createApp(targetDomain?: string) {
|
|
||||||
const appBridgeState = new AppBridgeStateContainer();
|
|
||||||
|
|
||||||
if (SSR) {
|
|
||||||
throw new Error(
|
|
||||||
"AppBridge detected you're running this app in SSR mode. Make sure to call `createApp` when window object exists."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let domain: string;
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
const id = url.searchParams.get("id") || "";
|
|
||||||
const path = window.location.pathname || "";
|
|
||||||
const theme: ThemeType = url.searchParams.get("theme") === "light" ? "light" : "dark";
|
|
||||||
|
|
||||||
if (targetDomain) {
|
|
||||||
domain = targetDomain;
|
|
||||||
} else {
|
|
||||||
domain = url.searchParams.get("domain") || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
appBridgeState.setState({ domain, id, path, theme });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatches Action to Saleor Dashboard.
|
|
||||||
*
|
|
||||||
* @param action - Action containing type and payload.
|
|
||||||
* @returns Promise resolved when Action is successfully completed.
|
|
||||||
*/
|
|
||||||
async function dispatch<T extends Actions>(action: T) {
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
|
||||||
if (window.parent) {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{
|
|
||||||
type: action.type,
|
|
||||||
payload: action.payload,
|
|
||||||
},
|
|
||||||
"*"
|
|
||||||
);
|
|
||||||
|
|
||||||
let intervalId: NodeJS.Timer;
|
|
||||||
|
|
||||||
const unsubscribe = appBridgeState.subscribe(EventType.response, ({ actionId, ok }) => {
|
|
||||||
if (action.payload.actionId === actionId) {
|
|
||||||
unsubscribe();
|
|
||||||
clearInterval(intervalId);
|
|
||||||
|
|
||||||
if (ok) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
"Error: Action responded with negative status. This indicates the action method was not used properly."
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// If dashboard doesn't respond within 1 second, reject and unsubscribe
|
|
||||||
intervalId = setInterval(() => {
|
|
||||||
unsubscribe();
|
|
||||||
reject(new Error("Error: Action response timed out."));
|
|
||||||
}, 1000);
|
|
||||||
} else {
|
|
||||||
reject(new Error("Error: Parent window does not exist."));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
dispatch,
|
|
||||||
subscribe: appBridgeState.subscribe.bind(appBridgeState),
|
|
||||||
unsubscribeAll: appBridgeState.unsubscribeAll.bind(appBridgeState),
|
|
||||||
getState: appBridgeState.getState.bind(appBridgeState),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from "./actions";
|
export * from "./actions";
|
||||||
export * from "./events";
|
export * from "./events";
|
||||||
|
@ -87,5 +6,7 @@ export * from "./types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated avoid default functions in SDKs
|
* @deprecated avoid default functions in SDKs
|
||||||
|
* TODO: Expose AppBridge()
|
||||||
*/
|
*/
|
||||||
|
export const createApp = (targetDomain?: string) => new AppBridge(targetDomain);
|
||||||
export default createApp;
|
export default createApp;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createApp } from ".";
|
import { AppBridge } from "./app-bridge";
|
||||||
import { AppBridgeState } from "./app-bridge-state";
|
import { AppBridgeState } from "./app-bridge-state";
|
||||||
|
|
||||||
export type App = ReturnType<typeof createApp>;
|
export type App = typeof AppBridge;
|
||||||
export { AppBridgeState };
|
export { AppBridgeState };
|
||||||
|
|
Loading…
Reference in a new issue