p-mazhnik

feat(web): automatically inject js libraries

## Next
Improvements:
* [Web] Automatically inject js libraries
## 3.0.0-beta.4
Fixes:
* Fixes a permission bug on Android where denying the permission would cause an infinite loop of permission requests.
... ...
... ... @@ -53,13 +53,6 @@ Ensure that you granted camera permission in XCode -> Signing & Capabilities:
<img width="696" alt="Screenshot of XCode where Camera is checked" src="https://user-images.githubusercontent.com/24459435/193464115-d76f81d0-6355-4cb2-8bee-538e413a3ad0.png">
### Web
Add this to `web/index.html`:
```html
<script type="text/javascript" src="https://unpkg.com/@zxing/library@0.19.1"></script>
```
## Usage
Import `package:mobile_scanner/mobile_scanner.dart`, and use the widget with or without the controller.
... ...
... ... @@ -28,8 +28,6 @@
<title>example</title>
<link rel="manifest" href="manifest.json">
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
<script type="text/javascript" src="https://unpkg.com/@zxing/library@0.19.1"></script>
</head>
<body>
<!-- This script installs service_worker.js to provide PWA functionality to
... ...
... ... @@ -8,6 +8,7 @@ import 'package:mobile_scanner/mobile_scanner_web.dart';
import 'package:mobile_scanner/src/barcode_utility.dart';
import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/objects/barcode.dart';
import 'package:mobile_scanner/src/web/utils.dart';
/// This plugin is the web implementation of mobile_scanner.
/// It only supports QR codes.
... ... @@ -25,6 +26,8 @@ class MobileScannerWebPlugin {
);
final MobileScannerWebPlugin instance = MobileScannerWebPlugin();
injectJSLibraries(barCodeReader.jsLibraries);
channel.setMethodCallHandler(instance.handleMethodCall);
event.setController(instance.controller);
}
... ...
... ... @@ -7,6 +7,24 @@ import 'package:mobile_scanner/src/enums/camera_facing.dart';
import 'package:mobile_scanner/src/objects/barcode.dart';
import 'package:mobile_scanner/src/web/media.dart';
class JsLibrary {
/// The name of global variable where library is stored.
/// Used to properly import the library if [usesRequireJs] flag is true
final String contextName;
final String url;
/// If js code checks for 'define' variable.
/// E.g. if at the beginning you see code like
/// if (typeof define === "function" && define.amd)
final bool usesRequireJs;
const JsLibrary({
required this.contextName,
required this.url,
required this.usesRequireJs,
});
}
abstract class WebBarcodeReaderBase {
/// Timer used to capture frames to be analyzed
Duration frameInterval = const Duration(milliseconds: 200);
... ... @@ -21,6 +39,9 @@ abstract class WebBarcodeReaderBase {
int get videoWidth;
int get videoHeight;
/// JS libraries to be injected into html page.
List<JsLibrary> get jsLibraries;
/// Starts streaming video
Future<void> start({
required CameraFacing cameraFacing,
... ...
... ... @@ -20,11 +20,14 @@ class Code {
external Uint8ClampedList get binaryData;
}
const jsqrLibrary = JsLibrary(
contextName: 'jsQR',
url: 'https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js',
usesRequireJs: true,
);
/// Barcode reader that uses jsQR library.
/// jsQR supports only QR codes format.
///
/// Include jsQR to your index.html file:
/// <script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
class JsQrCodeReader extends WebBarcodeReaderBase
with InternalStreamCreation, InternalTorchDetection {
JsQrCodeReader({required super.videoContainer});
... ... @@ -33,6 +36,9 @@ class JsQrCodeReader extends WebBarcodeReaderBase
bool get isStarted => localMediaStream != null;
@override
List<JsLibrary> get jsLibraries => [jsqrLibrary];
@override
Future<void> start({
required CameraFacing cameraFacing,
List<BarcodeFormat>? formats,
... ...
import 'dart:async';
import 'dart:html' as html;
import 'dart:js' show context;
import 'package:js/js.dart';
import 'package:mobile_scanner/src/web/base.dart';
Future<void> loadScript(JsLibrary library) async {
// ignore: avoid_dynamic_calls
if (library.usesRequireJs && context['define']?['amd'] != null) {
// see https://github.com/dart-lang/sdk/issues/33979
return loadScriptUsingRequireJS(library.contextName, library.url);
} else {
return loadScriptUsingScriptTag(library.url);
}
}
Future<void> loadScriptUsingScriptTag(String url) {
final script = html.ScriptElement()
..async = true
..defer = false
..crossOrigin = 'anonymous'
..type = 'text/javascript'
// ignore: unsafe_html
..src = url;
html.document.head!.append(script);
return script.onLoad.first;
}
Future<void> loadScriptUsingRequireJS(String packageName, String url) {
final Completer completer = Completer();
final String eventName = '_${packageName}Loaded';
context.callMethod(
'addEventListener',
[eventName, allowInterop((_) => completer.complete())],
);
final script = html.ScriptElement()
..type = 'text/javascript'
..async = false
..defer = false
..text = '''
require(["$url"], (package) => {
window.$packageName = package;
const event = new Event("$eventName");
dispatchEvent(event);
})
''';
html.document.head!.append(script);
return completer.future;
}
/// Injects JS [libraries]
///
/// Returns a [Future] that resolves when all of the `script` tags `onLoad` events trigger.
Future<void> injectJSLibraries(List<JsLibrary> libraries) {
final List<Future<void>> loading = [];
for (final library in libraries) {
final future = loadScript(library);
loading.add(future);
}
return Future.wait(loading);
}
... ...
... ... @@ -168,10 +168,13 @@ extension JsZXingBrowserMultiFormatReaderExt
external MediaStream? stream;
}
const zxingJsLibrary = JsLibrary(
contextName: 'ZXing',
url: 'https://unpkg.com/@zxing/library@0.19.1',
usesRequireJs: true,
);
/// Barcode reader that uses zxing-js library.
///
/// Include zxing-js to your index.html file:
/// <script type="text/javascript" src="https://unpkg.com/@zxing/library@0.19.1"></script>
class ZXingBarcodeReader extends WebBarcodeReaderBase
with InternalStreamCreation, InternalTorchDetection {
JsZXingBrowserMultiFormatReader? _reader;
... ... @@ -182,6 +185,9 @@ class ZXingBarcodeReader extends WebBarcodeReaderBase
bool get isStarted => localMediaStream != null;
@override
List<JsLibrary> get jsLibraries => [zxingJsLibrary];
@override
Future<void> start({
required CameraFacing cameraFacing,
List<BarcodeFormat>? formats,
... ...