无线服务器
此软件包提供了执行模块 任何有线 在无服务器的Node.js环境中执行后端 API. 当然,还有服务器应用软件
看我们的 演示应用程序 以实例为例.
结构和组成部分
该软件包提供功能,从JS/TS后端应用程序中与AnyCable服务器合作,并包括支持以下功能:
软件包也与 HTTP 处理器一起处理 任意有线 RPC- over- HTTP 请求和提供 频道 财务报告和审定财务报表 应用程序 用于描述您的应用程序的实时特性的抽象 .
使用量
安装 @anycable/serverless-js 使用您选择的工具的软件包,例如:
npm install @anycable/serverless-js
使用任何有线JWT
任何有线JWT 是一种推荐的认证客户的方法。 要启动,您可以使用标识对象并用它生成认证符:
import { identificator } from "@anycable/serverless-js";
const jwtSecret = "very-secret";
const jwtTTL = "1h";
export const identifier = identificator(jwtSecret, jwtTTL);
// Then, somewhere in your code, generate a token and provide it to the client
const userId = authenticatedUser.id;
const token = await identifier.generateToken({ userId });
const cableURL = `${CABLE_URL}?jid=${token}`
您可以将任何身份证明信息传递给代币. 以后可以用来 频道 (见下文).
已签名流
您可以创建 签名 实例生成签名的流名,并使用 任何有线酒吧/分店数字 :
import { signer } from "@anycable/serverless-js";
const streamsSecret = process.env.ANYCABLE_STREAMS_SECRET;
const sign = signer(secret);
const signedStreamName = sign("room/13");
然后,您可以与客户端使用生成的流名(使用 任何有线JS客户端 SDK:
import { createCable } from "@anycable/web";
const cable = createCable(WEBSOCKET_URL);
const stream = await fetchStreamForRoom("13");
const channel = cable.streamFromSigned(stream);
channel.on("message", (msg) => {
// handle notification
})
广播
改为: 广播 向连接的客户端发送消息,您必须使用广播机实例 :
import { broadcaster } from "@anycable/serverless-js";
// Broadcasting configuration
const broadcastURL =
process.env.ANYCABLE_BROADCAST_URL || "http://127.0.0.1:8090/_broadcast";
const broadcastToken = process.env.ANYCABLE_HTTP_BROADCAST_SECRET || "";
// Create a broadcasting function to send broadcast messages via HTTP API
export const broadcastTo = broadcaster(broadcastURL, broadcastToken);
当AnyCable以单一的应用秘密运行时(ANYCABLE_SECRET),广播终点由从该秘密衍生出来的密钥保护. 使用 broadcastKey 函数以获取它:
import { broadcaster, broadcastKey } from "@anycable/serverless-js";
export const broadcastTo = broadcaster(
broadcastURL,
broadcastKey(process.env.ANYCABLE_SECRET)
);
目前,这个软件包只支持通过HTTP进行广播. 然而,AnyCable提供不同的 广播适配器 (如Redis,NATS等),可以融入自己.
使用频道(任何有线RPC)
一个 应用程序 实例负责处理连接生命周期,并向适当的渠道发送信息.
// api/cable.ts
import {
Application,
ConnectionHandle,
broadcaster,
} from "@anycable/serverless-js";
// Some custom authentication logic
import { verifyToken } from "./auth";
// The identifiers type describe connection identifiers—e.g., user ID, username, etc.
export type CableIdentifiers = {
userId: string;
};
// Application instance handles connection lifecycle events
class CableApplication extends Application<CableIdentifiers> {
// IMPORTANT: When using AnyCable JWT, you don't need to define
// the connect() callback, authentication doesn't hit your server
async connect(handle: ConnectionHandle<CableIdentifiers>) {
const url = handle.env.url;
const params = new URL(url).searchParams;
if (params.has("token")) {
const payload = await verifyToken(params.get("token")!);
if (payload) {
const { userId } = payload;
handle.identifiedBy({ userId });
}
return;
}
// Reject connection if not authenticated
handle.reject();
}
async disconnect(handle: ConnectionHandle<CableIdentifiers>) {
// Here you can perform any cleanup work
console.log(`User ${handle.identifiers!.userId} disconnected`);
}
}
// Create and instance of the class to use in HTTP handlers (see the next section)
const app = new CableApplication();
// Register channels (see below)
export default app;
频道 实例反映了特定特性(例如聊天室、通知等)并负责处理收到的命令和订阅生命周期事件:
import { Channel, ChannelHandle } from "@anycable/serverless-js";
// We re-using the identifiers type from the cable application
import type { CableIdentifiers } from "../cable";
// Define the channel params (used by the client according to Action Cable protocol)
type ChatChannelParams = {
roomId: string;
};
export type ChatMessage = {
id: string;
username: string;
body: string;
createdAt: string;
};
export default class ChatChannel
extends Channel<CableIdentifiers, ChatChannelParams, ChatMessage>
{
// The `subscribed` method is called when the client subscribes to the channel
// You can use it to authorize the subscription and setup streaming
async subscribed(
handle: ChannelHandle<CableIdentifiers>,
params: ChatChannelParams | null,
) {
if (!params) {
handle.reject();
return;
}
if (!params.roomId) {
handle.reject();
return;
}
handle.streamFrom(`room:${params.roomId}`);
}
// This method is called by the client
async sendMessage(
handle: ChannelHandle<CableIdentifiers>,
params: ChatChannelParams,
data: SentMessage,
) {
const { body } = data;
if (!body) {
throw new Error("Body is required");
}
console.log(
`User ${handle.identifiers!.username} sent message: ${data.body}`,
);
const message: ChatMessage = {
id: Math.random().toString(36).substr(2, 9),
username: handle.identifiers!.username,
body,
createdAt: new Date().toISOString(),
};
// Broadcast the message to all subscribers (see below)
await broadcastTo(`room:${params.roomId}`, message);
}
}
// You MUST register a channel instance within the application
// The client MUST use the provided identifier to subscribe to the channel.
app.registerChannel("chat", new ChatChannel());
HTTP 处理器
要用通道粘上HTTP层,我们需要配置HTTP处理器. 下面可以找到一个流行的无服务器平台的例子.
维塞尔
定义 维塞尔 无服务器函数如下 :
// api/anycable/connect/route.ts
import { NextResponse } from "next/server";
import { connectHandler, Status } from "@anycable/serverless-js";
import app from "../../cable";
export async function POST(request: Request) {
try {
const response = await connectHandler(request, app);
return NextResponse.json(response, {
status: 200,
});
} catch (e) {
console.error(e);
return NextResponse.json({
status: Status.ERROR,
error_msg: "Server error",
});
}
}
// api/anycable/command/route.ts
import { NextResponse } from "next/server";
import { commandHandler, Status } from "@anycable/serverless-js";
import app from "../../cable";
export async function POST(request: Request) {
try {
const response = await commandHandler(request, app);
return NextResponse.json(response, {
status: 200,
});
} catch (e) {
console.error(e);
return NextResponse.json({
status: Status.ERROR,
error_msg: "Server error",
});
}
}
// api/anycable/disconnect/route.ts
import { NextResponse } from "next/server";
import { disconnectHandler, Status } from "@anycable/serverless-js";
import app from "../../cable";
export async function POST(request: Request) {
try {
const response = await disconnectHandler(request, app);
return NextResponse.json(response, {
status: 200,
});
} catch (e) {
console.error(e);
return NextResponse.json({
status: Status.ERROR,
error_msg: "Server error",
});
}
}
您也可以使用通用处理器和一些配置来避免重复:
// next.config.js
const nextConfig = {
// ...
rewrites: async () => {
return [
{
source: "/api/anycable/:path*",
destination: "/api/anycable",
},
];
},
};
// ...
然后可以使用以下处理器:
// api/anycable/route.ts
import { NextResponse } from "next/server";
import { handler, Status } from "@anycable/serverless-js";
import app from "../../cable";
export async function POST(request: Request) {
try {
const response = await handler(request, app);
return NextResponse.json(response, {
status: 200,
});
} catch (e) {
console.error(e);
return NextResponse.json({
status: Status.ERROR,
error_msg: "Server error",
});
}
}