These ten questions cover the chapter from the WebView's origin to the release build, and each turns on a detail that is easy to get wrong: what the bridge serializes, where Preferences keeps data, how plugins are found, what the version fields say, how links become routes, and what your own plugin rejects. Every snippet ran in the DevTools console of this chapter's BookNest (Remote Debugging Android): a debug build with the versions of Versions and App IDs, freshly installed on the Android 16 emulator, with the questions run in order and the page reloaded before each one. Treat each numbered block as separate. Write down what it prints and why, then check Appendix H, which gives the real output, the reason and the section it comes from.
To run them yourself, build and install the debug APK as in Running on the Emulator, open the app, then open chrome://inspect in desktop Chrome 1 and choose BookNest's WebView (Remote Debugging Android). Your free-space figures and device identifier will differ from the answers; everything else should match on an Android 16 emulator.
Questions
// 1. Where does the page run, and where does a native file appear? (Sections 4.6.1 and 4.6.2)
const info = await Capacitor.Plugins.Device.getInfo();
console.log(location.origin, Capacitor.getPlatform(), Capacitor.isNativePlatform());
console.log(info.webViewVersion === navigator.userAgent.match(/Chrome\/([\d.]+)/)[1]);
console.log(Capacitor.convertFileSrc('file:///data/user/0/com.example.booknest/files/c.jpg'));
console.log(typeof window.cordova, window.isSecureContext, typeof crypto.subtle);
// 2. What comes back from Preferences after a trip across the bridge? (Sections 4.6.5 and 4.7.5)
const { Preferences } = Capacitor.Plugins;
await Preferences.set({ key: 'when', value: new Date(Date.UTC(2026, 8, 25)) });
await Preferences.set({ key: 'price', value: 24.0 });
await Preferences.set({ key: 'cart', value: [1, 4] });
for (const key of ['when', 'price', 'cart', 'nothing']) {
console.log(key, JSON.stringify(await Preferences.get({ key })));
}
await Preferences.remove({ key: 'when' });
console.log(JSON.stringify(await Preferences.get({ key: 'when' })));
// 3. Preferences now holds lastBookId. What do the page's own stores say? (Sections 4.7.5, 4.12.2)
await Capacitor.Plugins.Preferences.set({ key: 'lastBookId', value: '3' });
const ls = (key) => localStorage.getItem(key);
console.log(ls('lastBookId'), ls('CapacitorStorage.lastBookId'));
console.log((await navigator.serviceWorker.getRegistrations()).length);
console.log((await Capacitor.Plugins.Preferences.keys()).keys.sort().join(' '));
// 4. Which plugins can the page call, and how does an unknown method fail? (Sections 4.6.4, 4.17.4)
for (const name of ['Camera', 'DiskSpace', 'BarcodeScanner']) {
console.log(name, Capacitor.isPluginAvailable(name));
}
console.log(typeof Capacitor.Plugins.BarcodeScanner, typeof Capacitor.Plugins.Camera.zoom);
console.log(JSON.stringify(await Capacitor.Plugins.Camera.checkPermissions()));
try { await Capacitor.Plugins.Camera.zoom({ level: 2 }); }
catch (e) { console.log(e.code, e.message); }// 5. After Section 4.18.1's change to build.gradle, what does App report? (Sections 4.7.7, 4.18.1)
const { id, version, build } = await Capacitor.Plugins.App.getInfo();
console.log(id, version, build, typeof build);
console.log(JSON.stringify(await Capacitor.Plugins.App.getState()));
const { identifier } = await Capacitor.Plugins.Device.getId();
console.log(identifier.length, /^[0-9a-f]+$/.test(identifier));
// 6. Which route does each link open? (Section 4.15.4)
for (const url of ['booknest://books/5/?tab=notes', 'booknest://lab', 'BookNest://Books/5/',
'https://booknest.example.com/books/3/#notes', 'https://evil.example.org/books/1/']) {
console.log(url, '->', lab.linkToRoute(url));
}
// 7. What do DiskSpace's two methods make of these calls? (Sections 4.10.4 and 4.10.5)
const { freeBytes, totalBytes } = await Capacitor.Plugins.DiskSpace.getSpace();
console.log(freeBytes < totalBytes, Number.isSafeInteger(totalBytes), totalBytes > 2 ** 31);
for (const bytes of [3_000_000_000, 1e15, '25', -1, undefined]) {
try {
const { fits } = await Capacitor.Plugins.DiskSpace.canFit({ bytes });
console.log(String(bytes), 'fits:', fits);
} catch (e) { console.log(String(bytes), e.code); }
}// 8. No live update has been downloaded. Which bundle is running? (Sections 4.14.2 and 4.18.1)
const { LiveUpdate } = Capacitor.Plugins;
console.log(JSON.stringify(await LiveUpdate.getCurrentBundle()));
console.log(JSON.stringify(await LiveUpdate.getBundles()), lab.WEB_BUNDLE);
console.log(JSON.stringify(await LiveUpdate.getVersionCode()));
console.log(JSON.stringify(await LiveUpdate.getVersionName()));
console.log(JSON.stringify(await LiveUpdate.ready()));
// 9. A dialog, then a routed popup. What does each Back-button check return? (Section 4.12.5)
const wait = (ms) => new Promise((done) => setTimeout(done, ms));
app.dialog.alert('Saved to your notes');
await wait(500);
console.log(lab.closeModal(app), app.views.current.router.url);
await wait(500);
console.log(lab.closeModal(app), document.querySelectorAll('.modal-in').length);
app.views.current.router.navigate('/about/');
await wait(800);
console.log(lab.closeModal(app), app.views.current.router.url);
// 10. Which methods does the bridge advertise for your plugin, and how? (Sections 4.6.4, 4.10.4)
const header = Capacitor.PluginHeaders.find((h) => h.name === 'DiskSpace');
console.log(header.methods.map((m) => `${m.name}:${m.rtype ?? 'callback'}`).join(' '));
const names = Capacitor.PluginHeaders.map((h) => h.name);
console.log(names.length, names.slice(0, 4).join(' '), names.includes('Cordova'));