@cowprotocol/widget-lib 0.22.3 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +353 -0
- package/LICENSE +674 -0
- package/README.md +0 -4
- package/jest.config.ts +10 -0
- package/package.json +10 -20
- package/project.json +51 -0
- package/src/IframeCowEventEmitter.ts +47 -0
- package/src/IframeSafeSdkBridge.ts +77 -0
- package/src/cowSwapWidget.ts +259 -0
- package/src/flexibleConfig.ts +39 -0
- package/src/index.ts +7 -0
- package/src/resolveFlexibleConfigValues.ts +14 -0
- package/src/themeUtils.ts +7 -0
- package/src/types.ts +458 -0
- package/src/urlUtils.spec.ts +88 -0
- package/src/urlUtils.ts +66 -0
- package/src/widgetIframeTransport.ts +5 -0
- package/tsconfig.json +22 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +9 -0
- package/IframeCowEventEmitter.d.ts +0 -10
- package/IframeSafeSdkBridge.d.ts +0 -8
- package/cowSwapWidget.d.ts +0 -18
- package/flexibleConfig.d.ts +0 -5
- package/index.d.ts +0 -7
- package/index.iife.js +0 -1
- package/index.js +0 -1
- package/index.mjs +0 -499
- package/resolveFlexibleConfigValues.d.ts +0 -2
- package/themeUtils.d.ts +0 -2
- package/types.d.ts +0 -365
- package/urlUtils.d.ts +0 -4
- package/widgetIframeTransport.d.ts +0 -3
package/README.md
CHANGED
|
@@ -18,10 +18,6 @@ You can find a detailed description of all widget parameters in the [documentati
|
|
|
18
18
|
npm install @cowprotocol/widget-lib --save
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
```
|
|
22
|
-
yarn add @cowprotocol/widget-lib
|
|
23
|
-
```
|
|
24
|
-
|
|
25
21
|
Create a container somewhere in your website, the widget will be rendered inside it:
|
|
26
22
|
|
|
27
23
|
```html
|
package/jest.config.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
displayName: 'widget-lib',
|
|
3
|
+
preset: '../../jest.preset.js',
|
|
4
|
+
transform: {
|
|
5
|
+
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
|
6
|
+
},
|
|
7
|
+
setupFilesAfterEnv: ['../../jest.setup.ts'],
|
|
8
|
+
moduleFileExtensions: ['ts', 'js', 'html'],
|
|
9
|
+
coverageDirectory: '../../../coverage/libs/widget-lib',
|
|
10
|
+
}
|
package/package.json
CHANGED
|
@@ -1,27 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cowprotocol/widget-lib",
|
|
3
|
-
"version": "
|
|
4
|
-
"type": "commonjs",
|
|
3
|
+
"version": "1.1.2",
|
|
5
4
|
"description": "CoW Swap Widget Library. Allows you to easily embed a CoW Swap widget on your website.",
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
"require": "./index.mjs"
|
|
10
|
-
},
|
|
5
|
+
"type": "commonjs",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
11
8
|
"license": "ISC",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"url": "https://github.com/cowprotocol/cowswap"
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
15
11
|
},
|
|
16
|
-
"keywords": [
|
|
17
|
-
"cowprotocol",
|
|
18
|
-
"widget",
|
|
19
|
-
"cowswap",
|
|
20
|
-
"dex",
|
|
21
|
-
"widget-lib"
|
|
22
|
-
],
|
|
23
12
|
"dependencies": {
|
|
24
|
-
"@cowprotocol/
|
|
25
|
-
"@cowprotocol/iframe-transport": "
|
|
13
|
+
"@cowprotocol/cow-sdk": "7.3.5",
|
|
14
|
+
"@cowprotocol/iframe-transport": "2.0.3",
|
|
15
|
+
"@cowprotocol/events": "3.2.0"
|
|
26
16
|
}
|
|
27
|
-
}
|
|
17
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "widget-lib",
|
|
3
|
+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
|
4
|
+
"sourceRoot": "libs/widget-lib/src",
|
|
5
|
+
"projectType": "library",
|
|
6
|
+
"targets": {
|
|
7
|
+
"build": {
|
|
8
|
+
"executor": "@nx/js:tsc",
|
|
9
|
+
"outputs": ["{options.outputPath}"],
|
|
10
|
+
"options": {
|
|
11
|
+
"outputPath": "dist/libs/widget-lib",
|
|
12
|
+
"main": "libs/widget-lib/src/index.ts",
|
|
13
|
+
"tsConfig": "libs/widget-lib/tsconfig.lib.json",
|
|
14
|
+
"assets": ["libs/widget-lib/*.md"],
|
|
15
|
+
"updateBuildableProjectDepsInPackageJson": true,
|
|
16
|
+
"buildableProjectDepsInPackageJsonType": "dependencies"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"serve": {
|
|
20
|
+
"executor": "@nx/vite:dev-server",
|
|
21
|
+
"defaultConfiguration": "development",
|
|
22
|
+
"options": {
|
|
23
|
+
"buildTarget": "widget-lib:build",
|
|
24
|
+
"port": 3012
|
|
25
|
+
},
|
|
26
|
+
"configurations": {}
|
|
27
|
+
},
|
|
28
|
+
"lint": {
|
|
29
|
+
"executor": "@nx/eslint:lint",
|
|
30
|
+
"outputs": ["{options.outputFile}"],
|
|
31
|
+
"options": {
|
|
32
|
+
"lintFilePatterns": ["libs/widget-lib/**/*.ts"]
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"test": {
|
|
36
|
+
"executor": "@nx/jest:jest",
|
|
37
|
+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
|
38
|
+
"options": {
|
|
39
|
+
"jestConfig": "libs/widget-lib/jest.config.ts",
|
|
40
|
+
"passWithNoTests": true
|
|
41
|
+
},
|
|
42
|
+
"configurations": {
|
|
43
|
+
"ci": {
|
|
44
|
+
"ci": true,
|
|
45
|
+
"codeCoverage": true
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"tags": []
|
|
51
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SimpleCowEventEmitter,
|
|
3
|
+
CowWidgetEventListeners,
|
|
4
|
+
CowWidgetEvents,
|
|
5
|
+
CowWidgetEventPayloadMap,
|
|
6
|
+
} from '@cowprotocol/events'
|
|
7
|
+
|
|
8
|
+
import { WindowListener, WidgetMethodsEmit } from './types'
|
|
9
|
+
import { widgetIframeTransport } from './widgetIframeTransport'
|
|
10
|
+
|
|
11
|
+
export class IframeCowEventEmitter {
|
|
12
|
+
private eventEmitter = new SimpleCowEventEmitter<CowWidgetEventPayloadMap, CowWidgetEvents>()
|
|
13
|
+
private listeners: CowWidgetEventListeners = []
|
|
14
|
+
private widgetListener: WindowListener
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
private contentWindow: Window,
|
|
18
|
+
listeners: CowWidgetEventListeners = [],
|
|
19
|
+
) {
|
|
20
|
+
// Subscribe listeners to local event emitter
|
|
21
|
+
this.updateListeners(listeners)
|
|
22
|
+
|
|
23
|
+
// Listen to iFrame, and forward to local event emitter
|
|
24
|
+
this.widgetListener = widgetIframeTransport.listenToMessageFromWindow(
|
|
25
|
+
this.contentWindow,
|
|
26
|
+
WidgetMethodsEmit.EMIT_COW_EVENT,
|
|
27
|
+
(cowEvent) => this.eventEmitter.emit(cowEvent.event, cowEvent.payload),
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public stopListeningIframe(): void {
|
|
32
|
+
widgetIframeTransport.stopListeningWindowListener(this.contentWindow, this.widgetListener)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
public updateListeners(listeners?: CowWidgetEventListeners): void {
|
|
36
|
+
// Unsubscribe from previous listeners
|
|
37
|
+
for (const listener of this.listeners) {
|
|
38
|
+
this.eventEmitter.off(listener)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Subscribe to events
|
|
42
|
+
this.listeners = listeners || []
|
|
43
|
+
for (const listener of this.listeners) {
|
|
44
|
+
this.eventEmitter.on(listener)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
export class IframeSafeSdkBridge {
|
|
2
|
+
forwardSdkMessage: (event: MessageEvent<unknown>) => void
|
|
3
|
+
|
|
4
|
+
constructor(
|
|
5
|
+
private appWindow: Window,
|
|
6
|
+
private iframeWidow: Window,
|
|
7
|
+
) {
|
|
8
|
+
this.forwardSdkMessage = (event: MessageEvent<unknown>) => {
|
|
9
|
+
if (!isSafeMessage(event.data)) {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof window !== 'undefined' && event.origin === window.location.origin) {
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (isSafeMessageRequest(event.data)) {
|
|
18
|
+
this.appWindow.parent.postMessage(event.data, '*')
|
|
19
|
+
} else if (isSafeMessageResponse(event.data)) {
|
|
20
|
+
this.iframeWidow.postMessage(event.data, '*')
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.startListening()
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private startListening(): void {
|
|
28
|
+
this.appWindow.addEventListener('message', this.forwardSdkMessage)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
public stopListening(): void {
|
|
32
|
+
this.appWindow.removeEventListener('message', this.forwardSdkMessage)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isSafeMessage(obj: unknown): obj is SafeMessage {
|
|
37
|
+
return typeof obj === 'object' && obj !== null && 'id' in obj && typeof obj.id === 'string'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isSafeMessageRequest(message: SafeMessage): message is SafeMessageRequest {
|
|
41
|
+
return (
|
|
42
|
+
'method' in message &&
|
|
43
|
+
typeof message.method === 'string' &&
|
|
44
|
+
'params' in message &&
|
|
45
|
+
'env' in message &&
|
|
46
|
+
typeof message.env === 'object' &&
|
|
47
|
+
message.env !== null &&
|
|
48
|
+
'sdkVersion' in message.env
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isSafeMessageResponse(message: SafeMessage): message is SafeMessageResponse {
|
|
53
|
+
return (
|
|
54
|
+
'success' in message &&
|
|
55
|
+
typeof message.success === 'boolean' &&
|
|
56
|
+
'version' in message &&
|
|
57
|
+
typeof message.version === 'string'
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface SafeMessage {
|
|
62
|
+
id: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface SafeMessageRequest extends SafeMessage {
|
|
66
|
+
method: string
|
|
67
|
+
params: unknown
|
|
68
|
+
env: {
|
|
69
|
+
sdkVersion: string
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface SafeMessageResponse extends SafeMessage {
|
|
74
|
+
id: string
|
|
75
|
+
success: boolean
|
|
76
|
+
version: string
|
|
77
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { CowWidgetEventListeners } from '@cowprotocol/events'
|
|
2
|
+
import { IframeRpcProviderBridge } from '@cowprotocol/iframe-transport'
|
|
3
|
+
|
|
4
|
+
import { IframeCowEventEmitter } from './IframeCowEventEmitter'
|
|
5
|
+
import { IframeSafeSdkBridge } from './IframeSafeSdkBridge'
|
|
6
|
+
import {
|
|
7
|
+
CowSwapWidgetParams,
|
|
8
|
+
CowSwapWidgetProps,
|
|
9
|
+
EthereumProvider,
|
|
10
|
+
WidgetMethodsEmit,
|
|
11
|
+
WidgetMethodsListen,
|
|
12
|
+
WindowListener,
|
|
13
|
+
} from './types'
|
|
14
|
+
import { buildWidgetPath, buildWidgetUrl, buildWidgetUrlQuery } from './urlUtils'
|
|
15
|
+
import { widgetIframeTransport } from './widgetIframeTransport'
|
|
16
|
+
|
|
17
|
+
const DEFAULT_HEIGHT = '640px'
|
|
18
|
+
const DEFAULT_WIDTH = '450px'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Reference: IframeResizer (apps/cowswap-frontend/src/modules/injectedWidget/updaters/IframeResizer.ts)
|
|
22
|
+
* Sometimes MutationObserver doesn't trigger when the height of the widget changes and the widget displays with a scrollbar.
|
|
23
|
+
* To avoid this we add a threshold to the height.
|
|
24
|
+
* 20px
|
|
25
|
+
*/
|
|
26
|
+
const HEIGHT_THRESHOLD = 20
|
|
27
|
+
|
|
28
|
+
const noopHandler: CowSwapWidgetHandler = {
|
|
29
|
+
updateParams: () => void 0,
|
|
30
|
+
updateListeners: () => void 0,
|
|
31
|
+
updateProvider: () => void 0,
|
|
32
|
+
destroy: () => void 0,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Callback function signature for updating the CoW Swap Widget.
|
|
37
|
+
*/
|
|
38
|
+
export interface CowSwapWidgetHandler {
|
|
39
|
+
updateParams: (params: CowSwapWidgetParams) => void
|
|
40
|
+
updateListeners: (newListeners?: CowWidgetEventListeners) => void
|
|
41
|
+
updateProvider: (newProvider?: EthereumProvider) => void
|
|
42
|
+
destroy: () => void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Generates and injects a CoW Swap Widget into the provided container.
|
|
47
|
+
* @param container - The HTML element to inject the widget into.
|
|
48
|
+
* @param params - Parameters for configuring the widget.
|
|
49
|
+
* @returns A callback function to update the widget with new settings.
|
|
50
|
+
*/
|
|
51
|
+
export function createCowSwapWidget(container: HTMLElement, props: CowSwapWidgetProps): CowSwapWidgetHandler {
|
|
52
|
+
const { params, provider: providerAux, listeners } = props
|
|
53
|
+
let provider = providerAux
|
|
54
|
+
let currentParams = params
|
|
55
|
+
|
|
56
|
+
if (typeof window === 'undefined') return noopHandler
|
|
57
|
+
|
|
58
|
+
// 1. Create a brand new iframe
|
|
59
|
+
const iframe = createIframe(params)
|
|
60
|
+
|
|
61
|
+
// 2. Clear the content (delete any previous iFrame if it exists)
|
|
62
|
+
container.innerHTML = ''
|
|
63
|
+
container.appendChild(iframe)
|
|
64
|
+
|
|
65
|
+
const { contentWindow: iframeWindow } = iframe
|
|
66
|
+
if (!iframeWindow) {
|
|
67
|
+
console.error('Iframe does not contain a window', iframe)
|
|
68
|
+
throw new Error('Iframe does not contain a window!')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 3. Send appCode (once the widget posts the ACTIVATE message)
|
|
72
|
+
const windowListeners: WindowListener[] = []
|
|
73
|
+
windowListeners.push(sendAppCodeOnActivation(iframeWindow, params.appCode))
|
|
74
|
+
|
|
75
|
+
// 4. Handle widget height changes
|
|
76
|
+
windowListeners.push(...listenToHeightChanges(iframe, params.height, params.maxHeight))
|
|
77
|
+
|
|
78
|
+
// 5. Intercept deeplinks navigation in the iframe
|
|
79
|
+
windowListeners.push(interceptDeepLinks())
|
|
80
|
+
|
|
81
|
+
// 6. Handle and forward widget events to the listeners
|
|
82
|
+
const iFrameCowEventEmitter = new IframeCowEventEmitter(window, listeners)
|
|
83
|
+
|
|
84
|
+
// 7. Wire up the iframeRpcProviderBridge with the provider (so RPC calls flow back and forth)
|
|
85
|
+
let iframeRpcProviderBridge = updateProvider(iframeWindow, null, provider)
|
|
86
|
+
|
|
87
|
+
// 8. Schedule the uploading of the params, once the iframe is loaded
|
|
88
|
+
iframe.addEventListener('load', () => updateParams(iframeWindow, currentParams, provider))
|
|
89
|
+
|
|
90
|
+
// 9. Listen for messages from the iframe
|
|
91
|
+
const iframeSafeSdkBridge = new IframeSafeSdkBridge(window, iframeWindow)
|
|
92
|
+
|
|
93
|
+
// 10. Return the handler, so the widget, listeners, and provider can be updated
|
|
94
|
+
return {
|
|
95
|
+
updateParams: (newParams: CowSwapWidgetParams) => {
|
|
96
|
+
currentParams = newParams
|
|
97
|
+
updateParams(iframeWindow, currentParams, provider)
|
|
98
|
+
},
|
|
99
|
+
updateListeners: (newListeners?: CowWidgetEventListeners) => iFrameCowEventEmitter.updateListeners(newListeners),
|
|
100
|
+
updateProvider: (newProvider) => {
|
|
101
|
+
provider = newProvider
|
|
102
|
+
iframeRpcProviderBridge = updateProvider(iframeWindow, iframeRpcProviderBridge, newProvider)
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
destroy: () => {
|
|
106
|
+
// Disconnect rpc provider and unsubscribe to events
|
|
107
|
+
iframeRpcProviderBridge.disconnect()
|
|
108
|
+
// Stop listening for cow events
|
|
109
|
+
iFrameCowEventEmitter.stopListeningIframe()
|
|
110
|
+
|
|
111
|
+
// Disconnect all listeners
|
|
112
|
+
windowListeners.forEach((listener) => window.removeEventListener('message', listener))
|
|
113
|
+
|
|
114
|
+
// Stop listening for SDK messages
|
|
115
|
+
iframeSafeSdkBridge.stopListening()
|
|
116
|
+
|
|
117
|
+
// Destroy the iframe
|
|
118
|
+
container.removeChild(iframe)
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Update the provider for the iframeRpcProviderBridge.
|
|
125
|
+
*
|
|
126
|
+
* It will disconnect from the previous provider and connect to the new one.
|
|
127
|
+
*
|
|
128
|
+
* @param iframe iframe window
|
|
129
|
+
* @param iframeRpcProviderBridge iframe RPC manager
|
|
130
|
+
* @param newProvider new provider
|
|
131
|
+
*
|
|
132
|
+
* @returns the iframeRpcProviderBridge
|
|
133
|
+
*/
|
|
134
|
+
function updateProvider(
|
|
135
|
+
iframe: Window,
|
|
136
|
+
iframeRpcProviderBridge: IframeRpcProviderBridge | null,
|
|
137
|
+
newProvider?: EthereumProvider,
|
|
138
|
+
): IframeRpcProviderBridge {
|
|
139
|
+
// Disconnect from the previous provider bridge
|
|
140
|
+
if (iframeRpcProviderBridge) {
|
|
141
|
+
iframeRpcProviderBridge.disconnect()
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const providerBridge = iframeRpcProviderBridge || new IframeRpcProviderBridge(iframe)
|
|
145
|
+
|
|
146
|
+
// Connect to the new provider
|
|
147
|
+
if (newProvider) {
|
|
148
|
+
providerBridge.onConnect(newProvider)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return providerBridge
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Creates an iframe element for the CoW Swap Widget based on provided parameters and settings.
|
|
156
|
+
* @param params - Parameters for the widget.
|
|
157
|
+
* @returns The generated HTMLIFrameElement.
|
|
158
|
+
*/
|
|
159
|
+
function createIframe(params: CowSwapWidgetParams): HTMLIFrameElement {
|
|
160
|
+
const { width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT } = params
|
|
161
|
+
|
|
162
|
+
const iframe = document.createElement('iframe')
|
|
163
|
+
|
|
164
|
+
iframe.src = buildWidgetUrl(params)
|
|
165
|
+
iframe.width = width
|
|
166
|
+
iframe.height = height
|
|
167
|
+
iframe.style.border = '0'
|
|
168
|
+
iframe.allow = 'clipboard-read; clipboard-write'
|
|
169
|
+
|
|
170
|
+
return iframe
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Updates the CoW Swap Widget based on the new settings provided.
|
|
175
|
+
* @param params - New params for the widget.
|
|
176
|
+
* @param contentWindow - Window object of the widget's iframe.
|
|
177
|
+
*/
|
|
178
|
+
function updateParams(
|
|
179
|
+
contentWindow: Window,
|
|
180
|
+
params: CowSwapWidgetParams,
|
|
181
|
+
provider: EthereumProvider | undefined,
|
|
182
|
+
): void {
|
|
183
|
+
const hasProvider = !!provider
|
|
184
|
+
|
|
185
|
+
const pathname = buildWidgetPath(params)
|
|
186
|
+
const search = buildWidgetUrlQuery(params).toString()
|
|
187
|
+
|
|
188
|
+
// Omit theme from appParams
|
|
189
|
+
const { theme: _theme, ...appParams } = params
|
|
190
|
+
|
|
191
|
+
widgetIframeTransport.postMessageToWindow(contentWindow, WidgetMethodsListen.UPDATE_PARAMS, {
|
|
192
|
+
urlParams: {
|
|
193
|
+
pathname,
|
|
194
|
+
search,
|
|
195
|
+
},
|
|
196
|
+
appParams,
|
|
197
|
+
hasProvider,
|
|
198
|
+
})
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Sends appCode to the contentWindow of the widget once the widget is activated.
|
|
203
|
+
*
|
|
204
|
+
* @param contentWindow - Window object of the widget's iframe.
|
|
205
|
+
* @param appCode - A unique identifier for the app.
|
|
206
|
+
*/
|
|
207
|
+
function sendAppCodeOnActivation(
|
|
208
|
+
contentWindow: Window,
|
|
209
|
+
appCode: string | undefined,
|
|
210
|
+
): (payload: MessageEvent<unknown>) => void {
|
|
211
|
+
return widgetIframeTransport.listenToMessageFromWindow(window, WidgetMethodsEmit.ACTIVATE, () => {
|
|
212
|
+
// Update the appData
|
|
213
|
+
widgetIframeTransport.postMessageToWindow(contentWindow, WidgetMethodsListen.UPDATE_APP_DATA, {
|
|
214
|
+
metaData: appCode ? { appCode } : undefined,
|
|
215
|
+
})
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Since deeplinks are not supported in iframes, this function intercepts the window.open calls from the widget and opens
|
|
221
|
+
*/
|
|
222
|
+
function interceptDeepLinks(): (payload: MessageEvent<unknown>) => void {
|
|
223
|
+
return widgetIframeTransport.listenToMessageFromWindow(
|
|
224
|
+
window,
|
|
225
|
+
WidgetMethodsEmit.INTERCEPT_WINDOW_OPEN,
|
|
226
|
+
({ href, rel, target }) => {
|
|
227
|
+
const url = href.toString()
|
|
228
|
+
|
|
229
|
+
if (!url.startsWith('http') && url.match(/^[a-zA-Z0-9]+:\/\//)) {
|
|
230
|
+
window.open(url, target, rel)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Listens for iframeHeight emitted by the widget, and applies dynamic height adjustments to the widget's iframe.
|
|
239
|
+
*
|
|
240
|
+
* @param iframe - The HTMLIFrameElement of the widget.
|
|
241
|
+
* @param defaultHeight - Default height for the widget.
|
|
242
|
+
* @param maxHeight - Maximum height for the widget.
|
|
243
|
+
*/
|
|
244
|
+
function listenToHeightChanges(
|
|
245
|
+
iframe: HTMLIFrameElement,
|
|
246
|
+
defaultHeight = DEFAULT_HEIGHT,
|
|
247
|
+
maxHeight?: number,
|
|
248
|
+
): WindowListener[] {
|
|
249
|
+
return [
|
|
250
|
+
widgetIframeTransport.listenToMessageFromWindow(window, WidgetMethodsEmit.UPDATE_HEIGHT, (data) => {
|
|
251
|
+
const newHeight = data.height ? data.height + HEIGHT_THRESHOLD : undefined
|
|
252
|
+
|
|
253
|
+
iframe.style.height = newHeight ? `${maxHeight ? Math.min(newHeight, maxHeight) : newHeight}px` : defaultHeight
|
|
254
|
+
}),
|
|
255
|
+
widgetIframeTransport.listenToMessageFromWindow(window, WidgetMethodsEmit.SET_FULL_HEIGHT, ({ isUpToSmall }) => {
|
|
256
|
+
iframe.style.height = isUpToSmall ? defaultHeight : `${maxHeight || document.body.offsetHeight}px`
|
|
257
|
+
}),
|
|
258
|
+
]
|
|
259
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SupportedChainId } from '@cowprotocol/cow-sdk'
|
|
2
|
+
|
|
3
|
+
import { FlexibleConfig, PerNetworkConfig, PerTradeTypeConfig, TradeType } from './types'
|
|
4
|
+
|
|
5
|
+
const TradeTypes = Object.values(TradeType)
|
|
6
|
+
|
|
7
|
+
export function resolveFlexibleConfig<T>(
|
|
8
|
+
config: FlexibleConfig<T>,
|
|
9
|
+
chainId: SupportedChainId,
|
|
10
|
+
tradeType: TradeType,
|
|
11
|
+
): T | undefined {
|
|
12
|
+
if (isPerTradeTypeConfig(config)) {
|
|
13
|
+
const value = config[tradeType]
|
|
14
|
+
|
|
15
|
+
return isPerNetworkConfig(value) ? value[chainId] : value
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (isPerNetworkConfig(config)) {
|
|
19
|
+
const value = config[chainId]
|
|
20
|
+
|
|
21
|
+
return isPerTradeTypeConfig(value) ? value[tradeType] : value
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return config as T
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isPerTradeTypeConfig<T>(config: FlexibleConfig<T>): config is PerTradeTypeConfig<T> {
|
|
28
|
+
if (typeof config !== 'object') return false
|
|
29
|
+
|
|
30
|
+
return Object.keys(config as object).every((key) => TradeTypes.includes(key as TradeType))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const D_REGEX = /^\d+$/
|
|
34
|
+
|
|
35
|
+
export function isPerNetworkConfig<T>(config: FlexibleConfig<T>): config is PerNetworkConfig<T> {
|
|
36
|
+
if (typeof config !== 'object') return false
|
|
37
|
+
|
|
38
|
+
return Object.keys(config as object).every((key) => D_REGEX.test(key))
|
|
39
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createCowSwapWidget } from './cowSwapWidget'
|
|
2
|
+
export { widgetIframeTransport } from './widgetIframeTransport'
|
|
3
|
+
export type { CowSwapWidgetHandler } from './cowSwapWidget'
|
|
4
|
+
export * from './types'
|
|
5
|
+
export * from './themeUtils'
|
|
6
|
+
export * from './flexibleConfig'
|
|
7
|
+
export { resolveFlexibleConfigValues } from './resolveFlexibleConfigValues'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { isPerNetworkConfig, isPerTradeTypeConfig } from './flexibleConfig'
|
|
2
|
+
import { FlexibleConfig } from './types'
|
|
3
|
+
|
|
4
|
+
export function resolveFlexibleConfigValues<T>(config: FlexibleConfig<T>): T[] {
|
|
5
|
+
if (isPerTradeTypeConfig(config)) {
|
|
6
|
+
return Object.values(config).flatMap((value) => (isPerNetworkConfig(value) ? Object.values(value) : [value]))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (isPerNetworkConfig(config)) {
|
|
10
|
+
return Object.values(config).flatMap((value) => (isPerTradeTypeConfig(value) ? Object.values(value) : [value]))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return [config] as T[]
|
|
14
|
+
}
|