fix(native): keep the iOS offline copy inside the app

loadSimulatedRequest went to the server URL without its trailing slash, and
Capacitor keeps a navigation in the WebView only when it starts with the
bridge's serverURL, slash included. The offline start page would have
opened in Safari instead. Also covers the platform helpers and the
hand-off API module.
This commit is contained in:
Maurice
2026-09-24 12:48:12 +02:00
parent 293b25466e
commit be211c22ed
3 changed files with 56 additions and 1 deletions
+4 -1
View File
@@ -121,7 +121,10 @@ public class TrekShellPlugin: CAPPlugin, CAPBridgedPlugin, ASWebAuthenticationPr
}
@objc func openOfflineCopy(_ call: CAPPluginCall) {
guard let raw = TrekServerStore.url, let url = URL(string: raw),
// With the trailing slash, like the bridge's serverURL: Capacitor only
// keeps a navigation in the app when it starts with that exact string,
// and hands anything else to Safari.
guard let raw = TrekServerStore.url, let url = URL(string: raw.hasSuffix("/") ? raw : raw + "/"),
let html = try? Data(contentsOf: TrekServerStore.offlineCopyURL) else {
call.resolve(["opened": false])
return
+19
View File
@@ -0,0 +1,19 @@
import { describe, it, expect, vi } from 'vitest'
import { nativeAuthApi } from './nativeAuth'
import { apiClient } from './client'
vi.mock('./client', () => ({ apiClient: { post: vi.fn() } }))
describe('nativeAuthApi', () => {
it('mints a hand-off code for a challenge', async () => {
vi.mocked(apiClient.post).mockResolvedValue({ data: { code: 'c1' } })
await expect(nativeAuthApi.handoff('ab'.repeat(32))).resolves.toEqual({ code: 'c1' })
expect(apiClient.post).toHaveBeenCalledWith('/auth/native/handoff', { challenge: 'ab'.repeat(32) })
})
it('redeems the code with its verifier', async () => {
vi.mocked(apiClient.post).mockResolvedValue({ data: { ok: true } })
await expect(nativeAuthApi.exchange({ code: 'c1', verifier: 'v'.repeat(64) })).resolves.toBeUndefined()
expect(apiClient.post).toHaveBeenCalledWith('/auth/native/exchange', { code: 'c1', verifier: 'v'.repeat(64) })
})
})
+33
View File
@@ -0,0 +1,33 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { Capacitor } from '@capacitor/core'
import { isNativeApp, isNativeIos, nativeServerHost } from './platform'
vi.mock('@capacitor/core', () => ({ Capacitor: { isNativePlatform: vi.fn(), getPlatform: vi.fn() } }))
beforeEach(() => {
vi.mocked(Capacitor.isNativePlatform).mockReturnValue(true)
vi.mocked(Capacitor.getPlatform).mockReturnValue('ios')
})
describe('platform', () => {
it('tells the app from a browser', () => {
expect(isNativeApp()).toBe(true)
vi.mocked(Capacitor.isNativePlatform).mockReturnValue(false)
expect(isNativeApp()).toBe(false)
})
it('narrows to iOS', () => {
expect(isNativeIos()).toBe(true)
vi.mocked(Capacitor.getPlatform).mockReturnValue('android')
expect(isNativeIos()).toBe(false)
vi.mocked(Capacitor.getPlatform).mockReturnValue('ios')
vi.mocked(Capacitor.isNativePlatform).mockReturnValue(false)
expect(isNativeIos()).toBe(false)
})
it('names the server the WebView sits on, only inside the app', () => {
expect(nativeServerHost()).toBe(window.location.host)
vi.mocked(Capacitor.isNativePlatform).mockReturnValue(false)
expect(nativeServerHost()).toBeNull()
})
})