Last active
January 12, 2025 09:11
-
-
Save thomasmassmann/07a687e6e16d39ea6a3bd0694013856a to your computer and use it in GitHub Desktop.
capacitor-barcode-scanner web patch
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Patch for the `@capacitor/barcode-scanner` plugin for web. | |
* This is related to | |
* https://github.com/ionic-team/capacitor-barcode-scanner/issues/50 | |
* | |
* This patch registers a new plugin for Capacitor that provides a fixed | |
* web implementation of the barcode scanner. | |
* | |
* The plugin is named `CapacitorBarcodeScannerPatchWeb` and only contains | |
* the web version. For iOS and Android, the original plugin needs to be used. | |
*/ | |
import type { | |
CapacitorBarcodeScannerOptions, | |
CapacitorBarcodeScannerPlugin, | |
CapacitorBarcodeScannerScanResult, | |
} from "@capacitor/barcode-scanner"; | |
import { | |
CapacitorBarcodeScannerCameraDirection, | |
CapacitorBarcodeScannerScanOrientation, | |
} from "@capacitor/barcode-scanner"; | |
import { registerPlugin } from "@capacitor/core"; | |
import { | |
applyCss, | |
barcodeScannerCss, | |
} from "@/patches/capacitor-barcode-scanner/utils"; | |
/** | |
* Registers the `OSBarcode` plugin with Capacitor. | |
* For web platforms, it applies necessary CSS for the barcode scanner and dynamically imports the web implementation. | |
* This allows for lazy loading of the web code only when needed, optimizing overall bundle size. | |
*/ | |
const CapacitorBarcodeScannerImpl = | |
registerPlugin<CapacitorBarcodeScannerPlugin>( | |
"CapacitorBarcodeScannerPatchWeb", | |
{ | |
web: () => { | |
applyCss(barcodeScannerCss); // Apply the CSS styles necessary for the web implementation of the barcode scanner. | |
return import("./web").then((m) => new m.CapacitorBarcodeScannerWeb()); // Dynamically import the web implementation and instantiate it. | |
}, | |
} | |
); | |
class CapacitorBarcodeScannerPatchWeb { | |
public static async scanBarcode( | |
options: CapacitorBarcodeScannerOptions | |
): Promise<CapacitorBarcodeScannerScanResult> { | |
// Ensure scanInstructions is at least a space. | |
options.scanInstructions = options.scanInstructions || " "; | |
// Set scanButton to false if not provided. | |
options.scanButton = options.scanButton || false; | |
// Ensure scanText is at least a space. | |
options.scanText = options.scanText || " "; | |
// Set cameraDirection to 'BACK' if not provided. | |
options.cameraDirection = | |
options.cameraDirection || CapacitorBarcodeScannerCameraDirection.BACK; | |
options.scanOrientation = | |
options.scanOrientation || | |
// Set scanOrientation to 'ADAPTIVE' if not provided. | |
CapacitorBarcodeScannerScanOrientation.ADAPTIVE; | |
return CapacitorBarcodeScannerImpl.scanBarcode(options); | |
} | |
} | |
export { CapacitorBarcodeScannerPatchWeb }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Device } from "@capacitor/device"; | |
import { | |
CapacitorBarcodeScanner, | |
CapacitorBarcodeScannerTypeHint, | |
} from "@capacitor/barcode-scanner"; | |
/** | |
* Use a patched version of the CapacitorBarcodeScanner for the web until a fix | |
* is available for https://github.com/ionic-team/capacitor-barcode-scanner/issues/50. | |
*/ | |
import { CapacitorBarcodeScannerPatchWeb } from "@/patches/capacitor-barcode-scanner"; | |
import { onMounted, ref } from "vue"; | |
const isPlatformWeb = ref(true); | |
// Code is copied from a utils module | |
const isWeb = async () => { | |
const deviceInfo = await Device.getInfo(); | |
const platform = deviceInfo.platform; | |
return platform === "web"; | |
} | |
onMounted(async () => { | |
// Check for the web platform, so we can use the patched version of the barcode | |
// scanner, if required. | |
isPlatformWeb.value = await isWeb(); | |
}); | |
const handleScanQRCode = async () => { | |
// The default scanner class for ios and android. | |
let scannerClass = CapacitorBarcodeScanner; | |
if (isPlatformWeb.value) { | |
// Patch the web implementation of the barcode scanner. | |
// Can be removed once the issue is fixed in the capacitor plugin. | |
scannerClass = CapacitorBarcodeScannerPatchWeb; | |
} | |
// Your code to scan... | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This file is added only for the path, but taken as-is from the original source. | |
* We need to add this, since @capacitor/barcode-scanner does not export those functions in the dist. | |
*/ | |
/** | |
* Predefined CSS rules for styling barcode scanner components. | |
* Each object in the array defines a CSS rule, with a selector and the CSS properties to apply. | |
*/ | |
export const barcodeScannerCss = [ | |
{ selector: ".scanner-container-display", css: "display: block;" }, | |
{ | |
selector: ".scanner-dialog", | |
css: "display: none; position: fixed; z-index: 999; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4);", | |
}, | |
{ | |
selector: ".scanner-dialog-inner", | |
css: "background-color: #fefefe; margin: 2% auto; padding: 20px; border: 1px solid #888; width: 96%;", | |
}, | |
{ | |
selector: ".close-button", | |
css: "color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer;", | |
}, | |
{ selector: ".close-button:hover", css: "color: #222;" }, | |
{ selector: ".scanner-container-full-width", css: "width: 100%;" }, | |
]; | |
/** | |
* Dynamically applies a set of CSS rules to the document. | |
* If a custom style element with a specific ID does not exist, it is created and appended to the document's head. | |
* Existing rules in the style element are cleared before new ones are applied. | |
* This function supports both modern and older browsers by using `CSSStyleSheet.insertRule` and `textContent` respectively. | |
* | |
* @param {Array<{selector: string, css: string}>} cssRules - An array of objects containing CSS selectors and styles to apply. | |
*/ | |
export function applyCss(cssRules: { selector: string; css: string }[]): void { | |
const styleId = "custom-style-os-cap-barcode"; // Unique identifier for the style element. | |
let styleElement = document.getElementById( | |
styleId | |
) as HTMLStyleElement | null; | |
if (!styleElement) { | |
// Create and append a new style element if it does not exist. | |
styleElement = document.createElement("style"); | |
styleElement.type = "text/css"; | |
styleElement.id = styleId; | |
document.head.appendChild(styleElement); | |
} | |
if (styleElement.sheet) { | |
// Clear existing CSS rules. | |
while (styleElement.sheet.cssRules.length) { | |
styleElement.sheet.deleteRule(0); | |
} | |
// Insert new CSS rules. | |
for (const { selector, css } of cssRules) { | |
styleElement.sheet.insertRule(`${selector} { ${css} }`); | |
} | |
} else { | |
// Fallback for older browsers using textContent. | |
styleElement.textContent = ""; | |
for (const { selector, css } of cssRules) { | |
styleElement.textContent += `${selector} { ${css} }`; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This file contains the actual patch for the web implementation of the barcode scanner. | |
* | |
* It fixes the error check in the HTML5 QR code scanner. | |
*/ | |
import type { Html5QrcodeResult } from "html5-qrcode"; | |
import { WebPlugin } from "@capacitor/core"; | |
import { Html5Qrcode } from "html5-qrcode"; | |
import type { | |
CapacitorBarcodeScannerPlugin, | |
CapacitorBarcodeScannerOptions, | |
CapacitorBarcodeScannerScanResult, | |
} from "@capacitor/barcode-scanner"; | |
import { CapacitorBarcodeScannerScanOrientation } from "@capacitor/barcode-scanner"; | |
/** | |
* Implements OSBarcodePlugin to provide web functionality for barcode scanning. | |
*/ | |
export class CapacitorBarcodeScannerWeb | |
extends WebPlugin | |
implements CapacitorBarcodeScannerPlugin | |
{ | |
/** | |
* Stops the barcode scanner and hides its UI. | |
* @private | |
* @returns {Promise<void>} A promise that resolves when the scanner has stopped and its UI is hidden. | |
*/ | |
private async stopAndHideScanner(): Promise<void> { | |
console.log((window as any).OSBarcodeWebScanner); | |
if ((window as any).OSBarcodeWebScanner) { | |
await (window as any).OSBarcodeWebScanner.stop(); | |
(window as any).OSBarcodeWebScanner = null; | |
} | |
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
document.getElementById( | |
"cap-os-barcode-scanner-container-dialog" | |
)!.style.display = "none"; | |
} | |
/** | |
* Builds the HTML elements necessary for the barcode scanner UI. | |
* This method checks if the scanner container exists before creating it to avoid duplicates. | |
* It also sets up the close button to stop and hide the scanner on click. | |
* @private | |
*/ | |
private buildScannerElement(): void { | |
if (document.getElementById("cap-os-barcode-scanner-container")) { | |
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
document.getElementById("cap-os-barcode-scanner-container")!.className = | |
"scanner-container-display"; | |
return; | |
} | |
// Create and configure scanner container elements | |
const caposbarcodescannercontainer = document.body.appendChild( | |
document.createElement("div") | |
); | |
caposbarcodescannercontainer.id = "cap-os-barcode-scanner-container"; | |
const caposbarcodescannercontainerdialog = document.createElement("div"); | |
caposbarcodescannercontainerdialog.id = | |
"cap-os-barcode-scanner-container-dialog"; | |
caposbarcodescannercontainerdialog.className = "scanner-dialog"; | |
// Inner dialog elements including the close button and scanner view | |
const caposbarcodescannercontainerdialoginner = | |
document.createElement("div"); | |
caposbarcodescannercontainerdialoginner.className = "scanner-dialog-inner"; | |
const caposbarcodescannercontainerdialoginnerclose = | |
document.createElement("span"); | |
caposbarcodescannercontainerdialoginnerclose.className = "close-button"; | |
caposbarcodescannercontainerdialoginnerclose.innerHTML = "×"; | |
caposbarcodescannercontainerdialoginner.appendChild( | |
caposbarcodescannercontainerdialoginnerclose | |
); | |
const caposbarcodescannercontainerdialoginnercontainerp = | |
document.createElement("p"); | |
caposbarcodescannercontainerdialoginnercontainerp.innerHTML = " "; | |
caposbarcodescannercontainerdialoginner.appendChild( | |
caposbarcodescannercontainerdialoginnercontainerp | |
); | |
const caposbarcodescannercontainerdialoginnercontainer = | |
document.createElement("div"); | |
caposbarcodescannercontainerdialoginnercontainer.className = | |
"scanner-container-full-width"; | |
caposbarcodescannercontainerdialoginnercontainer.id = | |
"cap-os-barcode-scanner-container-scanner"; | |
caposbarcodescannercontainerdialoginner.appendChild( | |
caposbarcodescannercontainerdialoginnercontainer | |
); | |
caposbarcodescannercontainerdialog.appendChild( | |
caposbarcodescannercontainerdialoginner | |
); | |
caposbarcodescannercontainer.appendChild( | |
caposbarcodescannercontainerdialog | |
); | |
caposbarcodescannercontainerdialoginnerclose.onclick = | |
this.stopAndHideScanner; | |
} | |
/** | |
* Initiates a barcode scan using the user's camera and HTML5 QR code scanner. | |
* Displays the scanner UI and waits for a scan to complete or fail. | |
* @param {OSBarcodeScanOptions} options Configuration options for the scan, including camera direction and UI preferences. | |
* @returns {Promise<OSBarcodeScanResult>} A promise that resolves with the scan result or rejects with an error. | |
*/ | |
async scanBarcode( | |
options: CapacitorBarcodeScannerOptions | |
): Promise<CapacitorBarcodeScannerScanResult> { | |
this.buildScannerElement(); | |
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
document.getElementById( | |
"cap-os-barcode-scanner-container-dialog" | |
)!.style.display = "block"; | |
return new Promise((resolve, reject) => { | |
const param = { | |
facingMode: options.cameraDirection === 1 ? "environment" : "user", | |
hasScannerButton: false, | |
scanButton: | |
options.scanButton === undefined ? false : options.scanButton, | |
showScanLine: false, | |
scanInstructions: options.scanInstructions | |
? options.scanInstructions | |
: "", | |
orientation: options.scanOrientation | |
? options.scanOrientation | |
: CapacitorBarcodeScannerScanOrientation.PORTRAIT, | |
showCameraSelection: options.web?.showCameraSelection | |
? options.web.showCameraSelection | |
: false, | |
typeHint: options.hint === 17 ? undefined : options.hint, | |
scannerFPS: options.web?.scannerFPS ? options.web.scannerFPS : 50, | |
}; | |
// Set up and start the scanner | |
const scannerElement = document.getElementById( | |
"cap-os-barcode-scanner-container-scanner" | |
); | |
if (!scannerElement) { | |
throw new Error("Scanner Element is required for web"); | |
} | |
(window as any).OSBarcodeWebScanner = new Html5Qrcode(scannerElement.id); | |
const Html5QrcodeConfig = { | |
fps: param.scannerFPS, | |
qrbox: scannerElement.getBoundingClientRect().width * (9 / 16) - 10, | |
aspectRatio: 16 / 9, | |
videoConstraints: { | |
focusMode: "continuous", | |
height: { min: 576, ideal: 1920 }, | |
deviceId: undefined, | |
facingMode: undefined, | |
}, | |
}; | |
// Success and error callbacks for the scanner | |
const OSBarcodeWebScannerSuccessCallback = ( | |
decodedText: string, | |
_decodedResult: Html5QrcodeResult | |
) => { | |
this.stopAndHideScanner(); | |
resolve({ ScanResult: decodedText }); | |
}; | |
const OSBarcodeWebScannerErrorCallback = (error: string) => { | |
if ( | |
/** | |
* =========================================================== | |
* START OF PATCH | |
* =========================================================== | |
*/ | |
error.indexOf("No MultiFormat") === -1 && | |
error.indexOf("No barcode or QR code detected") === -1 | |
/** | |
* =========================================================== | |
* END OF PATCH | |
* =========================================================== | |
*/ | |
) { | |
this.stopAndHideScanner(); | |
console.error(`[Scanner Web Error] ${error}`); | |
reject(error); | |
} | |
}; | |
(window as any).OSBarcodeWebScanner.start( | |
{ facingMode: param.facingMode }, | |
Html5QrcodeConfig, | |
OSBarcodeWebScannerSuccessCallback, | |
OSBarcodeWebScannerErrorCallback | |
); | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment