← KlugRegister

Documentation

KlugRegister documentation

com.klug.register.KlugRegister

Installation, guides and the complete API for creating QR codes, scanning them with the native camera scanner, and pairing a second device.

AndroidiOSmacOSiPad on Mac Version 1.1.0 Product page · Buy

Introduction

KlugRegister does two things: it creates QR codes from any text, returned as an AIR BitmapData, and it scans QR codes with a native camera scanner and returns the text. Together they let a second device sign in by pointing its camera at the first one, without typing a user name, password or e-mail code.

PlatformCreate · Scan
iOS 13+Yes · full-screen camera scanner with your hint text and cancel button
iPad app on a MacYes · as on iOS, with the Mac's camera
macOS 10.15+Yes · from macOS 13: a scanner sheet on the app window
AndroidYes · Google's code scanner from Play services, no camera permission
Windows, othersNo · no (isSupported and isScanSupported are false)
Extension IDcom.klug.register.KlugRegister
Version1.1.0
ArchitecturesiOS arm64 · macOS arm64 and x86_64 · Android armv7, arm64, x86, x86_64
Deliverycom.klug.register.KlugRegister.ane (the extension), the ActionScript library (SWC) for code completion and compiling, and this documentation.
Packagecom.klug.register: KlugRegister, KlugRegisterEvent

Requirements

AIR SDK51.x (HARMAN)
iOSiOS 13 or later; a camera for scanning
macOSmacOS 10.15 or later for creating codes; macOS 13 or later and a camera for scanning
AndroidAndroid 6.0 (API 23) or later; Google Play services for scanning
PermissionsiOS and macOS: camera, for scanning only. Android: none.
ServerFor pairing: your own backend that issues and redeems one-time codes

Add the extension

Self-contained: no core library ANE and no dependency ANEs are needed. One file, one extensionID.

1 · Copy the ANE and declare it

Copy com.klug.register.KlugRegister.ane into the extension folder of your project, add it to your build (IDE: native extension of the project; command line: -external-library-path for compiling, -extdir for packaging) and declare it:

app.xml
<extensions>
    <extensionID>com.klug.register.KlugRegister</extensionID>
</extensions>

2 · iOS (and the iPad app on the Mac)

Scanning uses the camera, so iOS needs a usage description. Creating codes needs nothing.

app.xml
<iPhone>
    <InfoAdditions><![CDATA[
    <key>NSCameraUsageDescription</key>
    <string>The camera is used to scan the QR code of your other device.</string>
    ]]></InfoAdditions>
</iPhone>

3 · macOS

For scanning on macOS 13 and later, add the usage description, and for a sandboxed app (Mac App Store) the camera entitlement in the entitlements file you sign the app with:

app.xml
<macOS>
    <InfoAdditions><![CDATA[
    <key>NSCameraUsageDescription</key>
    <string>The camera is used to scan the QR code of your other device.</string>
    ]]></InfoAdditions>
</macOS>
entitlements.plist
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>

4 · Android

No permission is needed: Google's code scanner runs in Play services and returns only the result, so the app does not request camera access. The libraries the extension uses (ZXing for creating codes and the Google code scanner) are declared inside the ANE and resolved automatically by the AIR SDK's Gradle build when you package the app; the build machine needs access to Google's Maven repository and Maven Central for that.

Recommended: let Play services download the scanner module when the app is installed, so the first scan works immediately:

app.xml
<android>
    <manifestAdditions><![CDATA[
<manifest>
    <application>
        <!-- download the QR scanner module at install time -->
        <meta-data android:name="com.google.mlkit.vision.DEPENDENCIES"
                   android:value="barcode_ui"/>
    </application>
</manifest>
    ]]></manifestAdditions>
</android>

Getting started

KlugRegister is a singleton. Creating a code is synchronous and returns a BitmapData. Scanning opens the scanner and returns at once; the result arrives as a KlugRegisterEvent.

QuickStart.as
import com.klug.register.KlugRegister;
import com.klug.register.KlugRegisterEvent;
import flash.display.Bitmap;
import flash.display.BitmapData;

var reg:KlugRegister = KlugRegister.getInstance();

// create
var bmp:BitmapData = reg.createQRCode( "https://example.com/hello", 8 );
if ( bmp != null ) addChild( new Bitmap( bmp ) );

// scan
reg.addEventListener( KlugRegisterEvent.SCANNED,  function( e:KlugRegisterEvent ):void { trace( "read: " + e.text ); } );
reg.addEventListener( KlugRegisterEvent.CANCELED, function( e:KlugRegisterEvent ):void { trace( "closed" ); } );
reg.addEventListener( KlugRegisterEvent.ERROR,    function( e:KlugRegisterEvent ):void { trace( e.errorCode, e.errorMessage ); } );

if ( reg.isScanSupported )
    reg.scanQRCode( "Point the camera at the code", "Cancel" );

Guides

Create a QR code

createQRCode(text, modulePx) encodes the text (UTF-8, error correction level M) and draws it black on white, with the 4-module quiet zone scanners need. modulePx is the size of one module in pixels, so the code stays perfectly sharp: the bitmap is (modules + 8) × modulePx pixels square. It returns null for empty text, for text too long for a QR code, or where creating is not supported.

  • Pick modulePx for the size you want: create once with 1 to learn the module count, or simply try a value and scale the Bitmap by whole numbers only.
  • Keep the text short: shorter text gives fewer modules, which scan faster from further away.
  • Leave the white border visible and do not tint the code; scanners need the contrast.
  • Call dispose() on the old BitmapData when you replace a code.
CreateCode.as
import com.klug.register.KlugRegister;
import flash.display.Bitmap;
import flash.display.BitmapData;

var reg:KlugRegister = KlugRegister.getInstance();
var qr:Bitmap;

// draw a code that fits into maxSize pixels, as sharp as possible
function showCode( text:String, maxSize:int ):void
{
    var probe:BitmapData = reg.createQRCode( text, 1 );    // 1 px per module
    if ( probe == null ) { showMessage( "Could not create the code." ); return; }
    var modulePx:int = Math.max( 1, Math.floor( maxSize / probe.width ) );
    probe.dispose();

    if ( qr != null ) { qr.bitmapData.dispose(); removeChild( qr ); }
    qr = new Bitmap( reg.createQRCode( text, modulePx ) );
    qr.x = ( stage.stageWidth - qr.width ) / 2;
    qr.y = ( stage.stageHeight - qr.height ) / 2;
    addChild( qr );
}

showCode( "https://example.com/pair?c=PLACEHOLDER", 600 );

Scan a QR code

scanQRCode(hint, cancelTitle) opens the scanner and returns true when it was started. It returns false when a scanner is already open, and on platforms without the extension (Windows). On macOS older than 13 the call returns true and reports ERROR with nocamera. The scanner closes itself on the first code it reads.

PlatformScanner
iOSFull screen above the app, back camera, your hint at the top and a button with cancelTitle at the bottom ("Cancel" if empty). Follows the interface orientation. Asks for camera permission on first use.
iPad app on a MacAs on iOS, with the Mac's camera.
macOS 13+A sheet on the app window with the live camera image, the hint and a cancel button (also the Esc key). Asks for camera permission on first use.
AndroidGoogle's code scanner with its own localized interface; hint and cancelTitle are not used. No camera permission.
ScanCode.as
import com.klug.register.KlugRegister;
import com.klug.register.KlugRegisterEvent;

var reg:KlugRegister = KlugRegister.getInstance();
reg.addEventListener( KlugRegisterEvent.SCANNED,  onScanned );
reg.addEventListener( KlugRegisterEvent.CANCELED, onScanCanceled );
reg.addEventListener( KlugRegisterEvent.ERROR,    onScanError );

function startScan():void
{
    if ( !reg.isScanSupported ) { showCodeEntry(); return; }   // your app function: type the code
    if ( !reg.scanQRCode( localize( "scan.hint" ), localize( "cancel" ) ) )   // your app function
        trace( "scanner already open" );
}

function onScanned( e:KlugRegisterEvent ):void
{
    handleScannedText( e.text );                  // your app function
}

function onScanCanceled( e:KlugRegisterEvent ):void
{
    // the user closed the scanner: no message
}

function onScanError( e:KlugRegisterEvent ):void
{
    switch ( e.errorCode )
    {
        case "denied":   showMessage( "Allow camera access in the system settings to scan." ); break;
        case "nocamera": showCodeEntry(); break;
        default:         showMessage( "The scanner could not start. Please try again." );   // "scanner", "empty"
    }
}

Camera permission

On iOS and macOS, scanQRCode() handles the permission itself: on first use the system asks the user; if the user allows it, the scanner opens. If access is refused, or was refused before, no scanner opens and ERROR arrives with errorCode == "denied"; the user can only change this in the system settings, so tell them where. On Android no permission is involved.

Pairing a second device

The typical use: a user who is signed in on device A wants to use device B without typing credentials. Device A shows a QR code with a one-time pairing code issued by your server; device B scans it and redeems the code with your server, which signs device B in. The QR code never contains a password or a long-lived token.

StepWhat happens
1 · IssueDevice A (signed in) asks your server for a pairing code. The server creates a random code (at least 128 bits, from a secure random source), stores it bound to the account with a short expiry, for example 2 to 5 minutes, and returns it.
2 · ShowDevice A shows the code as a QR code, with a countdown and a "new code" button. Optionally also as short text for manual entry.
3 · ScanDevice B scans the QR code and checks that the text has your expected format before it uses it.
4 · RedeemDevice B sends the code over HTTPS to your server. The server accepts it only if it exists, is not expired and was not used, marks it used in the same step (atomically), and answers with a new session for device B.
5 · Confirm (optional)For more safety the server first asks device A to confirm ("Sign in on a new device?") and only then completes step 4.
6 · DoneDevice A learns that the code was used (by polling or a push message) and closes the QR screen.

Server rules (in your own backend): single use; short expiry; store only a hash of the code; rate-limit redeem attempts per IP address; log pairings and show the user a list of signed-in devices with a way to remove them. A code on a screen can be photographed, which is why expiry, single use and the optional confirmation matter.

DeviceA_ShowCode.as
import com.klug.register.KlugRegister;
import flash.display.Bitmap;
import flash.events.Event;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.net.URLRequestHeader;
import flash.net.URLRequestMethod;

const PAIR_PREFIX:String = "https://example.com/pair?c=";   // your own format

function showPairingCode():void
{
    var req:URLRequest = new URLRequest( "https://api.example.com/pairing/issue" );
    req.method = URLRequestMethod.POST;
    req.requestHeaders.push( new URLRequestHeader( "Authorization", "Bearer " + mySessionToken() ) );   // your app function
    req.data = "{}";

    var loader:URLLoader = new URLLoader();
    loader.addEventListener( Event.COMPLETE, function( e:Event ):void {
        var answer:Object = JSON.parse( String( loader.data ) );   // e.g. { code: "...", expiresIn: 180 }
        showCode( PAIR_PREFIX + answer.code, 600 );                 // see "Create a QR code"
        startCountdown( answer.expiresIn, showPairingCode );        // your app function: new code on expiry
        waitUntilRedeemed( answer.code, closePairingScreen );       // your app function: poll or push
    });
    loader.load( req );
}
DeviceB_Redeem.as
import com.klug.register.KlugRegisterEvent;
import flash.net.URLVariables;

function onScanned( e:KlugRegisterEvent ):void
{
    if ( e.text.indexOf( PAIR_PREFIX ) != 0 )
    {
        showMessage( "This is not a pairing code." );     // any other QR code
        return;
    }
    var code:String = e.text.substr( PAIR_PREFIX.length );
    if ( !/^[A-Za-z0-9_-]{16,128}$/.test( code ) ) { showMessage( "Invalid code." ); return; }

    var vars:URLVariables = new URLVariables();
    vars.code = code;
    vars.device = myDeviceName();                        // your app function
    var req:URLRequest = new URLRequest( "https://api.example.com/pairing/redeem" );
    req.method = URLRequestMethod.POST;
    req.data = vars;

    var loader:URLLoader = new URLLoader();
    loader.addEventListener( Event.COMPLETE, function( ev:Event ):void {
        var answer:Object = JSON.parse( String( loader.data ) );
        if ( answer.ok ) startSession( answer.session );    // your app function
        else showMessage( "The code has expired. Show a new one on your first device." );
    });
    loader.load( req );
}

Optional: open the app from the phone's camera

If the QR code contains a URL with your app's own URL scheme (for example yourapp://pair?c=…), users can also scan it with the system camera app, which then opens your app. This is not needed for scanQRCode(); it is your app's own configuration. Register the scheme and read the URL from the InvokeEvent:

app.xml
<!-- iOS: inside <iPhone><InfoAdditions> -->
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLSchemes</key>
        <array><string>yourapp</string></array>
    </dict>
</array>

<!-- Android: inside <application> of the manifest additions -->
<activity>
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:scheme="yourapp"/>
    </intent-filter>
</activity>
Invoke.as
import flash.desktop.NativeApplication;
import flash.events.InvokeEvent;

NativeApplication.nativeApplication.addEventListener( InvokeEvent.INVOKE, function( e:InvokeEvent ):void {
    if ( e.arguments.length > 0 && String( e.arguments[ 0 ] ).indexOf( "yourapp://pair" ) == 0 )
        redeemFromUrl( String( e.arguments[ 0 ] ) );   // your app function, same checks as above
});

Background, return and orientation

  • On Android the scanner is a separate system screen; your app may receive Event.DEACTIVATE and Event.ACTIVATE around it. The result arrives after it closes.
  • On iOS the scanner covers the app and follows the interface orientation, in portrait and landscape.
  • The iOS scanner is shown above everything else and has closed when the result event arrives.
  • Every successful start ends with exactly one SCANNED, CANCELED or ERROR event. A false return means no scanner was opened and no event follows.

Cross-platform code

Read isSupported and isScanSupported once when the screen opens and keep the values. isSupported creates a tiny test code each time it is read. Methods never throw: without support, createQRCode() returns null and scanQRCode() returns false.

Capabilities.as
var reg:KlugRegister = KlugRegister.getInstance();
var canShow:Boolean = reg.isSupported;       // Android, iOS, macOS
var canScan:Boolean = reg.isScanSupported;   // Android, iOS, macOS 13+ with a camera

showCodeButton.visible = canShow;
scanButton.visible     = canScan;
typeCodeButton.visible = true;                // always offer manual entry as a fallback

API reference

class KlugRegister

Package com.klug.register · extends flash.events.EventDispatcher · singleton.

MemberDescription
getInstance():KlugRegisterStatic. Returns the single instance and creates it on first use.
KlugRegister()Constructor. Do not call it directly: a second call throws an Error. Use getInstance().
isSupported:BooleanRead-only. True where QR codes can be created: Android, iOS, macOS. False elsewhere. Creates a small test code on every read; keep the result.
isScanSupported:BooleanRead-only. True where the scanner is available: Android, iOS (including the iPad app on a Mac), and macOS 13 or later with a camera.
createQRCode(text:String, modulePx:int = 8):BitmapDataReturns the QR code for text (UTF-8, error correction M) as an opaque BitmapData, black modules on white, with a 4-module quiet zone. modulePx: pixels per module; size = (modules + 8) × modulePx. Returns null if text is null or empty, too long, or not supported. Synchronous. Android, iOS, macOS.
scanQRCode(hint:String = "", cancelTitle:String = ""):BooleanOpens the scanner. hint: text above the camera image; cancelTitle: label of the cancel button, "Cancel" if empty (iOS and macOS; not used on Android). Returns true if the scanner (or, on iOS / macOS, the permission request) was started; false if a scanner is already open or the platform has no native part (Windows). Result: SCANNED, CANCELED or ERROR. Android, iOS, macOS 13+.

class KlugRegisterEvent

Package com.klug.register · extends flash.events.Event · does not bubble, not cancelable. Fields that do not apply are empty strings.

MemberDescription
SCANNED:String = "QR_SCANNED"Static constant. A code was read; see text.
CANCELED:String = "QR_CANCELED"Static constant. The user closed the scanner.
ERROR:String = "QR_ERROR"Static constant. The scanner could not run; see errorCode.
KlugRegisterEvent(type:String)Constructor; created by the extension.
text:StringSCANNED: the content of the code.
errorCode:StringERROR: denied, nocamera, scanner or empty; see Events.
errorMessage:StringERROR: a readable description, for logs.
clone():EventReturns a copy with all fields.

Events

Scanner results are KlugRegisterEvents dispatched on the KlugRegister instance, asynchronously, after the scanner has closed. Creating codes has no events; the result is the return value.

TypeWhen · fields
QR_SCANNEDA QR code was read. text: its content (never empty).
QR_CANCELEDThe user closed the scanner (cancel button, Esc on the Mac, back on Android). No fields.
QR_ERROR · deniediOS, macOS: camera access was refused, now or earlier.
QR_ERROR · nocameraiOS, macOS: no usable camera; on macOS also when the system is older than macOS 13.
QR_ERROR · scannerAndroid: the Google scanner could not run, for example because its module is not downloaded yet or Play services are missing.
QR_ERROR · emptyAndroid: a code was read but contained no text.

Troubleshooting

The iOS app closes when the scanner opens.

NSCameraUsageDescription is missing from the InfoAdditions. iOS ends apps that use the camera without it.

I always get ERROR "denied".

The user refused camera access once; iOS and macOS do not ask again. Explain where to allow it (Settings → your app → Camera on iOS; System Settings → Privacy & Security → Camera on the Mac) and offer manual code entry.

Android: the first scan fails with "scanner".

Google's scanner module is downloaded by Play services on demand. Add the com.google.mlkit.vision.DEPENDENCIES meta-data from Add the extension so it is downloaded at install time, and let the user try again after a moment.

Android: my hint text does not appear.

Expected. Google's scanner has its own localized interface; hint and cancelTitle are used on iOS and macOS only.

macOS: isScanSupported is false.

Scanning needs macOS 13 or later and a camera (built-in or connected). Creating codes works from macOS 10.15.

createQRCode() returns null.

The text is empty or too long for a QR code, or the platform does not support creating codes (Windows). Keep pairing payloads short: a prefix plus a code is plenty.

The code does not scan well.

Show it larger (higher modulePx), keep the white border, raise the screen brightness while it is shown, and shorten the text. Avoid scaling the Bitmap by fractions; use whole-number module sizes.

scanQRCode() returns false.

A scanner is still open from a previous call, or the platform has no native part (Windows). Wait for the event of the open scanner before starting another one.

"KlugRegister is a singleton" error.

Use KlugRegister.getInstance() everywhere instead of new KlugRegister().

Changelog

1.1.0 update · Oct 2026QR scanner on macOS 13 and later: a sheet on the app window with the Mac camera, hint and cancel button.
1.1.0 · Oct 2026Android: create codes and scan with Google's code scanner, without camera permission.
1.0.0 · Oct 2026First release: create and scan QR codes on iOS (also the iPad app on a Mac), create on macOS.