Skip to content

Commit 30de59d

Browse files
committed
feat: initial commit
0 parents  commit 30de59d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

109 files changed

+11267
-0
lines changed

.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
umd
2+
*.js
3+
*.js.map
4+
*.d.ts
5+
6+
/node_modules/
7+
/actions/
8+
/client/
9+
/umd/
10+
/util/

README.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# API Extractor Issue
2+
3+
API Extractor is unable to parse out API for the `actions` portion of this package. See:
4+
5+
* [`src/index.ts`](src/index.ts)
6+
* [`src/actions/index.ts`](src/actions/index.ts)
7+
8+
## To build
9+
10+
1. `yarn`
11+
2. `yarn run build`
12+
3. `yarn run api`

api-extractor.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/api-extractor.schema.json",
3+
"compiler": {
4+
"configType": "tsconfig",
5+
"rootFolder": "."
6+
},
7+
"project": {
8+
"entryPointSourceFile": "index.d.ts"
9+
}
10+
}

package.json

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "api-extractor-issue",
3+
"version": "1.0.0",
4+
"types": "index.d.ts",
5+
"main": "index.js",
6+
"files": [
7+
"MessageTransport.d.ts",
8+
"MessageTransport.js",
9+
"MessageTransport.js.map",
10+
"actions/",
11+
"client/",
12+
"index.d.ts",
13+
"index.js",
14+
"index.js.map",
15+
"util/"
16+
],
17+
"private": true,
18+
"repository": "git@github.com:gf3/api-extractor-issue.git",
19+
"author": "Gianni Chiappetta <gianni@runlevel6.org>",
20+
"license": "MIT",
21+
"scripts": {
22+
"api": "api-extractor run",
23+
"docs": "api-documenter markdown",
24+
"build": "NODE_ENV=production tsc"
25+
},
26+
"sideEffects": false,
27+
"devDependencies": {
28+
"@microsoft/api-documenter": "^7.0.13",
29+
"@microsoft/api-extractor": "^7.0.9",
30+
"@types/node": "^10.12.5",
31+
"typescript": "3.2.1"
32+
}
33+
}

src/MessageTransport.ts

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import {AnyAction} from './actions/types';
2+
import {fromAction, AppActionType} from './actions/Error';
3+
import {isAppBridgeAction, isAppMessage} from './actions/validator';
4+
import {TransportDispatch} from './client';
5+
import {addAndRemoveFromCollection} from './util/collection';
6+
7+
/**
8+
* @internal
9+
*/
10+
export type HandlerData = {data: AnyAction};
11+
12+
/**
13+
* @internal
14+
*/
15+
export type Handler = (event: HandlerData) => void;
16+
17+
/**
18+
* @internal
19+
*/
20+
export interface MessageTransport {
21+
dispatch(message: any): void;
22+
hostFrame: Window;
23+
localOrigin: string;
24+
subscribe(handler: Handler): () => void;
25+
}
26+
27+
/**
28+
* Create a MessageTransport from an IFrame.
29+
* @remarks
30+
* Used on the host-side to create a postMessage MessageTransport.
31+
* @beta
32+
*/
33+
export function fromFrame(frame: HTMLIFrameElement, localOrigin: string): MessageTransport {
34+
const handlers: Handler[] = [];
35+
36+
if (typeof frame === 'undefined' || !frame.ownerDocument || !frame.ownerDocument.defaultView) {
37+
throw fromAction('App frame is undefined', AppActionType.WINDOW_UNDEFINED);
38+
}
39+
40+
const parent = frame.ownerDocument.defaultView;
41+
42+
parent.addEventListener('message', event => {
43+
if (event.origin !== localOrigin || !isAppMessage(event)) {
44+
return;
45+
}
46+
47+
for (const handler of handlers) {
48+
handler(event);
49+
}
50+
});
51+
52+
return {
53+
localOrigin,
54+
55+
hostFrame: parent,
56+
57+
dispatch(message) {
58+
const contentWindow = frame.contentWindow;
59+
if (contentWindow) {
60+
contentWindow.postMessage(message, '*');
61+
}
62+
},
63+
64+
subscribe(handler) {
65+
return addAndRemoveFromCollection(handlers, handler);
66+
},
67+
};
68+
}
69+
70+
/**
71+
* Create a MessageTransport from a parent window.
72+
* @remarks
73+
* Used on the client-side to create a postMessage MessageTransport.
74+
* @beta
75+
*/
76+
export function fromWindow(contentWindow: Window, localOrigin: string): MessageTransport {
77+
const handlers: Handler[] = [];
78+
if (typeof window !== undefined && contentWindow !== window) {
79+
window.addEventListener('message', event => {
80+
if (
81+
event.source !== contentWindow ||
82+
!(isAppBridgeAction(event.data.payload) || isAppMessage(event))
83+
) {
84+
return;
85+
}
86+
87+
for (const handler of handlers) {
88+
handler(event);
89+
}
90+
});
91+
}
92+
93+
return {
94+
localOrigin,
95+
96+
hostFrame: contentWindow,
97+
98+
dispatch(message: TransportDispatch) {
99+
if (!message.source || !message.source.shopOrigin) {
100+
return;
101+
}
102+
103+
const messageOrigin = `https://${message.source.shopOrigin}`;
104+
105+
contentWindow.postMessage(message, messageOrigin);
106+
},
107+
108+
subscribe(handler) {
109+
return addAndRemoveFromCollection(handlers, handler);
110+
},
111+
};
112+
}

src/actions/Button/README.md

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Button
2+
3+
## Setup
4+
5+
Create an app and import the `Button` module from `@shopify/app-bridge/actions`. Note that we'll be referring to this sample application throughout the examples below.
6+
7+
```js
8+
import createApp from '@shopify/app-bridge';
9+
import {Button} from '@shopify/app-bridge/actions';
10+
11+
const app = createApp({
12+
apiKey: '12345',
13+
});
14+
```
15+
16+
## Create a button
17+
18+
Generate a primary button with the label `Save`:
19+
20+
```js
21+
const myButton = Button.create(app, {label: 'Save'});
22+
```
23+
24+
## Subscribe to click action
25+
26+
You can subscribe to button actions by calling `subscribe`. This returns a method that you can call to unsubscribe from the action:
27+
28+
```js
29+
const myButton = Button.create(app, {label: 'Save'});
30+
const clickUnsubscribe = myButton.subscribe(Button.Action.CLICK, data => {
31+
// Do something with the click event
32+
});
33+
34+
// Unsubscribe to click actions
35+
clickUnsubscribe();
36+
```
37+
38+
## Dispatch click action
39+
40+
```js
41+
const myButton = Button.create(app, {label: 'Save'});
42+
myButton.dispatch(Button.Action.CLICK);
43+
```
44+
45+
## Dispatch click action with a payload
46+
47+
```js
48+
const myButton = Button.create(app, {label: 'Save'});
49+
// Trigger the action with a payload
50+
myButton.dispatch(Button.Action.CLICK, {message: 'Saved'});
51+
52+
// Subscribe to the action and read the payload
53+
myButton.subscribe(Button.Action.CLICK, data => {
54+
// data = { payload: { message: 'Saved'} }
55+
console.log(`Received ${data.payload.message} message`);
56+
});
57+
```
58+
59+
## Attach buttons to a modal
60+
61+
You can attach buttons to other actions such as modals. To learn more about modals, see [Modal](../Modal).
62+
63+
```js
64+
const okButton = Button.create(app, {label: 'Ok'});
65+
const cancelButton = Button.create(app, {label: 'Cancel'});
66+
const modalOptions = {
67+
title: 'My Modal',
68+
message: 'Hello world!',
69+
footer: {primary: okButton, secondary: [cancelButton]},
70+
};
71+
72+
const myModal = Modal.create(app, modalOptions);
73+
```
74+
75+
## Button Style
76+
77+
You can change the style of the button by passing the `style` property. Buttons support a single alternate style, the `Danger` style:
78+
79+
```js
80+
const myButton = Button.create(app, {label: 'Delete', style: Button.Style.Danger});
81+
```
82+
83+
## Update options
84+
85+
You can call the `set` method with partial button options to update the options of an existing button. This automatically triggers the `update` action on the button and merges the new given options with existing options:
86+
87+
```js
88+
const myButton = Button.create(app, {label: 'Save'});
89+
myButton.set({disabled: true});
90+
```
91+
92+
## Unsubscribe
93+
94+
You call `unsubscribe` to remove all current subscriptions on the button:
95+
96+
```js
97+
const myButton = Button.create(app, {label: 'Save'});
98+
myButton.subscribe(Button.Action.CLICK, data => {
99+
// Do something with the click event
100+
});
101+
102+
myButton.unsubscribe();
103+
```

src/actions/Button/actions.ts

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/**
2+
* @module Button
3+
*/
4+
5+
import {ClientApplication} from '../../client';
6+
import {actionWrapper, getEventNameSpace, getMergedProps, ActionSet} from '../helper';
7+
import {ActionSetProps, ClickAction, Component, ComponentType, Group, MetaAction} from '../types';
8+
import {Action, ClickPayload, Icon, Options, Payload, Style} from './types';
9+
10+
export interface ButtonUpdateAction extends MetaAction {
11+
readonly group: string;
12+
payload: Payload;
13+
}
14+
15+
export type ButtonAction = ButtonUpdateAction | ClickAction | MetaAction;
16+
17+
export function clickButton(
18+
group: string,
19+
component: Component,
20+
payload?: ClickPayload,
21+
): ClickAction {
22+
const {id} = component;
23+
const action = getEventNameSpace(group, Action.CLICK, component);
24+
const buttonPayload: ClickPayload = {
25+
id,
26+
payload,
27+
};
28+
29+
return actionWrapper({type: action, group, payload: buttonPayload});
30+
}
31+
32+
export function update(group: string, component: Component, props: Payload): ButtonUpdateAction {
33+
const {id} = component;
34+
const {label} = props;
35+
const action = getEventNameSpace(group, Action.UPDATE, component);
36+
const buttonPayload: Payload = {
37+
id,
38+
label,
39+
...props,
40+
};
41+
42+
return actionWrapper({type: action, group, payload: buttonPayload});
43+
}
44+
45+
export function isValidButtonProps(button: Payload) {
46+
return typeof button.id === 'string' && typeof button.label === 'string';
47+
}
48+
49+
export class Button extends ActionSet implements ActionSetProps<Options, Payload> {
50+
label!: string;
51+
disabled = false;
52+
icon?: Icon;
53+
style?: Style;
54+
55+
constructor(app: ClientApplication<any>, options: Options) {
56+
super(app, ComponentType.Button, Group.Button);
57+
this.set(options, false);
58+
}
59+
60+
get options(): Options {
61+
return {
62+
disabled: this.disabled,
63+
icon: this.icon,
64+
label: this.label,
65+
style: this.style,
66+
};
67+
}
68+
69+
get payload(): Payload {
70+
return {
71+
id: this.id,
72+
...this.options,
73+
};
74+
}
75+
76+
set(options: Partial<Options>, shouldUpdate = true) {
77+
const mergedOptions = getMergedProps(this.options, options);
78+
const {label, disabled, icon, style} = mergedOptions;
79+
80+
this.label = label;
81+
this.disabled = !!disabled;
82+
this.icon = icon;
83+
this.style = style;
84+
85+
if (shouldUpdate) {
86+
this.dispatch(Action.UPDATE);
87+
}
88+
89+
return this;
90+
}
91+
92+
dispatch(action: Action.UPDATE): ActionSet;
93+
94+
dispatch(action: Action.CLICK, payload?: any): ActionSet;
95+
96+
dispatch(action: Action, payload?: any) {
97+
switch (action) {
98+
case Action.CLICK:
99+
this.app.dispatch(clickButton(this.group, this.component, payload));
100+
break;
101+
case Action.UPDATE:
102+
const updateAction = update(this.group, this.component, this.payload);
103+
this.app.dispatch(updateAction);
104+
break;
105+
}
106+
107+
return this;
108+
}
109+
}
110+
111+
export function create(app: ClientApplication<any>, options: Options) {
112+
return new Button(app, options);
113+
}

0 commit comments

Comments
 (0)