Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -2200,48 +2200,3 @@ protected void handleOnNewIntent(Intent intent) {
}
}
}

final class ConsoleMessageEventHelper {

private ConsoleMessageEventHelper() {}

static JSObject create(String webViewId, String level, String message, String source, Integer line, Integer column) {
Map<String, Object> data = createData(webViewId, level, message, source, line, column);
JSObject event = new JSObject();
for (Map.Entry<String, Object> entry : data.entrySet()) {
event.put(entry.getKey(), entry.getValue());
}
return event;
}

static Map<String, Object> createData(String webViewId, String level, String message, String source, Integer line, Integer column) {
Map<String, Object> event = new HashMap<>();
if (webViewId != null && !webViewId.isEmpty()) {
event.put("id", webViewId);
}
event.put("level", normalizeLevel(level));
event.put("message", message != null ? message : "");
if (source != null && !source.isEmpty()) {
event.put("source", source);
}
if (line != null) {
event.put("line", line);
}
if (column != null) {
event.put("column", column);
}
return event;
}

static String normalizeLevel(String level) {
if (level == null) {
return "log";
}

String normalized = level.trim().toLowerCase();
return switch (normalized) {
case "log", "info", "warn", "error", "debug", "assert" -> normalized;
default -> "log";
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ee.forgr.capacitor_inappbrowser;

import com.getcapacitor.JSObject;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

final class ConsoleMessageEventHelper {

private ConsoleMessageEventHelper() {}

static JSObject create(String webViewId, String level, String message, String source, Integer line, Integer column) {
Map<String, Object> data = createData(webViewId, level, message, source, line, column);
JSObject event = new JSObject();
for (Map.Entry<String, Object> entry : data.entrySet()) {
event.put(entry.getKey(), entry.getValue());
}
return event;
}

static Map<String, Object> createData(String webViewId, String level, String message, String source, Integer line, Integer column) {
Map<String, Object> event = new HashMap<>();
if (webViewId != null && !webViewId.isEmpty()) {
event.put("id", webViewId);
}
event.put("level", normalizeLevel(level));
event.put("message", message != null ? message : "");
if (source != null && !source.isEmpty()) {
event.put("source", source);
}
if (line != null) {
event.put("line", line);
}
if (column != null) {
event.put("column", column);
}
return event;
}

static String normalizeLevel(String level) {
if (level == null) {
return "log";
}

String normalized = level.trim().toLowerCase(Locale.ROOT);
return switch (normalized) {
case "log", "info", "warn", "error", "debug" -> normalized;
case "tip" -> "info";
case "warning" -> "warn";
case "assert" -> "error";
default -> "log";
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

public class ConsoleMessageEventHelperTest {

@Test
public void normalizeLevelMapsAndroidMessageLevels() {
assertEquals("info", ConsoleMessageEventHelper.normalizeLevel("TIP"));
assertEquals("warn", ConsoleMessageEventHelper.normalizeLevel("WARNING"));
assertEquals("error", ConsoleMessageEventHelper.normalizeLevel("ASSERT"));
}

@Test
public void createBuildsConsoleEventPayload() {
Map<String, Object> payload = ConsoleMessageEventHelper.createData(
Expand Down
4 changes: 3 additions & 1 deletion example-app/src/js/feature-smoke.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,9 @@ async function verifyStartup(ctx) {
throw new Error("preShowScript did not run before entry script");
}
await waitUntil("consoleMessage", () =>
ctx.consoleMessages.some((event) => String(event.message).includes("feature-smoke-console-ok")),
ctx.consoleMessages.some(
(event) => event.level === "log" && String(event.message).includes("feature-smoke-console-ok"),
),
);
ctx.markStep("page load, proxy, console, preShowScript");
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"verify:ios": "bun run build:proxy-bridge && xcodebuild -scheme CapgoCapacitorInappbrowser -destination generic/platform=iOS",
"verify:android": "cd android && ./gradlew clean build test && cd ..",
"verify:web": "bun run build && bun run test:web",
"test:web": "bun test src/proxy-bridge-support.test.ts src/bundled-asset-support.test.ts src/fullscreen-web.test.ts src/web-open.test.ts",
"test:web": "bun test src/proxy-bridge-support.test.ts src/bundled-asset-support.test.ts src/fullscreen-web.test.ts src/web-console-capture.test.ts src/web-open.test.ts",
"test:maestro:android": "./scripts/test-maestro-android.sh",
"lint": "bun run eslint && bun run prettier -- --check && bun run check:appstore-2-5-2-ios && bun run swiftlint -- lint",
"fmt": "bun run eslint -- --fix && bun run prettier -- --write && bun run swiftlint -- --fix --format",
Expand Down
3 changes: 3 additions & 0 deletions src/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,9 @@ export interface OpenWebViewOptions {
* Emits `consoleMessage` events for JavaScript `console.*` output coming from the managed page.
* Useful when the webview stays hidden and you still need page-level diagnostics.
*
* On web, capture works only when the opened window stays same-origin with the host page so the
* opener can inject the console wrapper. Cross-origin pages ignore this option without error.
*
* @default false
* @since 8.6.0
*/
Expand Down
67 changes: 67 additions & 0 deletions src/web-console-capture-host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { buildConsoleCaptureScript } from './web-console-capture';

export type ConsoleCaptureWindow = Window & { __capgoInAppBrowserConsoleCaptureInstalled?: boolean };

export type ConsoleCaptureHost = {
window: Window;
consoleCaptureInstalledDocument?: Document;
consoleCaptureTimer?: number;
};

export function childDocumentHref(child: Window): string | undefined {
try {
const href = child.location.href;
if (!href || href === 'about:blank') {
return undefined;
}
return href;
} catch {
return undefined;
}
}

export function isConsoleCaptureActive(child: ConsoleCaptureWindow): boolean {
try {
return child.__capgoInAppBrowserConsoleCaptureInstalled === true;
} catch {
return false;
}
}

export function installConsoleCaptureScript(child: Window): void {
if (child.closed || !childDocumentHref(child)) {
return;
}
try {
const script = buildConsoleCaptureScript();
const doc = child.document;
if (!doc) {
return;
}
const target = doc.head ?? doc.documentElement;
if (!target) {
return;
}
const element = doc.createElement('script');
element.text = script;
target.appendChild(element);
element.remove();
} catch {
return;
}
}

export function ensureConsoleCaptureInstalled(host: ConsoleCaptureHost): void {
if (!childDocumentHref(host.window)) {
return;
}
const child = host.window as ConsoleCaptureWindow;
const doc = child.document;
if (isConsoleCaptureActive(child) && host.consoleCaptureInstalledDocument === doc) {
return;
}
installConsoleCaptureScript(host.window);
if (isConsoleCaptureActive(child)) {
host.consoleCaptureInstalledDocument = doc;
}
}
64 changes: 64 additions & 0 deletions src/web-console-capture.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, expect, it } from 'bun:test';

import {
buildConsoleCaptureScript,
CAPGO_CONSOLE_POST_TYPE,
normalizeConsoleLevel,
parseCapgoConsolePostMessage,
} from './web-console-capture';

describe('web console capture', () => {
it('parses console postMessage payloads', () => {
const parsed = parseCapgoConsolePostMessage({
type: CAPGO_CONSOLE_POST_TYPE,
level: 'warn',
message: 'popup blocked',
source: 'https://example.com/app.js',
line: 12,
column: 3,
});
expect(parsed).toEqual({
level: 'warn',
message: 'popup blocked',
source: 'https://example.com/app.js',
line: 12,
column: 3,
});
});

it('ignores unrelated postMessage payloads', () => {
Comment thread
cursor[bot] marked this conversation as resolved.
expect(parseCapgoConsolePostMessage({ type: 'other', message: 'x' })).toBeNull();
expect(parseCapgoConsolePostMessage(null)).toBeNull();
});

it('normalizes Android-style levels', () => {
expect(normalizeConsoleLevel('TIP')).toBe('info');
expect(normalizeConsoleLevel('WARNING')).toBe('warn');
expect(normalizeConsoleLevel('ASSERT')).toBe('error');
expect(normalizeConsoleLevel('unexpected')).toBe('log');
});

it('rejects invalid console payloads', () => {
expect(parseCapgoConsolePostMessage({ type: CAPGO_CONSOLE_POST_TYPE, level: 'log', message: 1 })).toBeNull();
expect(
parseCapgoConsolePostMessage({
type: CAPGO_CONSOLE_POST_TYPE,
level: 'warn',
message: 'ok',
line: '12',
column: '3',
}),
).toEqual({
level: 'warn',
message: 'ok',
line: 12,
column: 3,
});
});

it('builds an injection script with console hooks', () => {
const script = buildConsoleCaptureScript();
expect(script).toContain(CAPGO_CONSOLE_POST_TYPE);
expect(script).toContain('console.assert');
});
});
Loading
Loading