@streamplace/components 0.0.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/README.md +35 -0
- package/dist/components/chat/chat-box.js +109 -0
- package/dist/components/chat/chat-message.js +76 -0
- package/dist/components/chat/chat.js +56 -0
- package/dist/components/chat/mention-suggestions.js +39 -0
- package/dist/components/chat/mod-view.js +33 -0
- package/dist/components/mobile-player/fullscreen.js +69 -0
- package/dist/components/mobile-player/fullscreen.native.js +151 -0
- package/dist/components/mobile-player/player.js +103 -0
- package/dist/components/mobile-player/props.js +1 -0
- package/dist/components/mobile-player/shared.js +51 -0
- package/dist/components/mobile-player/ui/countdown.js +79 -0
- package/dist/components/mobile-player/ui/index.js +5 -0
- package/dist/components/mobile-player/ui/input.js +38 -0
- package/dist/components/mobile-player/ui/metrics.js +40 -0
- package/dist/components/mobile-player/ui/streamer-context-menu.js +4 -0
- package/dist/components/mobile-player/ui/viewer-context-menu.js +20 -0
- package/dist/components/mobile-player/use-webrtc.js +232 -0
- package/dist/components/mobile-player/video.js +375 -0
- package/dist/components/mobile-player/video.native.js +238 -0
- package/dist/components/mobile-player/webrtc-diagnostics.js +106 -0
- package/dist/components/mobile-player/webrtc-primitives.js +25 -0
- package/dist/components/mobile-player/webrtc-primitives.native.js +1 -0
- package/dist/components/ui/button.js +220 -0
- package/dist/components/ui/dialog.js +203 -0
- package/dist/components/ui/dropdown.js +148 -0
- package/dist/components/ui/icons.js +22 -0
- package/dist/components/ui/index.js +22 -0
- package/dist/components/ui/input.js +202 -0
- package/dist/components/ui/loader.js +7 -0
- package/dist/components/ui/primitives/button.js +121 -0
- package/dist/components/ui/primitives/input.js +202 -0
- package/dist/components/ui/primitives/modal.js +203 -0
- package/dist/components/ui/primitives/text.js +286 -0
- package/dist/components/ui/resizeable.js +101 -0
- package/dist/components/ui/text.js +175 -0
- package/dist/components/ui/textarea.js +17 -0
- package/dist/components/ui/toast.js +129 -0
- package/dist/components/ui/view.js +250 -0
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/useAvatars.js +32 -0
- package/dist/hooks/useCameraToggle.js +9 -0
- package/dist/hooks/useKeyboard.js +33 -0
- package/dist/hooks/useKeyboardSlide.js +11 -0
- package/dist/hooks/useLivestreamInfo.js +62 -0
- package/dist/hooks/useOuterAndInnerDimensions.js +27 -0
- package/dist/hooks/usePlayerDimensions.js +19 -0
- package/dist/hooks/useSegmentTiming.js +62 -0
- package/dist/index.js +16 -0
- package/dist/lib/facet.js +88 -0
- package/dist/lib/theme/atoms.js +620 -0
- package/dist/lib/theme/atoms.types.js +5 -0
- package/dist/lib/theme/index.js +9 -0
- package/dist/lib/theme/theme.js +248 -0
- package/dist/lib/theme/tokens.js +383 -0
- package/dist/lib/utils.js +94 -0
- package/dist/livestream-provider/index.js +25 -0
- package/dist/livestream-provider/websocket.js +41 -0
- package/dist/livestream-store/chat.js +186 -0
- package/dist/livestream-store/context.js +2 -0
- package/dist/livestream-store/index.js +4 -0
- package/dist/livestream-store/livestream-state.js +1 -0
- package/dist/livestream-store/livestream-store.js +42 -0
- package/dist/livestream-store/stream-key.js +115 -0
- package/dist/livestream-store/websocket-consumer.js +55 -0
- package/dist/player-store/context.js +2 -0
- package/dist/player-store/index.js +6 -0
- package/dist/player-store/player-provider.js +52 -0
- package/dist/player-store/player-state.js +22 -0
- package/dist/player-store/player-store.js +159 -0
- package/dist/player-store/single-player-provider.js +109 -0
- package/dist/streamplace-provider/context.js +2 -0
- package/dist/streamplace-provider/index.js +16 -0
- package/dist/streamplace-provider/poller.js +46 -0
- package/dist/streamplace-provider/xrpc.js +0 -0
- package/dist/streamplace-store/block.js +23 -0
- package/dist/streamplace-store/index.js +3 -0
- package/dist/streamplace-store/stream.js +193 -0
- package/dist/streamplace-store/streamplace-store.js +37 -0
- package/dist/streamplace-store/user.js +47 -0
- package/dist/streamplace-store/xrpc.js +12 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/37be0eec +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/56540125 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/67b1eb60 +0 -0
- package/node-compile-cache/v22.15.0-x64-efe9a9df-0/7c275f90 +0 -0
- package/package.json +50 -8
- package/src/components/chat/chat-box.tsx +195 -0
- package/src/components/chat/chat-message.tsx +192 -0
- package/src/components/chat/chat.tsx +128 -0
- package/src/components/chat/mention-suggestions.tsx +71 -0
- package/src/components/chat/mod-view.tsx +118 -0
- package/src/components/mobile-player/fullscreen.native.tsx +193 -0
- package/src/components/mobile-player/fullscreen.tsx +79 -0
- package/src/components/mobile-player/player.tsx +134 -0
- package/src/components/mobile-player/props.tsx +11 -0
- package/src/components/mobile-player/shared.tsx +56 -0
- package/src/components/mobile-player/ui/countdown.tsx +119 -0
- package/src/components/mobile-player/ui/index.ts +5 -0
- package/src/components/mobile-player/ui/input.tsx +85 -0
- package/src/components/mobile-player/ui/metrics.tsx +69 -0
- package/src/components/mobile-player/ui/streamer-context-menu.tsx +3 -0
- package/src/components/mobile-player/ui/viewer-context-menu.tsx +70 -0
- package/src/components/mobile-player/use-webrtc.tsx +282 -0
- package/src/components/mobile-player/video.native.tsx +360 -0
- package/src/components/mobile-player/video.tsx +557 -0
- package/src/components/mobile-player/webrtc-diagnostics.tsx +149 -0
- package/src/components/mobile-player/webrtc-primitives.native.tsx +6 -0
- package/src/components/mobile-player/webrtc-primitives.tsx +33 -0
- package/src/components/ui/button.tsx +309 -0
- package/src/components/ui/dialog.tsx +376 -0
- package/src/components/ui/dropdown.tsx +399 -0
- package/src/components/ui/icons.tsx +50 -0
- package/src/components/ui/index.ts +33 -0
- package/src/components/ui/input.tsx +350 -0
- package/src/components/ui/loader.tsx +9 -0
- package/src/components/ui/primitives/button.tsx +292 -0
- package/src/components/ui/primitives/input.tsx +422 -0
- package/src/components/ui/primitives/modal.tsx +421 -0
- package/src/components/ui/primitives/text.tsx +499 -0
- package/src/components/ui/resizeable.tsx +169 -0
- package/src/components/ui/text.tsx +330 -0
- package/src/components/ui/textarea.tsx +34 -0
- package/src/components/ui/toast.tsx +203 -0
- package/src/components/ui/view.tsx +344 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/useAvatars.tsx +44 -0
- package/src/hooks/useCameraToggle.ts +12 -0
- package/src/hooks/useKeyboard.tsx +41 -0
- package/src/hooks/useKeyboardSlide.ts +12 -0
- package/src/hooks/useLivestreamInfo.ts +67 -0
- package/src/hooks/useOuterAndInnerDimensions.tsx +32 -0
- package/src/hooks/usePlayerDimensions.ts +23 -0
- package/src/hooks/useSegmentTiming.tsx +88 -0
- package/src/index.tsx +27 -0
- package/src/lib/facet.ts +131 -0
- package/src/lib/theme/atoms.ts +760 -0
- package/src/lib/theme/atoms.types.ts +258 -0
- package/src/lib/theme/index.ts +48 -0
- package/src/lib/theme/theme.tsx +436 -0
- package/src/lib/theme/tokens.ts +409 -0
- package/src/lib/utils.ts +132 -0
- package/src/livestream-provider/index.tsx +48 -0
- package/src/livestream-provider/websocket.tsx +47 -0
- package/src/livestream-store/chat.tsx +261 -0
- package/src/livestream-store/context.tsx +10 -0
- package/src/livestream-store/index.tsx +4 -0
- package/src/livestream-store/livestream-state.tsx +21 -0
- package/src/livestream-store/livestream-store.tsx +59 -0
- package/src/livestream-store/stream-key.tsx +124 -0
- package/src/livestream-store/websocket-consumer.tsx +62 -0
- package/src/player-store/context.tsx +11 -0
- package/src/player-store/index.tsx +6 -0
- package/src/player-store/player-provider.tsx +89 -0
- package/src/player-store/player-state.tsx +187 -0
- package/src/player-store/player-store.tsx +239 -0
- package/src/player-store/single-player-provider.tsx +181 -0
- package/src/streamplace-provider/context.tsx +10 -0
- package/src/streamplace-provider/index.tsx +32 -0
- package/src/streamplace-provider/poller.tsx +55 -0
- package/src/streamplace-provider/xrpc.tsx +0 -0
- package/src/streamplace-store/block.tsx +29 -0
- package/src/streamplace-store/index.tsx +3 -0
- package/src/streamplace-store/stream.tsx +262 -0
- package/src/streamplace-store/streamplace-store.tsx +89 -0
- package/src/streamplace-store/user.tsx +57 -0
- package/src/streamplace-store/xrpc.tsx +15 -0
- package/tsconfig.json +9 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { AppBskyGraphBlock } from "@atproto/api";
|
|
2
|
+
import { usePDSAgent } from "./xrpc";
|
|
3
|
+
|
|
4
|
+
export function useCreateBlockRecord() {
|
|
5
|
+
let agent = usePDSAgent();
|
|
6
|
+
|
|
7
|
+
return async (subjectDID: string) => {
|
|
8
|
+
if (!agent) {
|
|
9
|
+
throw new Error("No PDS agent found");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!agent.did) {
|
|
13
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const record: AppBskyGraphBlock.Record = {
|
|
17
|
+
$type: "app.bsky.graph.block",
|
|
18
|
+
subject: subjectDID,
|
|
19
|
+
createdAt: new Date().toISOString(),
|
|
20
|
+
};
|
|
21
|
+
return await agent.com.atproto.repo.createRecord({
|
|
22
|
+
repo: agent.did,
|
|
23
|
+
collection: "app.bsky.graph.block",
|
|
24
|
+
record,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return record;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { AppBskyFeedPost, BlobRef, RichText } from "@atproto/api";
|
|
2
|
+
import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
|
|
3
|
+
import { StreamplaceAgent } from "streamplace/src/agent";
|
|
4
|
+
import { PlaceStreamLivestream } from "streamplace/src/lexicons";
|
|
5
|
+
import { LivestreamViewHydrated } from "streamplace/src/useful-types";
|
|
6
|
+
import { useUrl } from "./streamplace-store";
|
|
7
|
+
import { usePDSAgent } from "./xrpc";
|
|
8
|
+
|
|
9
|
+
const uploadThumbnail = async (
|
|
10
|
+
pdsAgent: StreamplaceAgent,
|
|
11
|
+
customThumbnail?: Blob,
|
|
12
|
+
) => {
|
|
13
|
+
if (customThumbnail) {
|
|
14
|
+
let tries = 0;
|
|
15
|
+
try {
|
|
16
|
+
let thumbnail = await pdsAgent.uploadBlob(customThumbnail);
|
|
17
|
+
|
|
18
|
+
while (
|
|
19
|
+
thumbnail.data.blob.size === 0 &&
|
|
20
|
+
customThumbnail.size !== 0 &&
|
|
21
|
+
tries < 3
|
|
22
|
+
) {
|
|
23
|
+
console.warn(
|
|
24
|
+
"Reuploading blob as blob sizes don't match! Blob size recieved is",
|
|
25
|
+
thumbnail.data.blob.size,
|
|
26
|
+
"and sent blob size is",
|
|
27
|
+
customThumbnail.size,
|
|
28
|
+
);
|
|
29
|
+
thumbnail = await pdsAgent.uploadBlob(customThumbnail);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (tries === 3) {
|
|
33
|
+
throw new Error("Could not successfully upload blob (tried thrice)");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (thumbnail.success) {
|
|
37
|
+
console.log("Successfully uploaded thumbnail");
|
|
38
|
+
return thumbnail.data.blob;
|
|
39
|
+
}
|
|
40
|
+
} catch (e) {
|
|
41
|
+
throw new Error("Error uploading thumbnail: " + e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
async function createNewPost(
|
|
47
|
+
agent: StreamplaceAgent,
|
|
48
|
+
record: AppBskyFeedPost.Record,
|
|
49
|
+
): Promise<{ uri: string; cid: string }> {
|
|
50
|
+
try {
|
|
51
|
+
const post = await agent.post(record);
|
|
52
|
+
|
|
53
|
+
return { uri: post.uri, cid: post.cid };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
console.error("Error creating new post:", error);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildGoLivePost(
|
|
61
|
+
text: string,
|
|
62
|
+
url: URL,
|
|
63
|
+
profile: ProfileViewDetailed,
|
|
64
|
+
params: URLSearchParams,
|
|
65
|
+
thumbnail: BlobRef | undefined,
|
|
66
|
+
): AppBskyFeedPost.Record {
|
|
67
|
+
const now = new Date();
|
|
68
|
+
const linkUrl = `${url.protocol}//${url.host}/${profile.handle}?${params.toString()}`;
|
|
69
|
+
const prefix = `🔴 LIVE `;
|
|
70
|
+
const textUrl = `${url.protocol}//${url.host}/${profile.handle}`;
|
|
71
|
+
const suffix = ` ${text}`;
|
|
72
|
+
const content = prefix + textUrl + suffix;
|
|
73
|
+
|
|
74
|
+
const rt = new RichText({ text: content });
|
|
75
|
+
rt.detectFacetsWithoutResolution();
|
|
76
|
+
const record: AppBskyFeedPost.Record = {
|
|
77
|
+
$type: "app.bsky.feed.post",
|
|
78
|
+
text: content,
|
|
79
|
+
"place.stream.livestream": {
|
|
80
|
+
url: linkUrl,
|
|
81
|
+
title: text,
|
|
82
|
+
},
|
|
83
|
+
facets: rt.facets,
|
|
84
|
+
createdAt: now.toISOString(),
|
|
85
|
+
};
|
|
86
|
+
record.embed = {
|
|
87
|
+
$type: "app.bsky.embed.external",
|
|
88
|
+
external: {
|
|
89
|
+
description: text,
|
|
90
|
+
thumb: thumbnail,
|
|
91
|
+
title: `@${profile.handle} is 🔴LIVE on ${url.host}!`,
|
|
92
|
+
uri: linkUrl,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return record;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function useCreateStreamRecord() {
|
|
100
|
+
let agent = usePDSAgent();
|
|
101
|
+
let url = useUrl();
|
|
102
|
+
|
|
103
|
+
return async (
|
|
104
|
+
title: string,
|
|
105
|
+
customThumbnail?: Blob,
|
|
106
|
+
submitPost: boolean = true,
|
|
107
|
+
) => {
|
|
108
|
+
if (!agent) {
|
|
109
|
+
throw new Error("No PDS agent found");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!agent.did) {
|
|
113
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let thumbnail: BlobRef | undefined = undefined;
|
|
117
|
+
|
|
118
|
+
const u = new URL(url);
|
|
119
|
+
|
|
120
|
+
if (customThumbnail) {
|
|
121
|
+
try {
|
|
122
|
+
thumbnail = await uploadThumbnail(agent, customThumbnail);
|
|
123
|
+
} catch (e) {
|
|
124
|
+
throw new Error(`Custom thumbnail upload failed ${e}`);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
// No custom thumbnail: fetch the server-side image and upload it
|
|
128
|
+
// try thrice lel
|
|
129
|
+
let tries = 0;
|
|
130
|
+
try {
|
|
131
|
+
for (; tries < 3; tries++) {
|
|
132
|
+
try {
|
|
133
|
+
console.log(
|
|
134
|
+
`Fetching thumbnail from ${u.protocol}//${u.host}/api/playback/${agent.did}/stream.png`,
|
|
135
|
+
);
|
|
136
|
+
const thumbnailRes = await fetch(
|
|
137
|
+
`${u.protocol}//${u.host}/api/playback/${agent.did}/stream.png`,
|
|
138
|
+
);
|
|
139
|
+
if (!thumbnailRes.ok) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`Failed to fetch thumbnail: ${thumbnailRes.status})`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const thumbnailBlob = await thumbnailRes.blob();
|
|
145
|
+
console.log(thumbnailBlob);
|
|
146
|
+
thumbnail = await uploadThumbnail(agent, thumbnailBlob);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.warn(
|
|
149
|
+
`Failed to fetch thumbnail, retrying (${tries + 1}/3): ${e}`,
|
|
150
|
+
);
|
|
151
|
+
// Wait 1 second before retrying
|
|
152
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
153
|
+
if (tries === 2) {
|
|
154
|
+
throw new Error(`Failed to fetch thumbnail after 3 tries: ${e}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (e) {
|
|
159
|
+
throw new Error(`Thumbnail upload failed ${e}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let newPost: undefined | { uri: string; cid: string } = undefined;
|
|
164
|
+
|
|
165
|
+
if (submitPost) {
|
|
166
|
+
const did = agent.did;
|
|
167
|
+
const profile = await agent.getProfile({ actor: did });
|
|
168
|
+
|
|
169
|
+
if (!profile) {
|
|
170
|
+
throw new Error("No profile found for the user DID");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const params = new URLSearchParams({
|
|
174
|
+
did: did,
|
|
175
|
+
time: new Date().toISOString(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
let post = buildGoLivePost(title, u, profile.data, params, thumbnail);
|
|
179
|
+
|
|
180
|
+
newPost = await createNewPost(agent, post);
|
|
181
|
+
|
|
182
|
+
if (!newPost.uri || !newPost.cid) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
"Cannot read properties of undefined (reading 'uri' or 'cid')",
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const record: PlaceStreamLivestream.Record = {
|
|
190
|
+
title: title,
|
|
191
|
+
url: url,
|
|
192
|
+
createdAt: new Date().toISOString(),
|
|
193
|
+
post: newPost,
|
|
194
|
+
thumb: thumbnail,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
await agent.com.atproto.repo.createRecord({
|
|
198
|
+
repo: agent.did,
|
|
199
|
+
collection: "place.stream.livestream",
|
|
200
|
+
record,
|
|
201
|
+
});
|
|
202
|
+
return record;
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function useUpdateStreamRecord() {
|
|
207
|
+
let agent = usePDSAgent();
|
|
208
|
+
let url = useUrl();
|
|
209
|
+
|
|
210
|
+
return async (
|
|
211
|
+
title: string,
|
|
212
|
+
livestream: LivestreamViewHydrated | null,
|
|
213
|
+
customThumbnail?: Blob,
|
|
214
|
+
) => {
|
|
215
|
+
if (!agent) {
|
|
216
|
+
throw new Error("No PDS agent found");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!agent.did) {
|
|
220
|
+
throw new Error("No user DID found, assuming not logged in");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!livestream) {
|
|
224
|
+
throw new Error("No latest record");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
let rkey = livestream.uri.split("/").pop();
|
|
228
|
+
let oldRecordValue: PlaceStreamLivestream.Record = livestream.record;
|
|
229
|
+
|
|
230
|
+
if (!rkey) {
|
|
231
|
+
throw new Error("No rkey?");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let thumbnail: BlobRef | undefined = oldRecordValue.thumb;
|
|
235
|
+
|
|
236
|
+
// update thumbnail if a new one is provided
|
|
237
|
+
if (customThumbnail) {
|
|
238
|
+
try {
|
|
239
|
+
thumbnail = await uploadThumbnail(agent, customThumbnail);
|
|
240
|
+
} catch (e) {
|
|
241
|
+
throw new Error(`Custom thumbnail upload failed ${e}`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const record: PlaceStreamLivestream.Record = {
|
|
246
|
+
title: title,
|
|
247
|
+
url: url,
|
|
248
|
+
createdAt: new Date().toISOString(),
|
|
249
|
+
post: oldRecordValue.post,
|
|
250
|
+
thumb: thumbnail,
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
await agent.com.atproto.repo.putRecord({
|
|
254
|
+
repo: agent.did,
|
|
255
|
+
collection: "place.stream.livestream",
|
|
256
|
+
rkey,
|
|
257
|
+
record,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return record;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { SessionManager } from "@atproto/api/dist/session-manager";
|
|
2
|
+
import { useContext } from "react";
|
|
3
|
+
import { PlaceStreamChatProfile, PlaceStreamLivestream } from "streamplace";
|
|
4
|
+
import { createStore, StoreApi, useStore } from "zustand";
|
|
5
|
+
import { StreamplaceContext } from "../streamplace-provider/context";
|
|
6
|
+
|
|
7
|
+
// there are three categories of XRPC that we need to handle:
|
|
8
|
+
// 1. Public (probably) OAuth XRPC to the users' PDS for apps that use this API.
|
|
9
|
+
// 2. Confidental OAuth to the Streamplace server for doing things that require
|
|
10
|
+
// server-side authentication. This isn't very much stuff yet, but you need
|
|
11
|
+
// to log into Streamplace to do things like have Streamplace update your
|
|
12
|
+
// activity status.
|
|
13
|
+
// 3. Anonymous XRPC to the Streamplace server for stuff like `getLiveUsers`. This
|
|
14
|
+
// is easy to handle internal to this library.
|
|
15
|
+
// For the Streamplace app itself, all three are the same. For apps that aren't
|
|
16
|
+
// doing OAuth through the Streamplace node, we need to expose an interface that
|
|
17
|
+
// allows them to use atcute or whatever for 1.
|
|
18
|
+
|
|
19
|
+
export interface StreamplaceState {
|
|
20
|
+
url: string;
|
|
21
|
+
liveUsers: PlaceStreamLivestream.LivestreamView[] | null;
|
|
22
|
+
setLiveUsers: (opts: {
|
|
23
|
+
liveUsers?: PlaceStreamLivestream.LivestreamView[];
|
|
24
|
+
liveUsersLoading?: boolean;
|
|
25
|
+
liveUsersError?: string | null;
|
|
26
|
+
liveUsersRefresh?: number;
|
|
27
|
+
}) => void;
|
|
28
|
+
liveUsersRefresh: number;
|
|
29
|
+
liveUsersLoading: boolean;
|
|
30
|
+
liveUsersError: string | null;
|
|
31
|
+
oauthSession: SessionManager | null;
|
|
32
|
+
handle: string | null;
|
|
33
|
+
chatProfile: PlaceStreamChatProfile.Record | null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type StreamplaceStore = StoreApi<StreamplaceState>;
|
|
37
|
+
|
|
38
|
+
export const makeStreamplaceStore = ({
|
|
39
|
+
url,
|
|
40
|
+
}: {
|
|
41
|
+
url: string;
|
|
42
|
+
}): StoreApi<StreamplaceState> => {
|
|
43
|
+
return createStore<StreamplaceState>()((set) => ({
|
|
44
|
+
url,
|
|
45
|
+
liveUsers: null,
|
|
46
|
+
setLiveUsers: (opts: {
|
|
47
|
+
liveUsers?: PlaceStreamLivestream.LivestreamView[];
|
|
48
|
+
liveUsersLoading?: boolean;
|
|
49
|
+
liveUsersError?: string | null;
|
|
50
|
+
liveUsersRefresh?: number;
|
|
51
|
+
}) => {
|
|
52
|
+
set({
|
|
53
|
+
...opts,
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
liveUsersRefresh: 0,
|
|
57
|
+
liveUsersLoading: true,
|
|
58
|
+
liveUsersError: null,
|
|
59
|
+
oauthSession: null,
|
|
60
|
+
handle: null,
|
|
61
|
+
chatProfile: null,
|
|
62
|
+
}));
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export function getStreamplaceStoreFromContext(): StreamplaceStore {
|
|
66
|
+
const context = useContext(StreamplaceContext);
|
|
67
|
+
if (!context) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"useStreamplaceStore must be used within a StreamplaceProvider",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return context.store;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function useStreamplaceStore<U>(
|
|
76
|
+
selector: (state: StreamplaceState) => U,
|
|
77
|
+
): U {
|
|
78
|
+
return useStore(getStreamplaceStoreFromContext(), selector);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const useUrl = () => useStreamplaceStore((x) => x.url);
|
|
82
|
+
|
|
83
|
+
export const useDID = () => useStreamplaceStore((x) => x.oauthSession?.did);
|
|
84
|
+
|
|
85
|
+
export const useHandle = () => useStreamplaceStore((x) => x.handle);
|
|
86
|
+
export const useSetHandle = (): ((handle: string) => void) => {
|
|
87
|
+
const store = getStreamplaceStoreFromContext();
|
|
88
|
+
return (handle: string) => store.setState({ handle });
|
|
89
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { PlaceStreamChatProfile } from "streamplace";
|
|
2
|
+
import {
|
|
3
|
+
getStreamplaceStoreFromContext,
|
|
4
|
+
useDID,
|
|
5
|
+
useStreamplaceStore,
|
|
6
|
+
} from "./streamplace-store";
|
|
7
|
+
import { usePDSAgent } from "./xrpc";
|
|
8
|
+
|
|
9
|
+
export function useGetChatProfile() {
|
|
10
|
+
const did = useDID();
|
|
11
|
+
const pdsAgent = usePDSAgent();
|
|
12
|
+
const store = getStreamplaceStoreFromContext();
|
|
13
|
+
|
|
14
|
+
return async () => {
|
|
15
|
+
if (!did || !pdsAgent) {
|
|
16
|
+
throw new Error("No DID or PDS agent");
|
|
17
|
+
}
|
|
18
|
+
const res = await pdsAgent.com.atproto.repo.getRecord({
|
|
19
|
+
repo: did,
|
|
20
|
+
collection: "place.stream.chat.profile",
|
|
21
|
+
rkey: "self",
|
|
22
|
+
});
|
|
23
|
+
if (!res.success) {
|
|
24
|
+
throw new Error("Failed to get chat profile record");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (PlaceStreamChatProfile.isRecord(res.data.value)) {
|
|
28
|
+
store.setState({ chatProfile: res.data.value });
|
|
29
|
+
} else {
|
|
30
|
+
console.log("not a record", res.data.value);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useGetBskyProfile() {
|
|
36
|
+
const did = useDID();
|
|
37
|
+
const pdsAgent = usePDSAgent();
|
|
38
|
+
const store = getStreamplaceStoreFromContext();
|
|
39
|
+
|
|
40
|
+
return async () => {
|
|
41
|
+
if (!did || !pdsAgent) {
|
|
42
|
+
throw new Error("No DID or PDS agent");
|
|
43
|
+
}
|
|
44
|
+
const res = await pdsAgent.app.bsky.actor.getProfile({
|
|
45
|
+
actor: did,
|
|
46
|
+
});
|
|
47
|
+
if (!res.success) {
|
|
48
|
+
throw new Error("Failed to get chat profile record");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
store.setState({ handle: res.data.handle });
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function useChatProfile() {
|
|
56
|
+
return useStreamplaceStore((x) => x.chatProfile);
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { StreamplaceAgent } from "streamplace";
|
|
3
|
+
import { useStreamplaceStore } from ".";
|
|
4
|
+
|
|
5
|
+
export function usePDSAgent(): StreamplaceAgent | null {
|
|
6
|
+
const oauthSession = useStreamplaceStore((state) => state.oauthSession);
|
|
7
|
+
|
|
8
|
+
return useMemo(() => {
|
|
9
|
+
if (!oauthSession) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return new StreamplaceAgent(oauthSession);
|
|
14
|
+
}, [oauthSession]);
|
|
15
|
+
}
|