Seize the day

POST : Android Dev Study

cpp QRCode Login + Android QrCode scanning

CPP에서 Qrcode 생성하기

nayuki/QR-Code-generator: High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, C++, C, Rust. (github.com) 간단히 파일 두 개로 구현된 라이브러리 이용하기 

 

GitHub - nayuki/QR-Code-generator: High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, C++, C, Rust.

High-quality QR Code generator library in Java, TypeScript/JavaScript, Python, C++, C, Rust. - GitHub - nayuki/QR-Code-generator: High-quality QR Code generator library in Java, TypeScript/JavaScri...

github.com

 

오랜만에 MFC 하려니 힘들구나.. 기억이 안나..

void CDlgQrcode::OnPaint() {
	CPaintDC dc(this);

	if (url_.IsEmpty()) {
		return;
	}

	// Manual operation
	CAtlStringA url = dfx::toUtf8(url_.GetBuffer(0));
	QrCode qr1 = QrCode::encodeText(url.GetBuffer(0), QrCode::Ecc::LOW);

	CBrush brushWhite, brushBlack;
	brushWhite.CreateSolidBrush(RGB(255, 255, 255));
	brushBlack.CreateSolidBrush(RGB(0, 0, 0));
	
	const int cellSize = 10;

	for (int y = 0; y < qr1.getSize(); y++) {
		for (int x = 0; x < qr1.getSize(); x++) {
			int start = (x+1) * cellSize; 
			int end = (y+1) * cellSize;
			CRect rc(start, end, start + cellSize, end + cellSize);
			if (qr1.getModule(x, y)) {
				dc.FillRect(&rc, &brushBlack);
			}
			else {
				dc.FillRect(&rc, &brushWhite);
			}
		}
	}
}

 

deno/ oak 서버에서  long polling하면서, Android에서 Qrcode 스캔하여 응답하기를 30초간 기다리기

router
    .get(v1 + "qrcode-login/:code", qrcodeLoginGetMethod)
    .post(v1 + "qrcode-login/:code", qrcodeLoginPostMethod)
    .post(v1 + "qrcode-accept/:code", qrcodeAccept)
    
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
const requestMap = new Map<string, any>()

// Chrome으로 열 경우, 안내 페이지를 연다. 안드로이드 앱의 SchemeActivity를 여는 lt:// link를 제공한다.
export const qrcodeLoginGetMethod = (context: any) => {
    const response = context.response
    const code: string = context.params.code
    if (!isValidQrcode(code)) {
        response.body = "<html><body><h1>Invalid qrcode login</h1></body></html>"
    } else {
        response.body = `<html>
    <body>
        <h1>Qrcode login</h1>
        <br> 
        <button onClick="location.href='lt://qrcode-login/${code}';"><h3>Open app(If you have app)</h3></button></p>
        <button onClick="location.href='https://play.google.com/store/apps/details?id=com.mdiwebma.screenshot';"><h3>Install app</h3></button>
    </body>
</html>`
    }
    response.status = 200    
}


export const qrcodeLoginPostMethod = async (context: any) => {
    const request = context.request
    const ip = request.ip
    console.log('qrcodeLogin ip='+ip)

    const response = context.response        
    const code: string = context.params.code
    
    if (!isValidQrcode(code)) throw new ErrorCode("Invalid Qrcode login", kErrorQrcode)
    
    await qrcodeLoginByLongPolling(code, ip, response)
}

async function qrcodeLoginByLongPolling(code: string, ip: string, response: any) {
    try {
        requestMap.set(code, {ip: ip})

        for(let i = 0; i< 10; i++) {
            await sleep(3000)
            //console.log(`${guid} ${i}`)
            
            const value = requestMap.get(code)
            const encrypted = value.encrypted
            if (typeof encrypted == "boolean") {
                handleResult(response, value)        
                return
            }
        }

        throw new ErrorCode("Qrcode login timeout", kErrorQrcode)
    } catch(err) {
        console.log(err)
        throw new ErrorCode(err.toString(), kErrorQrcode)
    } finally {
        requestMap.delete(code)
    }
}

isValidQrcode() : qrcode는 hash하여, 간단히 자체적으로 validation을 체크한다. 

qrcodeLoginGetMethod() : 안드로이드앱이 아닌, 일반 브라우저에서 Scanning한 경우라면 qrcodeLoginGetMethod 가 호출된다. 적당히 안내를 해서 앱으로 연결시키던지, 앱 다운로드 페이지로 안내한다. 

qrcodeLoginPostMethod() : Cpp 클라이언트에서 연결을 하고 있다가,  안드로이드앱에서 qrcode-accept/{code} 를 호출하여, requestMap에 값을 입력하면 그 값을 cpp 클라이언트에 응답한다. 

 

안드로이드 Qrcode 스캐닝

https://github.com/dm77/barcodescanner 의 라이브러리를 이용한다.

build.gradle

implementation 'me.dm7.barcodescanner:zxing:1.9.13'

 

AndroidManifest.xml

<uses-permission android:name="android.permission.CAMERA" />

<activity
            android:name=".activity.ScannerActivity"
            android:configChanges="orientation|screenSize|keyboardHidden"
            android:screenOrientation="portrait" />
            
<activity
            android:name=".activity.SchemeActivity"
            android:launchMode="singleTask"
            android:theme="@style/AppTheme.Transparent">
            <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="lt" />
            </intent-filter>
            <!--            App link guide -->
            <!--            https://developer.android.com/studio/write/app-link-indexing?hl=ko-->
            <!--            https://developer.android.com/guide/topics/manifest/data-element.html-->
            <!--            <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="https"-->
            <!--                    android:host="api.blabla.com"-->
            <!--                    android:port="443"-->
            <!--                    android:pathPrefix="/lt/v1/qrcode-login/" />-->
            <!--            </intent-filter>-->
</activity>

App link는 OS가 자동적으로 특정 url을 app으로 리다이렉트 시켜주는 것 같다.  다만 서버에 디지털 에셋 링크 파일을 만들어서 업로드해 두어야한다.  https://developer.android.com/studio/write/app-link-indexing?hl=ko   참고

 

ScannerActivity

import android.Manifest
import android.os.Bundle
import com.google.zxing.Result
import com.mdiwebma.base.BaseAppCompatActivity
import com.mdiwebma.base.utils.ToastUtils
import me.dm7.barcodescanner.zxing.ZXingScannerView

class ScannerActivity : BaseAppCompatActivity(0), ZXingScannerView.ResultHandler {

    private var scannerView: ZXingScannerView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        scannerView = ZXingScannerView(this)
        setContentView(scannerView)
        
        checkPermissions(arrayOf(Manifest.permission.CAMERA)) { isAllGranted, _ ->
            if (isAllGranted) {
                scannerView?.setResultHandler(this)
                scannerView?.startCamera()
            } else {
                finish()
            }
        }
    }

    override fun onResume() {
        super.onResume()
        // TODO resumeCamera 등 처리필요        
    }

    override fun onPause() {
        super.onPause()
        scannerView?.stopCamera()
    }

    override fun handleResult(rawResult: Result) {
        if ("/qrcode-login/" in rawResult.text) {
            val nodes = rawResult.text.split("/")
            val code = nodes.last()
            if (code.length != 36) {
                scannerView?.resumeCameraPreview(this)
                ToastUtils.show("hmm??..$code")
                return
            }

            lifecycleScope.launch {
                try {
                    withContext(Dispatchers.IO) {
                        SyncManager().qrcodeAccept(code)
                    }
                } catch (ex: Exception) {
                    ToastUtils.show(ex.toString())
                }
                finish()
            }
        } else {
            // retry
            ToastUtils.show(rawResult.text)
            scannerView?.resumeCameraPreview(this)
        }
    }
}

 

모바일 크롬에서 lt:// 링크를 클릭했을때 앱으로 열리게..
SchemeActivity.kt

class SchemeActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val deepLinkUri = intent.data ?: run {
            finish()
            return
        }
        val url = deepLinkUri.toString()
        if ("/qrcode-login/" in url) {
            // TODO: code 추출
            // jwt 를 만들어서,
            // qrcode-accept/{code} 로 전달..
        }
        finish()
    }
}

 

top

posted at

2021. 8. 29. 00:31


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크