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()
}
}