目的是使用Ably Realtime与其他服务结合使用以存储,操纵和共享数据的完整功能集构建聊天应用程序。
如果您有任何疑问,想法或想做出贡献,请提出问题或与我们联系。
节点16安装
NPM的Azure功能运行时。安装此运行:
npm install -g azure-functions-core-tools@4
.env文件./api:
JWT_SIGNING_KEY=key used to sign tokens for users. Can be anything you decide it to be
# Ably
ABLY_API_KEY=YOURKEY:HERE
APP_ID=[YOUR ABLY APP ID](https://faqs.ably.com/how-do-i-find-my-app-id)
CONTROL_KEY=[YOUR ABLY CONTROL KEY](https://ably.com/documentation/control-api#authentication)
# Azure
COSMOS_ENDPOINT=https://yourcosomsdb.documents.azure.com
COSMOS_KEY=ASK FOR THIS OR MAKE YOUR OWN
COSMOS_DATABASE_ID=metadata
AZURE_STORAGE_CONNECTION_STRING=your string here
AZURE_STORAGE_CONTAINER_NAME=container name here
# Auth0
AUTH0_DOMAIN=yourdomain.auth0.com
AUTH0_CLIENTID=yourclientid
AUTH0_REDIRECT_URI=http://localhost:8080/auth0-landing
# from root folder
npm run init # installs node modules for api & integrations
npm run start # runs the dev server如.env文件所示,您需要注册一些服务以使用其当前形式使用该项目。
为了获得明智的凭据,您首先需要注册一个免费的帐户。拥有一个很好的帐户后,您可以转到生成的默认应用程序并获取root API键。这将用于ABLY_API_KEY环境。 APP_ID env变量应设置为API键的第一部分,然后在全速度之前。如果您的API键为12345.jh40fj23jkd0-,32c3-j- ,则应将其设置为12345 。
对于CONTROL_KEY ,用于控制API密钥,应用程序等的Control_Key,您将需要作为登录用户进行访问令牌,然后单击“创建新的访问令牌”。从“帐户”下拉列表中给它一个名称,选择具有您拥有API键的应用程序的帐户,并确保选择read:key和write:key权限”的帐户。创建令牌,并将其值用作CONTROL_KEY 。
COSMOSDB用于存储此应用程序的数据。要获取一个Azure帐户并创建COSMOSDB资源,请按照Azure的设置教程中的步骤进行操作。您应该将COSMOS_ENDPOINT设置为新子帐户中提供的URI。 COSMOS_KEY是COSMOSDB帐户的主要键,您可以按Azure所述访问。
COSMOS_DATABASE_ID是您将在COSMOSDB帐户中使用的容器的名称。
使用为COSMOSDB创建的相同的Azure帐户,创建一个新的数据存储容器。设置该设置后,请转到侧边栏中的“访问键”部分,以获取AZURE_STORAGE_CONNECTION_STRING的connection string ,然后将AZURE_STORAGE_CONTAINER_NAME设置为您命名容器的任何内容。
如果要将auth0作为身份验证方法包括在内,则需要创建一个auth0帐户。拥有一个Auth0帐户后,创建一个应用程序,将“常规Web应用程序”作为应用程序类型。然后,您可以将AUTH0_DOMAIN的域复制为AUTH0_CLIENTID的客户端。 AUTH0_REDIRECT_URI应该指出适当的uri,在对用户进行身份验证后,应重定向到。默认值应适用于本地运行。
在Auth0应用程序的“设置”选项卡上,向下滚动到称为“应用程序URI”的部分。在其中,您应该看到一个“允许回调URL”和“允许注销URL”的字段。对于上下文,使用auth0的网页流量为:
您的网站将用户链接到您的auth0应用程序的登录页面,在该页面中,他们在auth0页面中签名将用户重定向到您的网站的“回调”页面时,当用户要注销时,它们被定向到auth0应用程序的登录页面,然后重定向到“返回”查询页面中指定的页面,
为了避免潜在的滥用和滥用,您需要指定URLS auth0可以重定向到哪些。在本地托管此聊天应用程序时,它托管在Localhost:8080上,因此将允许的回调URL设置为'http:// localhost:8080/auth0-landing'。当用户注销时,我们将将用户重定向回我们的主页,因此将允许的注销URL设置为'http:// localhost:8080/'。
聊天应用程序由以下内容组成:
Archive API接收来自反应堆的活动并保持聊天历史记录Chat Archive存储库的存储存储桶。 React应用程序是默认的单页应用程序。它使用react-router-dom和自定义AppProvider的混合物为应用程序提供安全上下文。
该应用程序使用 @ably-labs/react-hooks与通道相互作用,该应用程序由现代反应功能组件组成。
Snowpack是开发服务器,它将透明地构建生产的ES6代码。
BFF是一个特定应用程序的API,其中包含聊天应用程序的所有Serveride逻辑。由于它托管在Azure静态Web应用程序上,因此我们可以使用azure-functions-core-tools运行API服务器。
除此之外, Azure静态Web应用程序运行时将为我们自动使用API-因此我们不必担心配置托管。 BFF在无服务器的基础上执行,而Azure SWA将自动规模以满足需求。
要添加新的API端点,您将需要在api文件夹中添加新目录。
首先,为新API创建一个目录 - 例如, api/messages 。然后,在新目录中创建一个function.json文件。
{
"bindings" : [
{
"route" : " messages " ,
"authLevel" : " function " ,
"type" : " httpTrigger " ,
"direction" : " in " ,
"name" : " req " ,
"methods" : [ " get " , " post " ]
},
{
"type" : " http " ,
"direction" : " out " ,
"name" : " res "
}
],
"scriptFile" : " ../dist/messages/index.js "
}接下来,您需要创建您的TypeScript API:
import "../startup" ;
import { Context , HttpRequest } from "@azure/functions" ;
export default async function ( context : Context , req : HttpRequest ) : Promise < void > {
context . res = { status : 200 , body : "I'm an API" } ;
}现在,此API将安装在http://localhost:8080/api/messages 。
就是这样!更改代码并为您重建功能时,该工具和SDK将自动检测您的代码。
该应用在Web应用程序和BFF之间使用JWT令牌身份验证。我们在COSMOSDB数据库中存储用户凭据并盐分,单方法密码(使用BCRypt完成)。
当用户进行身份验证时,该应用将签名使用用户的ID和用户名的JWT令牌,然后在随后的认证数据请求中将其发送到BFF。这意味着,在API中使用少量代码,我们可以确保用户是他们声称是谁,并且有权访问API数据。
我们可以将此模型扩展到包括针对应用程序中资源的基于索赔的身份验证的roles集合。
Authenticated User Only API调用我们可以通过使用BFF API中的以下便利方法来创建JWT token身份验证的API调用。
import "../startup" ;
import { Context , HttpRequest } from "@azure/functions" ;
import { authorized , ApiRequestContext } from "../common/ApiRequestContext" ;
export default async function ( context : Context , req : HttpRequest ) : Promise < void > {
await authorized ( context , req , ( ) => {
// This code will only run if the user is authenticated
context . res = {
status : 200 ,
body : JSON . stringify ( "I am validated and authenticated" )
} ;
} ) ;
}如果要作为这些API调用之一的一部分访问已验证的用户信息,则可以执行以下操作:
import "../startup" ;
import { Context , HttpRequest } from "@azure/functions" ;
import { authorized , ApiRequestContext } from "../common/ApiRequestContext" ;
export default async function ( context : Context , req : HttpRequest ) : Promise < void > {
await authorized (
context ,
req ,
( { user } : ApiRequestContext ) => {
// user is the userDetails object retrieved from CosmosDb
context . res = {
status : 200 ,
body : JSON . stringify ( "I am validated and authenticated" )
} ;
} ,
true
) ; // <- true to include the userDetails object in the ApiRequestContext
} 应用内身份验证在AppProviders.jsx中实现。
它提供了一个React Hook,如果用户经过身份验证,则将返回userDetails对象(并确保完全验证用户)。如果未对给定的用户进行身份验证,则在所有情况下,他们都会将其重定向到登录页面。
由于AppProvider负责身份验证,因此您需要使用挂钩访问用户数据,并在任何组件中进行身份验证的API调用。
这是访问当前身份验证用户的userDetails访问对象的示例。
import { useAuth } from "../../AppProviders" ;
const MyComponent = ( ) => {
const { user } = useAuth ( ) ;
return < div > { user . username } </ div > ;
} ;
export default MyComponent ;您还可以访问BffApiClient类的实例,该类别将允许您进行身份验证的API调用,并且已经包含当前已登录的用户JWT token 。
import { useAuth } from "../../AppProviders" ;
const MyComponent = ( ) => {
const { api } = useAuth ( ) ;
const [ channels , setChannels ] = useState ( [ ] ) ;
useEffect ( ( ) => {
const fetchChannels = async ( ) => {
const response = await api . listChannels ( ) ;
setChannels ( response . channels ) ;
} ;
fetchChannels ( ) ;
} , [ ] ) ;
return < div > ... bind channel data here </ div > ;
} ;
export default MyComponent ;上面的示例使用useEffect挂钩在组件安装时获取通道 - API请求是使用useAuth Hook提供的api实例进行的。
这是您应该从组件对BFF进行API调用的唯一方法,因为它可以确保JWT令牌有效且存在。
如果您将新的BFF APIs添加到应用程序中,则需要在/app/src/sdk/BffApiClient.js中实现一个新功能,以使其可用于组件。
这些BffApiClient调用很简单,看起来像这样:
async listChannels ( ) {
const result = await this . get ( "/api/channels" ) ;
return await result . json ( ) ;
}客户端中的某些实用代码将确保在提出请求时存在正确的JWT token 。
我们使用COSMOSDB来存储我们的应用程序元数据,因为它是一个可扩展的,高度可用的托管数据库,我们不必管理自己。它可以以预先处理或无服务器模式运行,在不使用应用程序时(以某些性能为代价),有助于将成本降低。
我们使用单个COSMOSDB数据库存储所有元数据,在内部,我们为我们存储的每种实体创建了一个集合。
例如: User集合,存储我们的用户记录 - 可以使用类似SQL的语法查询。 COSMOSDB通过自动索引JSON文档来简单。
每个存储的元数据实体都有一个id和一个type字段,我们正在使用generic repository类( /api/common/dataaccess/CosmosDbMetadataRepository )来加载和保存这些实体。
对于本地开发,您可以使用云托管版的宇宙版本,也可以使用可用的docker container images之一运行数据库的本地副本。
我们正在使用Ably channels来存储我们的聊天消息,并将事件推向我们的React应用程序。每个连接的用户将接收他们正在实时积极查看的渠道的消息,并且我们使用Channel rewind带来填充最近发送的消息。
收到消息后,可能会corrected这些消息 - 例如,应用亵渎过滤或纠正拼写错误。这些校正消息将是流的一部分,并在React应用程序中追溯应用。 (在后来的史诗中进一步发展)
这种设计使我们能够忍受消耗这些事件的额外API,并在渠道上发布自己的详细说明,以供客户做出回应。
由于事件随着时间的流逝而消失,我们将通过Archive API将每个频道上的入站事件的副本存储到我们的Chat Archive中。
Archive API将接收我们所有频道的反应堆消息,并将其附加到特定于频道的Azure Storage Blobs 。 API将附加到一个文件,直到达到大小阈值(〜500KB),然后创建一个新文件以进行后续消息。
Archive API将在每个通道的Metadata database中维护当前活动存档文件的记录。
Archive API将能够在接收消息并存档以后将其显示在搜索中时更新搜索索引。
测试用jest编写,用ts-jest用来执行APIS TypeScript测试。