如何使用异步PHP和React Native扫描指纹

This article was peer reviewed by Adedayo Adeniyi. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

本文由Adedayo Adeniyi进行了同行评审。 感谢所有SitePoint的同行评审人员使SitePoint内容达到最佳状态!



We live in interesting times. A short while ago, a company called OfferZen announced a new, programmable credit card. It’s been a long time since I was this excited to get my hands on a piece of tech. My mind has been brimming with ideas ever since.

我们生活在有趣的时代。 不久前,一家名为OfferZen的公司宣布了一种新的可编程信用卡 。 自从我为获得一项技术而激动不已以来已经很长时间了。 从那以后,我的脑子一直充满着想法。

So, I decided to write about one of them!

所以,我决定写其中一个!

I’m going to describe the process of building custom multi-factor authentication for all transactions. I don’t want to do the usual (and boring on its own) SMS or push notification one-time-password stuff. I want to build a fingerprint scanner right into my phone.

我将描述为所有事务构建自定义多因素身份验证的过程。 我不想做普通的(而且很无聊的)SMS或推送通知一次性密码的东西。 我想在手机中内置一个指纹扫描仪。

Fingerprint scan progressing

In this tutorial, we’re going to look at how to set up a simple iOS app using React Native. We will also set up an asynchronous HTTP server, with a web socket connection to the app.

在本教程中,我们将研究如何使用React Native设置一个简单的iOS应用。 我们还将设置一个异步HTTP服务器,并通过Web套接字连接到该应用程序。

We will follow this up by adding fingerprint scanning capabilities to the app, and asking for these fingerprint scans from the HTTP server. Then we will build an endpoint through which GET requests can request a fingerprint scan and wait for one to occur.

我们将通过向该应用程序添加指纹扫描功能,并从HTTP服务器请求这些指纹扫描来进行后续操作。 然后,我们将构建一个端点,通过该端点,GET请求可以请求指纹扫描并等待指纹扫描发生。

There’s a lot to cover, so I’ve chosen to leave the programming of the credit card for another tutorial. This tutorial will be useful on its own, but even better alongside the next one!

有很多要讲的内容,所以我选择将信用卡编程留给其他教程。 本教程本身将非常有用,但与下一个教程一起会更好!

You can find the code for this tutorial here and here. I’ve tested it with PHP 7.1 and the latest version of Google Chrome.

您可以在此处此处找到本教程的代码。 我已经使用PHP 7.1和最新版的Google Chrome浏览器进行了测试。

什么是React Native? (What is React Native?)

If you’ve been building websites for a while, then you’ve probably heard the name React. It’s an interface builder library (among other things). It introduces many new and interesting ideas to the world of front-end development. One of them is that interfaces are easier to build when you think of them one discrete component at a time. Kind of like how one “eats an elephant one bite at a time“.

如果您已经建立网站一段时间,那么您可能已经听说过React这个名字。 这是一个界面构建器库(除其他外)。 它向前端开发领域介绍了许多新颖有趣的想法。 其中之一是,当您一次将它们视为一个离散组件时,接口更易于构建。 有点像一个人“一次吃一口大象 ”。

React Native takes these ideas one step further, by providing a build chain to compile front-end technologies (like HTML, Javascript, and CSS) to native iOS and Android applications.

React Native通过提供构建链来将前端技术(如HTML,Javascript和CSS)编译为本机iOS和Android应用程序,将这些想法进一步向前发展。

Yes, you can build an Android version of this tutorial’s examples. Unfortunately, I only have time to focus on iOS, and you’ll probably have to source the Android-specific third-party libraries (for fingerprint scanning, specifically) yourself.

是的,您可以构建本教程示例的Android版本。 不幸的是,我只有时间专注于iOS,您可能必须自己采购Android专用的第三方库(特别是用于指纹扫描)。

With React Native, it’s possible to write code very similar to what you’d find in a web project, and have it work flawlessly for most smartphone users. So why are we talking about it in the PHP channel? As you’ll see, this platform is so accommodating that a modest amount of Javascript knowledge is enough to build something useful. We don’t need to know Java or Objective-C or Swift!

使用React Native,可以编写与您在Web项目中发现的代码非常相似的代码,并使它对于大多数智能手机用户而言都可以正常工作。 那么,为什么我们要在PHP频道中谈论它呢? 就像您将看到的那样,该平台非常适应,以至于少量的Javascript知识就足以构建有用的东西。 我们不需要了解Java,Objective-C或Swift!

React Native入门 (Getting Started with React Native)

I’m not going to explain the steps to install React Native on every platform. There are already excellent docs for how to do this. For the purposes of this exercise, you’ll need to install XCode, NodeJS, and the react-native command-line tool.

我不会解释在每个平台上安装React Native的步骤。 有关如何执行此操作的已经有出色的文档 。 在本练习中,您需要安装XCode,NodeJS和react-native命令行工具。

If you have the time and/or desire to get the Android emulator going, the same docs will suffice.

如果您有时间和/或渴望使用Android模拟器, 则可以使用相同的文档

The docs include steps to create a new project and run it in the simulator. There’s no point continuing beyond this point if you can’t get the new application to run. If that’s the case, talk to me on Twitter or in the comments.

这些文档包括创建新项目并在模拟器中运行的步骤。 如果您无法运行新的应用程序,那么没有任何必要继续进行下去。 如果是这种情况,请在Twitter或评论中与我联系

安装TouchID (Installing TouchID)

It’s tempting to think that React Native only installs a generalized Javascript API to use, but that’s not the case. One of its best features (in my opinion) is the powerful native module support.

诱人的是,React Native仅安装了要使用的通用Javascript API,但事实并非如此。 (我认为)其最佳功能之一是强大的本机模块支持。

In the case of fingerprint scanning, there is no native Javascript API. But there are a plethora of native modules which provide one. I googled “react native ios fingerprint” and the first match appears to work wonderfully:

对于指纹扫描,没有本地Javascript API。 但是有很多本机模块可以提供一个。 我用Google搜索“React本地ios指纹”,发现第一场比赛表现出色:

yarn add react-native-touch-id
react-native link

You’ll need to quit the simulator and re-run react-native run-ios before the new app will have access to the native module.

您需要先退出模拟器,然后重新运行react-native run-ios然后新应用才能访问本机模块。

The default index.ios.js file is a bit messy, but it is mostly sufficient to try TouchID. After a bit of tidying up, it should look similar to this:

默认的index.ios.js文件有点混乱,但是尝试TouchID几乎足够了。 整理一下后,它应该类似于以下内容:

import React, { Component } from "react";
import { AppRegistry } from "react-native"
import TouchID  from "react-native-touch-id"

class Fingerprints extends Component {
  componentDidMount() {
    TouchID.authenticate("Trying TouchID")
      .then(success => {
        alert("Success")
      })
      .catch(error => {
        alert("Failure")
      })
  }

  render() {
    return null
  }
}

AppRegistry.registerComponent("Fingerprints", () => Fingerprints)

This is from index.ios.js, in the app project

这来自app项目中的index.ios.js

If you’re unsure about the general structure of this, it’s probably a good time to brush up on React development. There are plenty of great courses on the subject, ours included.

如果您不确定它的总体结构,那么这可能是重新编写React开发的好时机。 关于这个主题,有很多很棒的课程包括我们在内

Aside from the React Native boilerplate, we’re importing the TouchID library we installed. We’ve added a componentDidMount method, which is called automatically as this component is rendered. Inside it, we’ve added a call to TouchID.authenticate.

除了React Native样板外,我们TouchID导入我们安装的TouchID库。 我们添加了一个componentDidMount方法,该方法在呈现此组件时自动调用。 在其中,我们添加了对TouchID.authenticate的调用。

By default, this will automatically fail. The simulator is set to not have enrolled fingerprints by default. When you open the app with this new code, you should see the failure message.

默认情况下,这将自动失败。 默认情况下,模拟器设置为没有注册指纹。 当使用此新代码打开应用程序时,应该会看到失败消息。

To change this, go to “Hardware” menu and select “Touch ID” → “Toggle Enrolled State”. Once you refresh (which you can do with ⌘ + R), you should see the prompt to scan your fingerprint. Since the simulator doesn’t have a way to physically do this, head back to the same “Touch ID” menu, and select “Matching Touch”. You should then see the success message.

要更改此设置,请转到“硬件”菜单,然后选择“ Touch ID”→“切换已注册状态”。 刷新后(可以使用⌘+ R键进行刷新),您应该会看到提示来扫描指纹。 由于模拟器无法实际执行此操作,因此请返回相同的“ Touch ID”菜单,然后选择“ Matching Touch”。 然后,您应该看到成功消息。

I found this fascinating to play around with! You can open the ios/Fingerprints.xcodeproj in XCode, and run it on a connected iPhone to see real scans. Just remember to rename Fingerprints to the name of your React Native application.

我发现这很有趣! 您可以在XCode中打开ios/Fingerprints.xcodeproj ,然后在连接的iPhone上运行它以查看真实扫描。 只需记住将Fingerprints重命名为React Native应用程序的名称即可。

创建服务器 (Creating a Server)

On it’s own, this isn’t yet useful. We can simulate a fingerprint scan, but it’s happening automatically and not when needed. We should create a server, so that fingerprints can be requested.

就其本身而言,这还没有用。 我们可以模拟指纹扫描,但它会自动进行,而不是在需要时进行。 我们应该创建一个服务器,以便可以请求指纹。

My favorite kind of PHP server is an async PHP server. I’ve written about this many times before, so feel free to check out more detailed explanations on the topic: Game Development with ReactJS and PHP and Procedurally Generated Game Terrain with ReactJS, PHP, and Websockets.

我最喜欢的一种PHP服务器是异步PHP服务器。 我之前已经写过很多次,所以请随时查看关于该主题的更详细的解释: 使用ReactJS和PHP进行游戏开发以及 使用ReactJS,PHP和Websockets程序生成的游戏地形

I’ll not go too deeply into what things are doing, but feel free to dive into the code for this tutorial to learn about things I don’t mention here.

我不会深入研究正在做什么,但是可以随意学习本教程的代码以了解我在这里没有提到的事情。

To begin with, let’s install Aerys. Our composer.json file could look something like:

首先,让我们安装Aerys。 我们的composer.json文件可能类似于:

{
  "scripts": {
    "dev": "vendor/bin/aerys -d -c loader.php",
    "prod": "vendor/bin/aerys -c loader.php"
  },
  "require": {
    "amphp/aerys": "dev-amp_v2",
    "pre/kitchen-sink": "^0.1.0"
  },
  "autoload": {
    "psr-4": {
      "App\\": "app"
    }
  },
  "config": {
    "process-timeout": 0
  },
  "minimum-stability": "dev",
  "prefer-stable": true
}

This is from composer.json in the server project

这来自服务器项目中的composer.json

We can also set up a placeholder for requesting scans, along with an HTTP GET route to get to it:

我们还可以设置一个占位符来请求扫描,以及一个到达它的HTTP GET路由:

namespace App\Action;

use Aerys\Request;
use Aerys\Response;

class ScanAction
{
  public function __invoke(Request $request, Response $response)
  {
    $response->end("requesting a scan...");
  }
}

This is from app/Action/ScanAction.pre in the server project

这是来自服务器项目中的app/Action/ScanAction.pre

use Aerys\Router;
use App\Action\ScanAction;

return (Router $router) => {
  $router->route(
    "GET", "/scan", new ScanAction
  );
};

This is from routes/web.pre in the server project

这是来自服务器项目中的routes/web.pre

And we can start the server up with a server config file and pre-processor loader script:

我们可以使用服务器配置文件和预处理程序加载器脚本启动服务器:

$port = 8080;

$host = new Aerys\Host();
$host->expose("*", $port);

$host->use($router = Aerys\router());

$web = process .."/routes/web.pre";
$web($router);

exec("echo 'http://127.0.0.1:{$port}' | pbcopy");

This is from config.pre in the server project

这是来自服务器项目中的config.pre

return Pre\processAndRequire(__DIR__ . "/config.pre");

This is from loader.php in the server project

这是来自服务器项目中的loader.php

loader.php applies preprocessor macros and requires config.pre. config.pre creates a new async Aerys server, and loads the routes file. The routes file registers a GET route to the ScanAction class.

loader.php应用预处理器宏,并且需要config.preconfig.pre创建一个新的异步Aerys服务器,并加载路由文件。 路由文件将GET路由注册到ScanAction类。

If you’re wondering whether some of this syntax is standard PHP: it’s not. Those are the preprocessor macros I mentioned, and they do neat things like convert .."/routes/web.pre" to __DIR__ . "/routes/web.pre". Just look at files like config.php and routes/web.php to see the valid PHP syntax that is generated…

如果您想知道某些语法是否为标准PHP:则不是。 这些是我提到的预处理器宏,它们完成了诸如将.."/routes/web.pre"转换为__DIR__ . "/routes/web.pre" __DIR__ . "/routes/web.pre" 。 只需查看config.phproutes/web.php类的文件,即可查看所生成的有效PHP语法。

Now when we go to http://127.0.0.1:8080/scan, we should see the message “requesting a scan…”. This is good. Let’s add web sockets to our server:

现在,当我们转到http://127.0.0.1:8080/scan ,我们应该看到消息“正在请求扫描…”。 很好 让我们向服务器添加Web套接字:

namespace App\Socket;

use Aerys\Request;
use Aerys\Response;
use Aerys\Websocket;
use Aerys\Websocket\Endpoint;
use Aerys\Websocket\Message;

class FingerprintSocket implements Websocket
{
  private $endpoint;
  private $connections = [];

  public function onStart(Endpoint $endpoint)
  {
    $this->endpoint = $endpoint;
  }

  public function onHandshake(Request $request,
    Response $response)
  {
    // $origin = $request->getHeader("origin");
    //
    // if ($origin !== "http://127.0.0.1:8080") {
    //   $response->setStatus(403);
    //   $response->end("origin not allowed");
    //   return null;
    // }

    $info = $request->getConnectionInfo();

    return $info["client_addr"];
  }

  public function onOpen(int $clientId, $address)
  {
    $this->connections[$clientId] = $address;
  }

  public function onData(int $clientId, Message $message)
  {
    $body = yield $message;

    yield $this->endpoint->send(
      $payload, $clientId
    );
  }

  public function onClose(int $clientId,
    int $code, string $reason)
  {
    unset($this->connections[$clientId]);
  }

  public function onStop()
  {
    // nothing to see here...
  }
}

This is from app/Socket/FingerprintSocket.pre in the server project

这是来自服务器项目中的app/Socket/FingerprintSocket.pre

use Aerys\Router;
use App\Action\ScanAction;
use App\Socket\FingerprintSocket;

return (Router $router) => {
  $router->route(
    "GET", "/scan", new ScanAction
  );

  $router->route(
    "GET", "/ws", Aerys\websocket(new FingerprintSocket)
  );
};

This is from routes/web.pre in the server project

这是来自服务器项目中的routes/web.pre

We’ve just registered a new route which will respond to web socket connections and echo messages back. Using the Chrome developer console, in that same /scan endpoint, we can test this out:

我们刚刚注册了一条新路由,该路由将响应Web套接字连接并回显消息。 使用Chrome开发者控制台,在相同的/scan端点中,我们可以对其进行测试:

Testing the web socket

连接到React Native (Connecting to React Native)

Since we can echo messages back through a web socket, it’s probably a good time to try connecting from our app. React Native ships with web socket support, so we can add similar code to the same componentDidMount method:

由于我们可以通过Web套接字回显消息,因此现在是尝试从应用程序进行连接的好时机。 React Native附带了Web套接字支持,因此我们可以将相似的代码添加到相同的componentDidMount方法中:

componentDidMount() {
  // TouchID.authenticate("Trying TouchID")
  //   .then(success => {
  //     alert("Success")
  //   })
  //   .catch(error => {
  //     alert("Failure")
  //   })

  const socket = new WebSocket("ws://127.0.0.1:8080/ws")
  socket.addEventListener("message", e => console.log(e.data))
  socket.addEventListener("open", e => socket.send("hi"))
}

This is from index.ios.js in the app project

这来自应用程序项目中的index.ios.js

To see these console messages, you’ll need to enable remote debugging.

要查看这些控制台消息,您需要启用远程调试。

如何启用远程调试 (How to Enable Remote Debugging)

Press + D and select “Debug Remote JS”. This will open a new Chrome tab. Open the developer tools pane of this new tab, then refresh the app. This should lead to seeing “hi” in the console, as it is echoed back from the server.

+ D并选择“调试远程JS”。 这将打开一个新的Chrome标签。 打开此新选项卡的开发人员工具窗格,然后刷新应用程序。 这应该导致在控制台中看到“ hi”,因为它是从服务器回显的。

请求指纹 (Requesting Fingerprints)

Let’s customize the React Native code to listen for requests to scan fingerprints:

让我们自定义React Native代码以侦听扫描指纹的请求:

componentDidMount() {
  const socket = new WebSocket("ws://127.0.0.1:8080/ws")

  socket.addEventListener("message", (e) => {
    const data = JSON.parse(e.data)

    if (data.type === "scan") {
      TouchID.authenticate("Trying TouchID")
        .then(success => {
          // alert("Success")

          socket.send(JSON.stringify({
            "type": "success",
            "data": success,
            "promise": data.promise
          }))
        })
        .catch(error => {
          // alert("Failure")

          socket.send(JSON.stringify({
            "type": "error",
            "data": error,
            "promise": data.promise
          }))
        })
    }
  })

  // socket.addEventListener("open", e => socket.send("hi"))
}

This is from index.ios.js in the app project

这是来自app项目中的index.ios.js

When a message arrives, we parse it into JSON. If the type is scan, we request a fingerprint scan. Whether the fingerprint scan completes successfully or not, we send a serialized JSON object with details about the event.

当消息到达时,我们将其解析为JSON。 如果类型为scan ,我们请求指纹扫描。 无论指纹扫描是否成功完成,我们都会发送带有事件详细信息的序列化JSON对象。

We also need to adjust the server to deal with this new kind of message:

我们还需要调整服务器以处理这种新的消息:

use Aerys\Router;
use App\Action\ScanAction;
use App\Socket\FingerprintSocket;

$socket = new FingerprintSocket;

return (Router $router) => {
  $router->route(
    "GET", "/scan", new ScanAction($socket)
  );

  $router->route(
    "GET", "/ws", Aerys\websocket($socket)
  );
};

This is from routes/web.pre in the server project

这是来自服务器项目中的routes/web.pre

We provide a reference to the socket class to the action class. This means we can call methods on the socket class in response to HTTP requests:

我们为动作类提供了对套接字类的引用。 这意味着我们可以在套接字类上调用方法以响应HTTP请求:

namespace App\Action;

use Aerys\Request;
use Aerys\Response;
use App\Socket\FingerprintSocket;

class ScanAction
{
  private $socket;

  public function __construct(FingerprintSocket $socket)
  {
    $this->socket = $socket;
  }

  public function __invoke(Request $request, Response $response)
  {
    try {
      yield $this->socket->requestScan();
      $response->end("success!");
    } catch ($e) {
      $response->end("failure!");
    }
  }
}

This is from app/Action/ScanAction.pre in the server project

这是来自服务器项目中的app/Action/ScanAction.pre

When the /scan route is called, we request a new fingerprint scan from the socket class. This is a delayed action (because the app must prompt and wait for the scan), so we can expect to use promises:

调用/scan路由时,我们请求套接字类进行新的指纹扫描。 这是一个延迟的操作(因为该应用程序必须提示并等待扫描),因此我们可以期望使用promises:

private $id = 0;
private $promises = [];

public async function requestScan()
{
  $deferred = new Deferred;
  $this->promises["_{$this->id}"] = $deferred;

  $body = json_encode([
    "type" => "scan",
    "promise" => "_{$this->id}"
  ]);

  $this->id += 1;
  $this->endpoint->broadcast($body);

  return $deferred->promise();
}

public function onData(int $clientId, Message $message)
{
  $body = yield $message;
  $data = json_decode($body);

  $promise = $this->promises[$data->promise];

  if ($data->type === "success") {
    $promise->resolve($data->data);
  } else {
    $promise->fail(
      new \Exception($data->data->message)
    );
  }

  unset($this->promises["_{$this->id}"]);

  // yield $this->endpoint->send(
  //   $body, $clientId
  // );
}

This is from app/Socket/FingerprintSocket.pre in the server project

这是来自服务器项目中的app/Socket/FingerprintSocket.pre

The requestScan method is asynchronous. It returns a Deferred object (which acts like a promise, with resolve and fail methods). We store a reference to the deferred object, and broadcast its identity (and a request to scan) to all connected web socket clients.

requestScan方法是异步的。 它返回一个Deferred对象(其作用类似于promise,带有resolvefail方法)。 我们存储对延迟对象的引用,并将其标识(和扫描请求)广播到所有连接的Web套接字客户端。

We return the deferred reference, so that the action will wait until it is resolved (thanks to the yield keyword). Then, we listen for success or fail messages from the app. We find the related deferred object, and resolve or fail it appropriately.

我们返回延迟的引用,以便该操作将等待直到其解决(由于yield关键字)。 然后,我们会监听来自应用程序的successfail消息。 我们找到相关的延迟对象,并适当地解决它或使它失败。

At that point, the action’s yield statement resolves and we can respond, either successfully or not. Here’s what it looks like, all put together:

到那时,动作的yield语句解析,我们可以成功或失败地做出响应。 放在一起的样子如下:

Demo

摘要 (Summary)

Given how little JavaScript we needed to write, and how cool the interactivity between iOS and the async PHP server is, I think this has been a huge success. We’ve created a way to request fingerprint scans via a simple HTTP GET request. That’s huge on its own, but now think about how we could integrate this during a credit card authorization step!

考虑到我们只需要编写很少JavaScript,以及iOS和异步PHP服务器之间的交互多么酷,我认为这已经取得了巨大的成功。 我们创建了一种通过简单的HTTP GET请求来请求指纹扫描的方法。 它本身是巨大的,但是现在考虑一下如何在信用卡授权步骤中将其集成!

More on this in a follow-up – until then, please let us know what you think in the comments section below!

在后续活动中,请进一步了解–在此之前,请在下面的评论部分中告诉我们您的想法!

翻译自: https://www.sitepoint.com/scan-fingerprints-async-php-react-native/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值