Skip to content

Commit

Permalink
Upgrade bindings, add streaming to Browser SDK (#825)
Browse files Browse the repository at this point in the history
* Upgrade bindings

* Remove pinnedFrameUrl from Node SDK

* Remove pinnedFrameUrl from Browser SDK

* Remove pinnedFrameUrl from dev tool

* Add streaming to Browser SDK

* Create slow-rocks-marry.md

* Increase test timeout

* Refactor browser SDK streaming tests
  • Loading branch information
rygine authored Feb 4, 2025
1 parent 80aa5ec commit ec5cd41
Show file tree
Hide file tree
Showing 24 changed files with 829 additions and 313 deletions.
6 changes: 6 additions & 0 deletions .changeset/slow-rocks-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@xmtp/browser-sdk": patch
"@xmtp/node-sdk": patch
---

Upgrade bindings, add streaming to Browser SDK
63 changes: 2 additions & 61 deletions apps/xmtp.chat/src/components/Conversation/ManageConversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ const defaultPolicySet: PolicySet = {
updateGroupNamePolicy: PermissionPolicy.Allow,
updateGroupDescriptionPolicy: PermissionPolicy.Allow,
updateGroupImageUrlSquarePolicy: PermissionPolicy.Allow,
updateGroupPinnedFrameUrlPolicy: PermissionPolicy.Allow,
updateMessageExpirationPolicy: PermissionPolicy.Admin,
updateMessageDisappearingPolicy: PermissionPolicy.Admin,
};

const adminPolicySet: PolicySet = {
Expand All @@ -59,8 +58,7 @@ const adminPolicySet: PolicySet = {
updateGroupNamePolicy: PermissionPolicy.Admin,
updateGroupDescriptionPolicy: PermissionPolicy.Admin,
updateGroupImageUrlSquarePolicy: PermissionPolicy.Admin,
updateGroupPinnedFrameUrlPolicy: PermissionPolicy.Admin,
updateMessageExpirationPolicy: PermissionPolicy.Admin,
updateMessageDisappearingPolicy: PermissionPolicy.Admin,
};

export const ManageConversation: React.FC = () => {
Expand All @@ -87,7 +85,6 @@ export const ManageConversation: React.FC = () => {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [imageUrl, setImageUrl] = useState("");
const [pinnedFrameUrl, setPinnedFrameUrl] = useState("");

const policyTooltip = useMemo(() => {
if (permissionsPolicy === GroupPermissionsOptions.Default) {
Expand Down Expand Up @@ -135,9 +132,6 @@ export const ManageConversation: React.FC = () => {
if (imageUrl !== conversation?.imageUrl) {
await conversation?.updateImageUrl(imageUrl);
}
if (pinnedFrameUrl !== conversation?.pinnedFrameUrl) {
await conversation?.updatePinnedFrameUrl(pinnedFrameUrl);
}
if (addedMembers.length > 0) {
await conversation?.addMembers(addedMembers);
}
Expand Down Expand Up @@ -189,11 +183,6 @@ export const ManageConversation: React.FC = () => {
defaultPolicySet.updateGroupImageUrlSquarePolicy,
MetadataField.ImageUrlSquare,
);
await conversation?.updatePermission(
PermissionUpdateType.UpdateMetadata,
defaultPolicySet.updateGroupPinnedFrameUrlPolicy,
MetadataField.PinnedFrameUrl,
);
break;
}
case GroupPermissionsOptions.AdminOnly: {
Expand Down Expand Up @@ -228,11 +217,6 @@ export const ManageConversation: React.FC = () => {
adminPolicySet.updateGroupImageUrlSquarePolicy,
MetadataField.ImageUrlSquare,
);
await conversation?.updatePermission(
PermissionUpdateType.UpdateMetadata,
adminPolicySet.updateGroupPinnedFrameUrlPolicy,
MetadataField.PinnedFrameUrl,
);
}
}
}
Expand Down Expand Up @@ -268,11 +252,6 @@ export const ManageConversation: React.FC = () => {
policySet.updateGroupImageUrlSquarePolicy,
MetadataField.ImageUrlSquare,
);
await conversation?.updatePermission(
PermissionUpdateType.UpdateMetadata,
policySet.updateGroupPinnedFrameUrlPolicy,
MetadataField.PinnedFrameUrl,
);
}
void navigate(`/conversations/${conversationId}`);
} catch (error) {
Expand All @@ -299,7 +278,6 @@ export const ManageConversation: React.FC = () => {
setName(conversation.name ?? "");
setDescription(conversation.description ?? "");
setImageUrl(conversation.imageUrl ?? "");
setPinnedFrameUrl(conversation.pinnedFrameUrl ?? "");
const consentState = await conversation.consentState();
setConsentState(consentState);
consentStateRef.current = consentState;
Expand Down Expand Up @@ -408,17 +386,6 @@ export const ManageConversation: React.FC = () => {
}}
/>
</Group>
<Group gap="md" align="center" wrap="nowrap">
<Text flex="1 1 40%">Pinned frame URL</Text>
<TextInput
size="md"
flex="1 1 60%"
value={pinnedFrameUrl}
onChange={(event) => {
setPinnedFrameUrl(event.target.value);
}}
/>
</Group>
</Stack>
</Paper>
)}
Expand Down Expand Up @@ -655,32 +622,6 @@ export const ManageConversation: React.FC = () => {
]}
/>
</Group>
<Group gap="md" justify="space-between" align="center">
<Text>Update group pinned frame</Text>
<NativeSelect
disabled={
conversation?.metadata?.conversationType === "dm" ||
permissionsPolicy !==
GroupPermissionsOptions.CustomPolicy
}
value={policySet.updateGroupPinnedFrameUrlPolicy}
onChange={(event) => {
setPolicySet({
...policySet,
updateGroupPinnedFrameUrlPolicy: parseInt(
event.currentTarget.value,
10,
) as PermissionPolicy,
});
}}
data={[
{ value: "0", label: "Everyone" },
{ value: "1", label: "Disabled" },
{ value: "2", label: "Admins only" },
{ value: "3", label: "Super admins only" },
]}
/>
</Group>
</Stack>
</Paper>
<Paper p="md" radius="md" withBorder>
Expand Down
55 changes: 10 additions & 45 deletions apps/xmtp.chat/src/components/Conversation/NewConversation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ export const NewConversation: React.FC = () => {
updateGroupDescriptionPolicy: PermissionPolicy.Allow,
updateGroupImageUrlSquarePolicy: PermissionPolicy.Allow,
updateGroupNamePolicy: PermissionPolicy.Allow,
updateGroupPinnedFrameUrlPolicy: PermissionPolicy.Allow,
updateMessageExpirationPolicy: PermissionPolicy.Allow,
updateMessageDisappearingPolicy: PermissionPolicy.Allow,
});
const [createConversationError, setCreateConversationError] = useState<
string | null
Expand All @@ -61,7 +60,6 @@ export const NewConversation: React.FC = () => {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [imageUrl, setImageUrl] = useState("");
const [pinnedFrameUrl, setPinnedFrameUrl] = useState("");

const policyTooltip = useMemo(() => {
if (permissionsPolicy === GroupPermissionsOptions.Default) {
Expand All @@ -85,8 +83,7 @@ export const NewConversation: React.FC = () => {
updateGroupDescriptionPolicy: PermissionPolicy.Allow,
updateGroupImageUrlSquarePolicy: PermissionPolicy.Allow,
updateGroupNamePolicy: PermissionPolicy.Allow,
updateGroupPinnedFrameUrlPolicy: PermissionPolicy.Allow,
updateMessageExpirationPolicy: PermissionPolicy.Admin,
updateMessageDisappearingPolicy: PermissionPolicy.Admin,
});
} else {
setPolicySet({
Expand All @@ -97,8 +94,7 @@ export const NewConversation: React.FC = () => {
updateGroupDescriptionPolicy: PermissionPolicy.Admin,
updateGroupImageUrlSquarePolicy: PermissionPolicy.Admin,
updateGroupNamePolicy: PermissionPolicy.Admin,
updateGroupPinnedFrameUrlPolicy: PermissionPolicy.Admin,
updateMessageExpirationPolicy: PermissionPolicy.Admin,
updateMessageDisappearingPolicy: PermissionPolicy.Admin,
});
}
}, [permissionsPolicy]);
Expand All @@ -125,14 +121,20 @@ export const NewConversation: React.FC = () => {
: await newGroup(members, {
description,
imageUrlSquare: imageUrl,
pinnedFrameUrl,
name,
permissions: permissionsPolicy,
customPermissionPolicySet:
permissionsPolicy === GroupPermissionsOptions.CustomPolicy
? policySet
: undefined,
});

// this won't happen due to another guard
// TODO: remove once other guard is refactored
if (!conversation) {
return;
}

// automatically sync when creating a group with no members
if (!isDmGroup && members.length === 0) {
await conversation.sync();
Expand Down Expand Up @@ -232,17 +234,6 @@ export const NewConversation: React.FC = () => {
}}
/>
</Group>
<Group gap="md" align="center" wrap="nowrap">
<Text flex="1 1 40%">Pinned frame URL</Text>
<TextInput
flex="1 1 60%"
disabled={isDmGroup}
value={pinnedFrameUrl}
onChange={(event) => {
setPinnedFrameUrl(event.target.value);
}}
/>
</Group>
</Stack>
</Paper>
<Paper p="md" radius="md" withBorder>
Expand Down Expand Up @@ -450,32 +441,6 @@ export const NewConversation: React.FC = () => {
]}
/>
</Group>
<Group gap="md" justify="space-between" align="center">
<Text>Update group pinned frame</Text>
<NativeSelect
disabled={
isDmGroup ||
permissionsPolicy !==
GroupPermissionsOptions.CustomPolicy
}
value={policySet.updateGroupPinnedFrameUrlPolicy}
onChange={(event) => {
setPolicySet({
...policySet,
updateGroupPinnedFrameUrlPolicy: parseInt(
event.currentTarget.value,
10,
) as PermissionPolicy,
});
}}
data={[
{ value: "0", label: "Everyone" },
{ value: "1", label: "Disabled" },
{ value: "2", label: "Admins only" },
{ value: "3", label: "Super admins only" },
]}
/>
</Group>
</Stack>
</Paper>
<Paper p="md" radius="md" withBorder>
Expand Down
2 changes: 1 addition & 1 deletion sdks/browser-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
"@xmtp/content-type-primitives": "^2.0.0",
"@xmtp/content-type-text": "^2.0.0",
"@xmtp/proto": "^3.72.3",
"@xmtp/wasm-bindings": "^0.0.13",
"@xmtp/wasm-bindings": "^0.0.14",
"uuid": "^11.0.3"
},
"devDependencies": {
Expand Down
80 changes: 80 additions & 0 deletions sdks/browser-sdk/src/AsyncStream.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
type ResolveValue<T> = {
value: T | undefined;
done: boolean;
};

type ResolveNext<T> = (resolveValue: ResolveValue<T>) => void;

export type StreamCallback<T> = (
err: Error | null,
value: T | undefined,
) => void | Promise<void>;

export class AsyncStream<T> {
#done = false;
#resolveNext: ResolveNext<T> | null;
#queue: (T | undefined)[];

onReturn: (() => void) | undefined = undefined;

constructor() {
this.#queue = [];
this.#resolveNext = null;
this.#done = false;
}

get isDone() {
return this.#done;
}

callback: StreamCallback<T> = (error, value) => {
if (error) {
throw error;
}

if (this.#done) {
return;
}

if (this.#resolveNext) {
this.#resolveNext({
done: false,
value,
});
this.#resolveNext = null;
} else {
this.#queue.push(value);
}
};

next = (): Promise<ResolveValue<T>> => {
if (this.#queue.length > 0) {
return Promise.resolve({
done: false,
value: this.#queue.shift(),
});
} else if (this.#done) {
return Promise.resolve({
done: true,
value: undefined,
});
} else {
return new Promise((resolve) => {
this.#resolveNext = resolve;
});
}
};

return = (value: T | undefined) => {
this.#done = true;
this.onReturn?.();
return Promise.resolve({
done: true,
value,
});
};

[Symbol.asyncIterator]() {
return this;
}
}
28 changes: 28 additions & 0 deletions sdks/browser-sdk/src/ClientWorkerClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import type {
ClientEventsWorkerMessageData,
ClientSendMessageData,
} from "@/types";
import type {
ClientStreamEvents,
ClientStreamEventsErrorData,
} from "@/types/clientStreamEvents";

const handleError = (event: ErrorEvent) => {
console.error(`Worker error on line ${event.lineno} in "${event.filename}"`);
Expand Down Expand Up @@ -63,6 +67,30 @@ export class ClientWorkerClass {
}
};

// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
handleStreamMessage = <T extends ClientStreamEvents["result"]>(
streamId: string,
callback: (error: Error | null, value: T | null) => void,
) => {
const streamHandler = (
event: MessageEvent<ClientStreamEvents | ClientStreamEventsErrorData>,
) => {
const eventData = event.data;
if (eventData.streamId === streamId) {
if ("error" in eventData) {
callback(new Error(eventData.error), null);
} else {
callback(null, eventData.result as T);
}
}
};
this.#worker.addEventListener("message", streamHandler);

return () => {
this.#worker.removeEventListener("message", streamHandler);
};
};

close() {
this.#worker.removeEventListener("message", this.handleMessage);
this.#worker.removeEventListener("error", handleError);
Expand Down
Loading

0 comments on commit ec5cd41

Please sign in to comment.