Skip to content

Commit

Permalink
Merge branch 'feat/on-chain-graph-sdk' into test/on-chain-graph-sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
kaushalmeena committed Dec 26, 2023
2 parents efb9375 + 15fd592 commit 45b34cc
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 48 deletions.
23 changes: 5 additions & 18 deletions src/lib/apis/sendMessageOnXMTP.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import { Client } from "@xmtp/xmtp-js";
import { BrowserProvider, Eip1193Provider } from "ethers";
import { XMTP_ADDRESS_BATCH_SIZE } from "../constants/xmtp-messaging";
import {
MessagingResult,
ProgressResult,
SendMessageOnXMTPParamsType,
SendMessageOnXMTPReturnType,
SendMessageOnXMTPReturnType
} from "../types/xmtp-messaging";
import { processAddressesViaAirstack } from "../utils/xmtp-messaging";
import { getProcessedAddresses, getXMTPClient } from "../utils/xmtp-messaging";

export async function sendMessageOnXMTP({
message,
addresses,
wallet,
cacheXMTPClient,
abortController,
onProgress,
onComplete,
Expand All @@ -28,24 +27,12 @@ export async function sendMessageOnXMTP({
error: null,
};

let signer = wallet;

if (abortController?.signal?.aborted) {
return resultToReturn;
}

try {
if (!signer) {
if (!("ethereum" in window)) {
throw new Error("Browser based wallet not found");
}
const provider = new BrowserProvider(window.ethereum as Eip1193Provider);
signer = await provider.getSigner();
}

const client = await Client.create(signer, {
env: "production",
});
const client = await getXMTPClient(wallet, cacheXMTPClient);

if (abortController?.signal?.aborted) {
return resultToReturn;
Expand All @@ -72,7 +59,7 @@ export async function sendMessageOnXMTP({
// process addresses using Airstack's XMTPs api:
// 1. resolve identity to address
// 2. check if XMTP is enabled for address
const processedBatch = await processAddressesViaAirstack(
const processedBatch = await getProcessedAddresses(
currentBatch,
abortController
);
Expand Down
23 changes: 11 additions & 12 deletions src/lib/hooks/useSendMessageOnXMTP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ export function useLazySendMessageOnXMTP(

const [loading, setLoading] = useState(false);

const abortControllerRef = useRef<AbortController | null>(null);
const abortControllerRef = useRef<AbortController>();

const hookParamsRef =
useRef<UseLazySendMessageOnXMTPParamsType>(hookParams);
const hookParamsRef = useRef<UseLazySendMessageOnXMTPParamsType>(hookParams);

// store it in refs so that is can be used in callbacks/events
hookParamsRef.current = hookParams;
Expand All @@ -36,13 +35,11 @@ export function useLazySendMessageOnXMTP(
setLoading(true);

// create a new abort controller only if the previous one is aborted
const abortController =
abortControllerRef.current =
!abortControllerRef.current || abortControllerRef.current?.signal?.aborted
? new AbortController()
: abortControllerRef.current;

abortControllerRef.current = abortController;

const result = await sendMessageOnXMTP({
...hookParamsRef.current,
...params,
Expand All @@ -62,14 +59,17 @@ export function useLazySendMessageOnXMTP(
return result;
}, []);

const cancel = useCallback(() => {
const abort = useCallback(() => {
abortControllerRef.current?.abort();
// optimistically stop loading as sendMessageOnXMTP can't be quickly aborted
setLoading(false);
}, []);

const cancel = useCallback(() => {
abort();
setLoading(false); // optimistically stop loading as sendMessageOnXMTP can't be quickly aborted
}, [abort]);

// cleanup, call cancel on unmount
useEffect(() => cancel, [cancel]);
useEffect(() => abort, [abort]);

return [send, { data, progress, error, loading, cancel }];
}
Expand All @@ -82,8 +82,7 @@ export function useSendMessageOnXMTP(

useEffect(() => {
send();
return () => cancel();
}, [cancel, send]);
}, [send]);

return { data, progress, error, loading, cancel };
}
5 changes: 4 additions & 1 deletion src/lib/types/xmtp-messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ export type MessagingResult = {
error?: unknown;
};

export type WalletType = Signer | WalletClient;

export type SendMessageOnXMTPParamsType = {
message: string;
addresses: string[];
wallet?: Signer | WalletClient;
wallet?: WalletType;
cacheXMTPClient?: boolean;
abortController?: AbortController;
onProgress?: (data: ProgressResult) => void;
onComplete?: (data: MessagingResult[]) => void;
Expand Down
44 changes: 44 additions & 0 deletions src/lib/utils/xmtp-messaging/clientCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Client } from "@xmtp/xmtp-js";
import { WalletType } from "../../types/xmtp-messaging";

const CACHE_EXPIRATION = 1000 * 60 * 10; // 10 minutes

type XMTPClientCache = Map<
string,
{
client: Client;
createdAt: number;
}
>;

const clientCache: XMTPClientCache = new Map();

async function getClientCacheKey(wallet: WalletType) {
if ("address" in wallet) {
return wallet.address as string;
}
if ("getAddress" in wallet) {
return wallet.getAddress();
}
}

function isClientCacheValid(createdAt: number) {
return Date.now() - createdAt < CACHE_EXPIRATION;
}

export async function getClientFromCache(wallet: WalletType) {
const key = await getClientCacheKey(wallet);
if (!key) return null;
const cachedData = clientCache.get(key);
if (!cachedData || !isClientCacheValid(cachedData.createdAt)) return null;
return cachedData.client;
}

export async function putClientIntoCache(wallet: WalletType, client: Client) {
const key = await getClientCacheKey(wallet);
if (!key) return null;
clientCache.set(key, {
client,
createdAt: Date.now(),
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const GetXMTPOwnersQuery = `query GetXMTPOwners($owners: [Identity!]) {
// use Airstack's XMTPs api to process addresses:
// 1. resolve identity to address
// 2. check if XMTP is enabled for address
export async function processAddressesViaAirstack(
export async function getProcessedAddresses(
addresses: string[],
abortController?: AbortController
): Promise<ProcessedAddress[]> {
Expand Down
39 changes: 39 additions & 0 deletions src/lib/utils/xmtp-messaging/getXMTPClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Client } from "@xmtp/xmtp-js";
import { BrowserProvider, Eip1193Provider } from 'ethers';
import { WalletType } from "../../types/xmtp-messaging";
import { getClientFromCache, putClientIntoCache } from "./clientCache";

// get browser based (MetaMask, Coinbase etc) wallet
async function getBrowserBasedWallet():Promise<WalletType> {
if (!("ethereum" in window)) {
throw new Error("Browser based wallet not found");
}
const provider = new BrowserProvider(window.ethereum as Eip1193Provider);
return provider.getSigner();
}

// get xmtp client from cache or create one
export async function getXMTPClient(
wallet?: WalletType,
cacheXMTPClient?: boolean
): Promise<Client> {
let xmtpClient;
let userWallet = wallet;
// try to get browser based (MetaMask, Coinbase etc) wallet if wallet is not provided
if (!userWallet) {
userWallet = await getBrowserBasedWallet();
}
// try to get xmtp client from cache if caching is enabled
if (cacheXMTPClient) {
xmtpClient = await getClientFromCache(userWallet);
}
// if cached xmtp client doesn't exist then create one
if (!xmtpClient) {
xmtpClient = await Client.create(userWallet, { env: "production" });
}
// put xmtp client into cache if caching is enabled
if (cacheXMTPClient) {
putClientIntoCache(userWallet, xmtpClient);
}
return xmtpClient;
}
4 changes: 3 additions & 1 deletion src/lib/utils/xmtp-messaging/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from "./processAddressesViaAirstack";
export * from "./clientCache";
export * from "./getProcessedAddresses";
export * from "./getXMTPClient";
34 changes: 19 additions & 15 deletions src/playgrounds/XMTPMessaging/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from "react";
import { init, useLazySendMessageOnXMTP } from "../../lib";
import { Wallet } from "ethers";

init("190fc193f24b34d7cafc3dec305c96b0a", {
env: "dev",
Expand All @@ -21,10 +20,13 @@ const stringify = (obj: unknown) =>
function XMTPMessaging() {
const [messageText, setMessageText] = useState("Hey this is sample message");
const [addressesText, setAddressesText] = useState("gm.xmtp.eth");
const [keyText, setKeyText] = useState("");

const [configOptions, setConfigOptions] = useState({
cacheXMTPClient: true,
});

const [sendMessage, { data, progress, error, loading, cancel }] =
useLazySendMessageOnXMTP({
cacheXMTPClient: configOptions.cacheXMTPClient,
onComplete: (data) =>
console.log("useLazySendMessageOnXMTP:onComplete -", data),
onProgress: (data) =>
Expand All @@ -40,12 +42,9 @@ function XMTPMessaging() {
.filter(Boolean)
.map((x) => x.trim());

const wallet = keyText ? new Wallet(keyText) : undefined;

const result = await sendMessage({
message: messageText,
addresses,
wallet,
});

console.log("sendMessage:returnValue -", result);
Expand All @@ -54,15 +53,6 @@ function XMTPMessaging() {
return (
<div>
<h2>XMTP Messaging playground</h2>
<div>
<h3>Wallet private key (optional)</h3>
<textarea
style={{ minWidth: 840 }}
rows={2}
value={keyText}
onChange={(event) => setKeyText(event.target.value)}
/>
</div>
<div style={{ display: "flex", gap: "2rem" }}>
<div>
<h3>Message</h3>
Expand Down Expand Up @@ -94,6 +84,20 @@ function XMTPMessaging() {
<button type="button" disabled={!loading} onClick={cancel}>
Cancel
</button>
<label style={{ display: "flex", alignItems: "center", gap: "0.5rem", marginLeft: "0.5rem" }}>
Cache XMTP client
<input
type="checkbox"
style={{ transform: "scale(1.5)" }}
checked={configOptions.cacheXMTPClient}
onChange={(event) =>
setConfigOptions((prev) => ({
...prev,
cacheXMTPClient: event.target.checked,
}))
}
/>
</label>
</div>
<h3>Hook Data</h3>
<div>
Expand Down

0 comments on commit 45b34cc

Please sign in to comment.