Skip to content

Commit 2bccf2a

Browse files
authored
fix: secure site screens breaks in demo (#4792)
1 parent d686cda commit 2bccf2a

File tree

3 files changed

+337
-5
lines changed

3 files changed

+337
-5
lines changed

.changeset/shy-states-appear.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@reown/appkit-controllers': patch
3+
'@reown/appkit-scaffold-ui': patch
4+
'@reown/appkit': patch
5+
'@reown/appkit-ui': patch
6+
'pay-test-exchange': patch
7+
'@reown/appkit-adapter-bitcoin': patch
8+
'@reown/appkit-adapter-ethers': patch
9+
'@reown/appkit-adapter-ethers5': patch
10+
'@reown/appkit-adapter-solana': patch
11+
'@reown/appkit-adapter-wagmi': patch
12+
'@reown/appkit-utils': patch
13+
'@reown/appkit-cdn': patch
14+
'@reown/appkit-cli': patch
15+
'@reown/appkit-codemod': patch
16+
'@reown/appkit-common': patch
17+
'@reown/appkit-core': patch
18+
'@reown/appkit-experimental': patch
19+
'@reown/appkit-pay': patch
20+
'@reown/appkit-polyfills': patch
21+
'@reown/appkit-siwe': patch
22+
'@reown/appkit-siwx': patch
23+
'@reown/appkit-testing': patch
24+
'@reown/appkit-universal-connector': patch
25+
'@reown/appkit-wallet': patch
26+
'@reown/appkit-wallet-button': patch
27+
---
28+
29+
Fixed an issue where secure site screens would break in demo

packages/scaffold-ui/src/views/w3m-approve-transaction-view/index.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ import { LitElement, html } from 'lit'
22
import { state } from 'lit/decorators.js'
33

44
import { getW3mThemeVariables } from '@reown/appkit-common'
5-
import { ConnectorController, ModalController, ThemeController } from '@reown/appkit-controllers'
5+
import {
6+
ConnectorController,
7+
ModalController,
8+
OptionsController,
9+
ThemeController
10+
} from '@reown/appkit-controllers'
611
import { customElement } from '@reown/appkit-ui'
712

813
import styles from './styles.js'
@@ -63,21 +68,23 @@ export class W3mApproveTransactionView extends LitElement {
6368

6469
this.iframe.style.height = `${PAGE_HEIGHT}px`
6570

66-
// Update container size to prevent the iframe from being cut off
6771
container.style.height = `${PAGE_HEIGHT}px`
68-
if (width && width <= 430) {
72+
if (OptionsController.state.enableEmbedded) {
73+
this.updateFrameSizeForEmbeddedMode()
74+
} else if (width && width <= 430) {
75+
// Update container size to prevent the iframe from being cut off
6976
this.iframe.style.width = '100%'
7077
this.iframe.style.left = '0px'
7178
this.iframe.style.bottom = '0px'
7279
this.iframe.style.top = 'unset'
80+
this.onShowIframe()
7381
} else {
7482
this.iframe.style.width = `${PAGE_WIDTH}px`
7583
this.iframe.style.left = `calc(50% - ${PAGE_WIDTH / 2}px)`
7684
this.iframe.style.top = `calc(50% - ${PAGE_HEIGHT / 2}px + ${HEADER_HEIGHT / 2}px)`
7785
this.iframe.style.bottom = 'unset'
86+
this.onShowIframe()
7887
}
79-
this.ready = true
80-
this.onShowIframe()
8188
})
8289
this.bodyObserver.observe(window.document.body)
8390
}
@@ -90,6 +97,7 @@ export class W3mApproveTransactionView extends LitElement {
9097
// -- Private ------------------------------------------- //
9198
private onShowIframe() {
9299
const isMobile = window.innerWidth <= 430
100+
this.ready = true
93101
this.iframe.style.animation = isMobile
94102
? 'w3m-iframe-zoom-in-mobile 200ms var(--wui-ease-out-power-2)'
95103
: 'w3m-iframe-zoom-in 200ms var(--wui-ease-out-power-2)'
@@ -113,6 +121,28 @@ export class W3mApproveTransactionView extends LitElement {
113121
})
114122
}
115123
}
124+
125+
private async updateFrameSizeForEmbeddedMode() {
126+
const container = this?.renderRoot?.querySelector('div') as HTMLDivElement
127+
128+
/*
129+
* Wait for the resize to complete to avoid getting wrong rect sizes
130+
* as it is not updated yet when the animation is pending
131+
*/
132+
await new Promise(resolve => {
133+
setTimeout(resolve, 300)
134+
})
135+
136+
const rect = this.getBoundingClientRect()
137+
138+
container.style.width = '100%'
139+
140+
this.iframe.style.left = `${rect.left}px`
141+
this.iframe.style.top = `${rect.top}px`
142+
this.iframe.style.width = `${rect.width}px`
143+
this.iframe.style.height = `${rect.height}px`
144+
this.onShowIframe()
145+
}
116146
}
117147

118148
declare global {
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
import { fixture } from '@open-wc/testing'
2+
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
3+
4+
import { html } from 'lit'
5+
6+
import { type ThemeType, type ThemeVariables, getW3mThemeVariables } from '@reown/appkit-common'
7+
import {
8+
type AuthConnector,
9+
ConnectorController,
10+
ModalController,
11+
OptionsController,
12+
ThemeController
13+
} from '@reown/appkit-controllers'
14+
15+
import { W3mApproveTransactionView } from '../../src/views/w3m-approve-transaction-view/index'
16+
17+
// --- Types ---------------------------------------------------- //
18+
interface MockResizeObserver extends ResizeObserver {
19+
triggerCallback(entries: Partial<ResizeObserverEntry>[]): void
20+
}
21+
22+
// --- Constants ---------------------------------------------------- //
23+
const FRAME_CONTAINER = '[data-ready]'
24+
const IFRAME_ID = 'w3m-iframe'
25+
26+
const MOCK_AUTH_CONNECTOR = {
27+
provider: {
28+
syncTheme: vi.fn().mockResolvedValue(undefined)
29+
}
30+
} as unknown as AuthConnector
31+
32+
const MOCK_THEME_SNAPSHOT = {
33+
themeMode: 'dark',
34+
themeVariables: {
35+
'--w3m-accent-color': '#3396ff',
36+
'--w3m-background-color': '#000000'
37+
}
38+
} as unknown as ReturnType<typeof ThemeController.getSnapshot>
39+
40+
const MOCK_RECT = {
41+
left: 100,
42+
top: 200,
43+
width: 400,
44+
height: 600
45+
} as DOMRect
46+
47+
beforeAll(() => {
48+
global.ResizeObserver = class {
49+
private callback: (entries: ResizeObserverEntry[], observer: ResizeObserver) => void
50+
51+
constructor(callback: (entries: ResizeObserverEntry[], observer: ResizeObserver) => void) {
52+
this.callback = callback
53+
}
54+
55+
observe() {}
56+
unobserve() {}
57+
disconnect() {}
58+
59+
triggerCallback(entries: Partial<ResizeObserverEntry>[]) {
60+
const mockEntries = entries.map(entry => ({
61+
contentBoxSize: entry.contentBoxSize || [],
62+
borderBoxSize: entry.borderBoxSize || [],
63+
contentRect: entry.contentRect || {
64+
width: 0,
65+
height: 0,
66+
top: 0,
67+
left: 0,
68+
bottom: 0,
69+
right: 0
70+
},
71+
devicePixelContentBoxSize: entry.devicePixelContentBoxSize || [],
72+
target: entry.target || document.body
73+
})) as ResizeObserverEntry[]
74+
this.callback(mockEntries, this)
75+
}
76+
}
77+
})
78+
79+
beforeAll(() => {
80+
const mockIframe = document.createElement('iframe')
81+
mockIframe.id = IFRAME_ID
82+
mockIframe.style.display = 'none'
83+
document.body.appendChild(mockIframe)
84+
})
85+
86+
describe('W3mApproveTransactionView - Basic Rendering', () => {
87+
beforeEach(() => {
88+
vi.resetAllMocks()
89+
90+
vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({
91+
...OptionsController.state,
92+
enableEmbedded: false
93+
})
94+
95+
vi.spyOn(ConnectorController, 'getAuthConnector').mockReturnValue(MOCK_AUTH_CONNECTOR)
96+
vi.spyOn(ThemeController, 'getSnapshot').mockReturnValue(MOCK_THEME_SNAPSHOT)
97+
vi.spyOn(ModalController, 'subscribeKey').mockReturnValue(() => {})
98+
})
99+
100+
afterEach(() => {
101+
vi.resetAllMocks()
102+
})
103+
104+
it('should render basic structure with frame container', async () => {
105+
const element = await fixture<W3mApproveTransactionView>(
106+
html`<w3m-approve-transaction-view></w3m-approve-transaction-view>`
107+
)
108+
await element.updateComplete
109+
110+
const frameContainer = element.shadowRoot?.querySelector(FRAME_CONTAINER)
111+
112+
expect(frameContainer).not.toBeNull()
113+
expect(frameContainer?.getAttribute('data-ready')).toBe('false')
114+
expect(frameContainer?.id).toBe('w3m-frame-container')
115+
})
116+
117+
it('should initialize with ready state as false', async () => {
118+
const element = await fixture<W3mApproveTransactionView>(
119+
html`<w3m-approve-transaction-view></w3m-approve-transaction-view>`
120+
)
121+
await element.updateComplete
122+
123+
expect(element.ready).toBe(false)
124+
})
125+
126+
it('should subscribe to modal controller events on construction', async () => {
127+
const element = await fixture<W3mApproveTransactionView>(
128+
html`<w3m-approve-transaction-view></w3m-approve-transaction-view>`
129+
)
130+
131+
await element.updateComplete
132+
133+
expect(ModalController.subscribeKey).toHaveBeenCalledWith('open', expect.any(Function))
134+
expect(ModalController.subscribeKey).toHaveBeenCalledWith('shake', expect.any(Function))
135+
})
136+
137+
it('should sync theme on first update', async () => {
138+
const element = await fixture<W3mApproveTransactionView>(
139+
html`<w3m-approve-transaction-view></w3m-approve-transaction-view>`
140+
)
141+
142+
await element.updateComplete
143+
144+
expect(ConnectorController.getAuthConnector).toHaveBeenCalled()
145+
expect(ThemeController.getSnapshot).toHaveBeenCalled()
146+
expect(MOCK_AUTH_CONNECTOR.provider.syncTheme).toHaveBeenCalledWith({
147+
themeVariables: MOCK_THEME_SNAPSHOT.themeVariables,
148+
w3mThemeVariables: getW3mThemeVariables(
149+
MOCK_THEME_SNAPSHOT.themeVariables as ThemeVariables,
150+
MOCK_THEME_SNAPSHOT.themeMode as ThemeType
151+
)
152+
})
153+
})
154+
})
155+
156+
describe('W3mApproveTransactionView - Iframe Positioning Logic', () => {
157+
let element: W3mApproveTransactionView
158+
let mockIframe: HTMLIFrameElement
159+
160+
beforeEach(async () => {
161+
vi.resetAllMocks()
162+
163+
mockIframe = document.getElementById(IFRAME_ID) as HTMLIFrameElement
164+
165+
vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({
166+
...OptionsController.state,
167+
enableEmbedded: false
168+
})
169+
170+
vi.spyOn(ConnectorController, 'getAuthConnector').mockReturnValue(MOCK_AUTH_CONNECTOR)
171+
vi.spyOn(ThemeController, 'getSnapshot').mockReturnValue(MOCK_THEME_SNAPSHOT)
172+
vi.spyOn(ModalController, 'subscribeKey').mockReturnValue(() => {})
173+
174+
element = await fixture<W3mApproveTransactionView>(
175+
html`<w3m-approve-transaction-view></w3m-approve-transaction-view>`
176+
)
177+
178+
vi.spyOn(element, 'getBoundingClientRect').mockReturnValue(MOCK_RECT)
179+
})
180+
181+
it('should position iframe correctly for desktop view', async () => {
182+
Object.defineProperty(window, 'innerWidth', { value: 1200, writable: true })
183+
184+
await element.updateComplete
185+
186+
const resizeObserver = element['bodyObserver'] as MockResizeObserver
187+
188+
if (resizeObserver) {
189+
const mockEntries = [
190+
{
191+
contentBoxSize: [{ inlineSize: 1200, blockSize: 800 }]
192+
}
193+
]
194+
await resizeObserver.triggerCallback(mockEntries)
195+
}
196+
197+
expect(mockIframe.style.display).toBe('block')
198+
expect(mockIframe.style.width).toBe('360px')
199+
expect(mockIframe.style.left).toBe('calc(50% - 180px)')
200+
expect(mockIframe.style.top).toBe('calc(50% - 268px)')
201+
expect(mockIframe.style.bottom).toBe('unset')
202+
})
203+
204+
it('should position iframe correctly for mobile view', async () => {
205+
Object.defineProperty(window, 'innerWidth', { value: 400, writable: true })
206+
207+
await element.updateComplete
208+
209+
const resizeObserver = element['bodyObserver'] as MockResizeObserver
210+
if (resizeObserver) {
211+
const mockEntries = [
212+
{
213+
contentBoxSize: [{ inlineSize: 400, blockSize: 600 }]
214+
}
215+
]
216+
await resizeObserver.triggerCallback(mockEntries)
217+
}
218+
219+
expect(mockIframe.style.display).toBe('block')
220+
expect(mockIframe.style.width).toBe('100%')
221+
expect(mockIframe.style.left).toBe('0px')
222+
expect(mockIframe.style.bottom).toBe('0px')
223+
expect(mockIframe.style.top).toBe('unset')
224+
})
225+
226+
it('should position iframe correctly for embedded mode', async () => {
227+
vi.spyOn(OptionsController, 'state', 'get').mockReturnValue({
228+
...OptionsController.state,
229+
enableEmbedded: true
230+
})
231+
232+
vi.spyOn(global, 'setTimeout').mockImplementation((callback: any) => {
233+
callback()
234+
235+
return setTimeout(() => {}, 1)
236+
})
237+
238+
await element.updateComplete
239+
240+
const resizeObserver = element['bodyObserver'] as MockResizeObserver
241+
if (resizeObserver) {
242+
const mockEntries = [
243+
{
244+
contentBoxSize: [{ inlineSize: 800, blockSize: 600 }]
245+
}
246+
]
247+
await resizeObserver.triggerCallback(mockEntries)
248+
}
249+
250+
expect(mockIframe.style.display).toBe('block')
251+
expect(mockIframe.style.left).toBe('100px')
252+
expect(mockIframe.style.top).toBe('200px')
253+
expect(mockIframe.style.width).toBe('400px')
254+
expect(mockIframe.style.height).toBe('600px')
255+
})
256+
257+
it('should set ready state to true after positioning', async () => {
258+
await element.updateComplete
259+
260+
const resizeObserver = element['bodyObserver'] as MockResizeObserver
261+
262+
if (resizeObserver) {
263+
const mockEntries = [
264+
{
265+
contentBoxSize: [{ inlineSize: 1200, blockSize: 800 }]
266+
}
267+
]
268+
await resizeObserver.triggerCallback(mockEntries)
269+
}
270+
271+
expect(element.ready).toBe(true)
272+
})
273+
})

0 commit comments

Comments
 (0)