also looking at this
fix(android): hand the launch link to the page that subscribes for it
#232
17 files
+776
-136
| @@ -23,17 +23,22 @@ function pageScript(): string { | ||
| 23 | 23 | |
| 24 | 24 | interface Post { action: string, callbackId?: string, [key: string]: unknown } |
| 25 | 25 | |
| 26 | function loadPage() { | |
| 26 | type Listener = (event: { type: string, detail: unknown }) => void | |
| 27 | ||
| 28 | /** `beforeInject` runs first, the way a page's own script runs before WebKit's didFinish injects the bridge. */ | |
| 29 | function loadPage(beforeInject?: (page: Record<string, any>) => void) { | |
| 27 | 30 | const posts: Post[] = [] |
| 28 | const listeners: Record<string, ((event: { type: string, detail: unknown }) => void)[]> = {} | |
| 31 | const listeners: Record<string, Listener[]> = {} | |
| 29 | 32 | const page: Record<string, any> = { |
| 30 | 33 | webkit: { messageHandlers: { craft: { postMessage: (message: Post) => posts.push(message) } } }, |
| 31 | addEventListener: (type: string, listener: (event: { type: string, detail: unknown }) => void) => { | |
| 34 | addEventListener: (type: string, listener: Listener) => { | |
| 32 | 35 | (listeners[type] ||= []).push(listener) |
| 33 | 36 | }, |
| 34 | removeEventListener: () => {}, | |
| 37 | removeEventListener: (type: string, listener: Listener) => { | |
| 38 | listeners[type] = (listeners[type] || []).filter(existing => existing !== listener) | |
| 39 | }, | |
| 35 | 40 | dispatchEvent: (event: { type: string, detail: unknown }) => { |
| 36 | for (const listener of listeners[event.type] || []) listener(event) | |
| 41 | for (const listener of [...(listeners[event.type] || [])]) listener(event) | |
| 37 | 42 | return true |
| 38 | 43 | }, |
| 39 | 44 | location: { href: 'craft://app/' }, |
| @@ -47,6 +52,7 @@ function loadPage() { | ||
| 47 | 52 | } |
| 48 | 53 | } |
| 49 | 54 | const quiet = { log() {}, warn() {}, error() {} } |
| 55 | beforeInject?.(page) | |
| 50 | 56 | // eslint-disable-next-line no-new-func |
| 51 | 57 | new Function('window', 'document', 'navigator', 'CustomEvent', 'console', pageScript())( |
| 52 | 58 | page, |
| @@ -69,6 +75,9 @@ function loadPage() { | ||
| 69 | 75 | // What Swift's resolveCallback and rejectCallback evaluate. |
| 70 | 76 | answer: (action: string, value: unknown) => craft._resolveCallback(last(action).callbackId, value), |
| 71 | 77 | refuse: (action: string, message: string, code: string) => craft._rejectCallback(last(action).callbackId, message, code), |
| 78 | // What DeepLinkManager.dispatchDeepLink evaluates, once the script has run. | |
| 79 | link: (url: string, initial: boolean) => | |
| 80 | page.dispatchEvent(new CustomEvent('craftDeepLink', { detail: { url, initial } })), | |
| 72 | 81 | } |
| 73 | 82 | } |
| 74 | 83 | |
| @@ -144,4 +153,36 @@ describe('the injected iOS page script', () => { | ||
| 144 | 153 | await expect(settled).rejects.toMatchObject({ code: 'NATIVE_CALL_FAILED' }) |
| 145 | 154 | }) |
| 146 | 155 | } |
| 156 | ||
| 157 | // #198 and #215. The Android page script runs the same block; its own test | |
| 158 | // covers the rest of the contract. | |
| 159 | const tick = () => new Promise(resolve => setTimeout(resolve, 5)) | |
| 160 | ||
| 161 | it('hands the launch link to a page that subscribes after it arrived', async () => { | |
| 162 | const page = loadPage() | |
| 163 | page.link('app://launch', true) | |
| 164 | ||
| 165 | const seen: unknown[] = [] | |
| 166 | page.craft.deepLinks.onLink((detail: unknown) => seen.push(detail)) | |
| 167 | await tick() | |
| 168 | ||
| 169 | expect(seen).toEqual([{ url: 'app://launch', initial: true }]) | |
| 170 | }) | |
| 171 | ||
| 172 | it('delivers the launch link once to a craftReady handler that asks for it both ways', async () => { | |
| 173 | // The handler runs inside the script, before native has dispatched the | |
| 174 | // link at all, so there is nothing held yet for getInitialURL to claim. | |
| 175 | const seen: unknown[] = [] | |
| 176 | const page = loadPage((window) => { | |
| 177 | window.addEventListener('craftReady', () => { | |
| 178 | void window.craft.deepLinks.getInitialURL() | |
| 179 | window.craft.deepLinks.onLink((detail: unknown) => seen.push(detail)) | |
| 180 | }) | |
| 181 | }) | |
| 182 | page.link('app://launch', true) | |
| 183 | await tick() | |
| 184 | ||
| 185 | expect(seen).toEqual([]) | |
| 186 | expect(page.last('getInitialURL').callbackId).toMatch(/^cb_\d+$/) | |
| 187 | }) | |
| 147 | 188 | }) |