アカウントの同時使用デバイスの追跡
アクセス管理、セキュリティの確保、リソースの最適化が最優先されるアプリケーションでは、アカウントごとの同時デバイスやストリームの追跡が不可欠です。サブスクリプション・ベースのサービスでは、デバイスまたはストリームの制限を実施することで、コンテンツへの同時アクセスを指定されたデバイス数に制限します。通貨追跡はまた、不正な共有を防止することでアカウントのセキュリティをサポートし、潜在的な悪用からサービスを保護します。これはまた、リアルタイムの需要に基づいて動的にリソースを管理することにより、特にメディアやエンターテイメントなど、トラフィックの多いアプリケーションにおけるリソースの最適化を可能にします。
以下に示すパターンは、Momentoが複雑なインフラストラクチャを必要とせずにリアルタイム・セッション・モニタリングを提供する方法を示しています。
概要
Momentoで同時実行を監視するには、接続されたプレイヤーから発信されるheartbeatsに依存します。サーバーコンポーネントは、Momentoのキャッシュ辞書を管理し、指定された間隔で一意のプレーヤーからのハートビートを追跡します。エンタイトルメントチェックの間に、最後の完全なインターバル辞書がフェッチされ、同時実行カウントが決定さ れます。
同時実行追跡の主なコンポーネントは以下の通りです:
- Device - 各デバイスまたはストリームは、Momento Topicsを介してハートビートを送信します。
- Momento
- Cache - 各アカウントの最近のハートビートを、インターバルベースのキャッシュ辞書に格納します。
- Auth - トークンにアカウントIDを直接エンコードして、プレイヤー用のセッショントークンを作成します。
- Account - システムのユーザーアカウントを表します。
同時実行トラッカーの構築
このパターンには4つのコンポーネントが必要です:
- トークン自動販売機
- デバイス・ハートビート
- ハートビートハンドラ
- 同時実行チェッカー
トークン自動販売機
トークン自動販売機は、限定されたパーミッションを持つ短命のセッショントークンを払い出すパターンです。これはサーバー側のコンポーネントで、通常はAPIエンドポイントで、動的にトークンを生成します。以下は、セッショントークンを生成するために使用されるコードのスニペットです。このコードはAPIエンドポイントハンドラの中に記述する必要があります。
- Node.js
- Go
- .NET
const scope = { permissions: [
{
role: 'publishonly',
cache: 'video',
topic: 'heartbeat'
}
]};
const response = await authClient.generateDisposableToken(scope, ExpiresIn.minutes(30), { tokenId: accountId });
if(response.type === GenerateDisposableTokenResponse.Success){
return { token: response.authToken };
}
resp, err := authClient.GenerateDisposableToken(ctx, &momento.GenerateDisposableTokenRequest{
ExpiresIn: utils.ExpiresInMinutes(30),
Scope: momento.TopicPublishOnly(
momento.CacheName{Name: "video"},
momento.TopicName{Name: "heartbeat"},
),
Props: momento.DisposableTokenProps{
TokenId: &req.PlayerID,
},
})
if err != nil {
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
return
}
switch r := resp.(type) {
case *auth_resp.GenerateDisposableTokenSuccess:
return r.ApiKey
default:
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
}
var response = await _authClient.GenerateDispableTokenAsync(
DisposableTokenScopes.TopicPublishOnly("video", "heartbeat"),
ExpiresIn.Minutes(30)
);
return response.AuthToken;
上のスニペットでは、ユーザが heartbeat
トピックにメッセージを 発行 できるように明示的なパーミッションを設定しています。これは、プレイヤーのハートビートがハンドラと通信する方法です。トークンは30分間有効で、ユーザーの accountId
がトークンに埋め込まれています。埋め込まれたアカウントIDは、サーバ上のハートビート購読の引数として表示されます。
実運用シナリオでは、このコードは既存の authZ メカニズムに組み込まれ、生成されたトークンを claim として返すかもしれません。ここでは、上記のコードスニペットの前に、ユーザが認証され、そのアカウント ID にアクセスでき、そのユーザが見ようとしているコンテンツを安全に識別できていることを前提としています。
デバイス・ハートビート
トークン自動販売機が設置されたので、これをデバイスで使用して、定期的にハートビートを発行することができます。ハートビートには、ユースケースに応じて、メディア、プレーヤー、デバイスに関するあらゆる情報を含めることができます。この簡単なウォークスルーでは、最小限の情報を提供し、デバイスIDのみを含めることにします。
- Momento Web SDK (React)
- HTTP only
import React, { useEffect, useState, useMemo } from 'react';
import ReactDOM from 'react-dom/client';
import { TopicClient, CredentialProvider } from '@gomomento/sdk-web';
const HEARTBEAT_INTERVAL_MS = 5000;
function getMediaIdFromQuery() {
const params = new URLSearchParams(window.location.search);
return params.get('id');
}
function Device() {
const [topicClient, setTopicClient] = useState(null);
const mediaId = useMemo(() => getMediaIdFromQuery(), []);
const deviceId = useMemo(() => {
const savedDeviceId = localStorage.getItem('deviceId');
if (savedDeviceId) return savedDeviceId;
const newDeviceId = crypto.randomUUID();
localStorage.setItem('deviceId', newDeviceId);
return newDeviceId;
}, []);
const message = useMemo(() => JSON.stringify({ deviceId, mediaId }), [deviceId, mediaId]);
useEffect(() => {
async function initTopicClient() {
const response = await fetch('http://localhost:3000/tokens', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ accountId: 'account-id' }),
});
if (response.ok) {
const { token } = await response.json();
const topicClient = new TopicClient({
credentialProvider: CredentialProvider.fromString(token),
});
setTopicClient(topicClient);
}
}
initTopicClient();
}, [mediaId]);
useEffect(() => {
if (topicClient) {
const intervalId = setInterval(() => {
topicClient.publish('video', 'heartbeat', message);
}, HEARTBEAT_INTERVAL_MS);
return () => clearInterval(intervalId);
}
}, [topicClient, mediaId, message]);
return (
<div>
<h2>Device {deviceId}: {topicClient ? 'Connected' : 'Not Connected'}</h2>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Device />);
<!DOCTYPE html>
<html lang="en">
<body>
<div id="root">
<h2>Device <span id="deviceId"></span>: <span id="status">Not Connected</span></h2>
</div>
<script>
const HEARTBEAT_INTERVAL_MS = 5000;
const mediaId = getMediaIdFromQuery();
const deviceId = getDeviceId();
const message = JSON.stringify({ deviceId, mediaId });
let token;
function getMediaIdFromQuery() {
const params = new URLSearchParams(window.location.search);
return params.get('id');
}
function getDeviceId() {
let deviceId = localStorage.getItem('deviceId');
if (!deviceId) {
deviceId = crypto.randomUUID();
localStorage.setItem('deviceId', deviceId);
}
document.getElementById('deviceId').innerText = deviceId;
return deviceId;
}
async function sendHeartbeat() {
await fetch(`<MOMENTO_REGION_ENDPOINT>/topics/video/heartbeat`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': token
},
body: message,
});
}
async function getToken() {
const response = await fetch('http://localhost:3000/tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accountId: 'account-id' }),
});
if (response.ok) {
const { token } = await response.json();
return token;
}
}
function startHeartbeat() {
setInterval(() => {
sendHeartbeat();
}, HEARTBEAT_INTERVAL_MS);
}
async function init() {
const statusElement = document.getElementById('status');
token = await getToken();
if (token) {
statusElement.innerText = 'Connected';
startHeartbeat();
} else {
statusElement.innerText = 'Failed to Connect';
}
}
init();
</script>
</body>
</html>
上記の例では、プレーヤhtmlにはハートビート・ロジックしか含まれていません。トークンを取得するために、ローカルで実行されているAPIエンドポイントの後ろに置いたステップ1のトークン自動販売機を呼び出します。プレーヤーがトークンを取得すると、デバイスIDとメディアIDをheartbeat
トピックに公開し始めます。ハートビートは5秒ごとに送信されるので、ハートビートハンドラーはアクティブなインスタンスを追跡できます。
デバイスのハートビートのコードで注意すべき点が2つあります:
- トークン自動販売機に供給されるアカウントIDはハードコードされています。
- Momento HTTP APIを呼び出す場合、ベースURLはregion basedです。プレースホルダを、使用するケースに適したリージョンのエンドポイントに置き換えてください。Momento SDKを使用する場合、リージョン処理は管理されます。
メダル自動販売機の完全な例については、このチュートリアルをご覧ください.
ハートビートハンドラ
特定のアカウントのデバイスは、一連のキャッシュ辞書で追跡されます。一意のキャッシュ・ディクショナリは、指定された時間間隔にわたってデバイスのハートビートを追跡するために使用されます。時間間隔は、ビジネス要件に応じて変えることができます。この例では、コンカレンシーを1分に1回評価します。
インターバル・ベースの辞書の命名規則は {accountId}-{intervalTime}
です。インターバル時間を計算するには、指定された分の時間を数値で表し、切り捨てます。
- Node.js
- Go
- .NET
function getIntervalMarker(minutesBack = 0) {
const now = new Date();
now.setTime(now.getTime() - minutesBack * 60000);
now.setSeconds(0, 0);
return now.getTime();
}
func getIntervalMarker(minutesBack int) int64 {
now := time.Now().Add(-time.Duration(minutesBack) * time.Minute)
rounded := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), 0, 0, now.Location())
return rounded.UnixNano() / int64(time.Millisecond)
}
static long GetIntervalMarker(int minutesBack = 0)
{
DateTime now = DateTime.UtcNow.AddMinutes(-minutesBack);
now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, 0, DateTimeKind.Utc);
return new DateTimeOffset(now).ToUnixTimeMilliseconds();
}