@bridgecord/sdk 1.0.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/README.md +144 -0
- package/dist/index.cjs +311 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +263 -0
- package/dist/index.d.ts +263 -0
- package/dist/index.js +274 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// src/bridgecord.ts
|
|
2
|
+
var DEFAULT_API_URL = "https://api.bridgecord.io";
|
|
3
|
+
var DEFAULT_EMBED_URL = "https://embed.bridgecord.io";
|
|
4
|
+
var Bridgecord = class {
|
|
5
|
+
embedId;
|
|
6
|
+
sessionToken;
|
|
7
|
+
displayName;
|
|
8
|
+
apiUrl;
|
|
9
|
+
embedUrl;
|
|
10
|
+
iframe = null;
|
|
11
|
+
container = null;
|
|
12
|
+
listeners = /* @__PURE__ */ new Map();
|
|
13
|
+
messageHandler = null;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.embedId = options.embedId;
|
|
16
|
+
this.sessionToken = options.sessionToken;
|
|
17
|
+
this.displayName = options.displayName;
|
|
18
|
+
this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
19
|
+
this.embedUrl = options.embedUrl ?? DEFAULT_EMBED_URL;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Authenticate as a guest with the configured displayName.
|
|
23
|
+
* Called automatically by mount() if displayName is set and no sessionToken exists.
|
|
24
|
+
* Returns the session token.
|
|
25
|
+
*/
|
|
26
|
+
async authenticateGuest(displayName) {
|
|
27
|
+
const name = displayName ?? this.displayName;
|
|
28
|
+
if (!name) throw new Error("displayName is required for guest authentication");
|
|
29
|
+
const res = await fetch(`${this.apiUrl}/api/v1/auth/embed/guest`, {
|
|
30
|
+
method: "POST",
|
|
31
|
+
headers: { "Content-Type": "application/json" },
|
|
32
|
+
body: JSON.stringify({ displayName: name, embedId: this.embedId })
|
|
33
|
+
});
|
|
34
|
+
const json = await res.json();
|
|
35
|
+
if (!json.ok) throw new Error(json.error ?? "Guest authentication failed");
|
|
36
|
+
this.sessionToken = json.data.accessToken;
|
|
37
|
+
this.emit("auth", json.data.user);
|
|
38
|
+
return json.data.accessToken;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Mount the Bridgecord embed into a container element.
|
|
42
|
+
* Creates an iframe pointing to the embed app.
|
|
43
|
+
*
|
|
44
|
+
* If `displayName` was provided in the constructor and no `sessionToken` exists,
|
|
45
|
+
* the SDK will first authenticate as a guest, then mount the iframe with the session.
|
|
46
|
+
*/
|
|
47
|
+
async mount(container, options) {
|
|
48
|
+
if (this.iframe) {
|
|
49
|
+
this.destroy();
|
|
50
|
+
}
|
|
51
|
+
if (this.displayName && !this.sessionToken) {
|
|
52
|
+
await this.authenticateGuest();
|
|
53
|
+
}
|
|
54
|
+
this.container = container;
|
|
55
|
+
this.iframe = document.createElement("iframe");
|
|
56
|
+
this.iframe.src = this.buildIframeUrl();
|
|
57
|
+
this.iframe.style.width = "100%";
|
|
58
|
+
this.iframe.style.height = `${options?.height ?? 600}px`;
|
|
59
|
+
this.iframe.style.border = "none";
|
|
60
|
+
this.iframe.style.borderRadius = "8px";
|
|
61
|
+
this.iframe.setAttribute("allow", "clipboard-write");
|
|
62
|
+
if (options?.className) {
|
|
63
|
+
this.iframe.className = options.className;
|
|
64
|
+
}
|
|
65
|
+
this.messageHandler = (e) => {
|
|
66
|
+
if (!e.data?.type?.startsWith("bc:")) return;
|
|
67
|
+
this.handleIframeMessage(e.data);
|
|
68
|
+
};
|
|
69
|
+
window.addEventListener("message", this.messageHandler);
|
|
70
|
+
container.appendChild(this.iframe);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to Bridgecord events.
|
|
74
|
+
*/
|
|
75
|
+
on(event, handler) {
|
|
76
|
+
if (!this.listeners.has(event)) {
|
|
77
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
78
|
+
}
|
|
79
|
+
this.listeners.get(event)?.add(handler);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Unsubscribe from Bridgecord events.
|
|
83
|
+
*/
|
|
84
|
+
off(event, handler) {
|
|
85
|
+
this.listeners.get(event)?.delete(handler);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Destroy the embed and clean up all resources.
|
|
89
|
+
*/
|
|
90
|
+
destroy() {
|
|
91
|
+
if (this.messageHandler) {
|
|
92
|
+
window.removeEventListener("message", this.messageHandler);
|
|
93
|
+
this.messageHandler = null;
|
|
94
|
+
}
|
|
95
|
+
if (this.iframe && this.container) {
|
|
96
|
+
this.container.removeChild(this.iframe);
|
|
97
|
+
this.iframe = null;
|
|
98
|
+
}
|
|
99
|
+
this.listeners.clear();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Open a direct Socket.io connection (no iframe).
|
|
103
|
+
* For developers who want complete rendering control.
|
|
104
|
+
*/
|
|
105
|
+
static async connect(options) {
|
|
106
|
+
const apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
107
|
+
let { sessionToken } = options;
|
|
108
|
+
if (options.displayName && !sessionToken) {
|
|
109
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/embed/guest`, {
|
|
110
|
+
method: "POST",
|
|
111
|
+
headers: { "Content-Type": "application/json" },
|
|
112
|
+
body: JSON.stringify({
|
|
113
|
+
displayName: options.displayName,
|
|
114
|
+
embedId: options.embedId
|
|
115
|
+
})
|
|
116
|
+
});
|
|
117
|
+
const json = await res.json();
|
|
118
|
+
if (!json.ok) throw new Error(json.error ?? "Guest authentication failed");
|
|
119
|
+
sessionToken = json.data.accessToken;
|
|
120
|
+
}
|
|
121
|
+
const configRes = await fetch(`${apiUrl}/api/v1/embeds/${options.embedId}`, {
|
|
122
|
+
headers: sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {}
|
|
123
|
+
});
|
|
124
|
+
const configJson = await configRes.json();
|
|
125
|
+
if (!configJson.ok) throw new Error(configJson.error ?? "Failed to load embed config");
|
|
126
|
+
const config = configJson.data;
|
|
127
|
+
const { io } = await import(
|
|
128
|
+
/* webpackIgnore: true */
|
|
129
|
+
"socket.io-client"
|
|
130
|
+
);
|
|
131
|
+
const socket = io(apiUrl, {
|
|
132
|
+
autoConnect: true,
|
|
133
|
+
auth: sessionToken ? { token: sessionToken } : void 0
|
|
134
|
+
});
|
|
135
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
136
|
+
let currentChannel = null;
|
|
137
|
+
function emit(event, data) {
|
|
138
|
+
for (const handler of listeners.get(event) ?? []) {
|
|
139
|
+
handler(data);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
socket.on("connect", () => emit("ready", void 0));
|
|
143
|
+
socket.on("message:initial", (msgs) => emit("messages:initial", msgs));
|
|
144
|
+
socket.on("message:new", (msg) => emit("message", msg));
|
|
145
|
+
socket.on("connect_error", (err) => emit("error", err));
|
|
146
|
+
async function request(path, init) {
|
|
147
|
+
const headers = new Headers(init?.headers);
|
|
148
|
+
headers.set("Content-Type", "application/json");
|
|
149
|
+
if (sessionToken) {
|
|
150
|
+
headers.set("Authorization", `Bearer ${sessionToken}`);
|
|
151
|
+
}
|
|
152
|
+
const res = await fetch(`${apiUrl}${path}`, { ...init, headers });
|
|
153
|
+
const json = await res.json();
|
|
154
|
+
if (!json.ok) throw new Error(json.error);
|
|
155
|
+
return json.data;
|
|
156
|
+
}
|
|
157
|
+
const connection = {
|
|
158
|
+
config,
|
|
159
|
+
get currentChannel() {
|
|
160
|
+
return currentChannel;
|
|
161
|
+
},
|
|
162
|
+
joinChannel(channelId) {
|
|
163
|
+
if (currentChannel) {
|
|
164
|
+
socket.emit("channel:leave", { serverId: config.serverId, channelId: currentChannel });
|
|
165
|
+
}
|
|
166
|
+
currentChannel = channelId;
|
|
167
|
+
socket.emit("channel:join", { serverId: config.serverId, channelId }, (ok) => {
|
|
168
|
+
if (!ok) emit("error", new Error(`Failed to join channel ${channelId}`));
|
|
169
|
+
});
|
|
170
|
+
const ch = config.channels.find((c) => c.id === channelId);
|
|
171
|
+
if (ch) emit("channel:changed", ch);
|
|
172
|
+
},
|
|
173
|
+
leaveChannel() {
|
|
174
|
+
if (currentChannel) {
|
|
175
|
+
socket.emit("channel:leave", { serverId: config.serverId, channelId: currentChannel });
|
|
176
|
+
currentChannel = null;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
async sendMessage(content) {
|
|
180
|
+
if (!currentChannel) throw new Error("No channel joined");
|
|
181
|
+
return request(`/api/v1/channels/${currentChannel}/messages`, {
|
|
182
|
+
method: "POST",
|
|
183
|
+
body: JSON.stringify({ content })
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
on(event, handler) {
|
|
187
|
+
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
188
|
+
listeners.get(event)?.add(handler);
|
|
189
|
+
},
|
|
190
|
+
off(event, handler) {
|
|
191
|
+
listeners.get(event)?.delete(handler);
|
|
192
|
+
},
|
|
193
|
+
async fetchRewards() {
|
|
194
|
+
return request(`/api/v1/rewards/${config.serverId}/me`);
|
|
195
|
+
},
|
|
196
|
+
async fetchLeaderboard(limit = 25, offset = 0) {
|
|
197
|
+
return request(
|
|
198
|
+
`/api/v1/rewards/${config.serverId}/leaderboard?limit=${limit}&offset=${offset}`
|
|
199
|
+
);
|
|
200
|
+
},
|
|
201
|
+
destroy() {
|
|
202
|
+
if (currentChannel) {
|
|
203
|
+
socket.emit("channel:leave", {
|
|
204
|
+
serverId: config.serverId,
|
|
205
|
+
channelId: currentChannel
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
socket.disconnect();
|
|
209
|
+
listeners.clear();
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
return connection;
|
|
213
|
+
}
|
|
214
|
+
buildIframeUrl() {
|
|
215
|
+
const params = new URLSearchParams();
|
|
216
|
+
if (this.sessionToken) {
|
|
217
|
+
params.set("session", this.sessionToken);
|
|
218
|
+
}
|
|
219
|
+
const qs = params.toString();
|
|
220
|
+
return `${this.embedUrl}/${this.embedId}${qs ? `?${qs}` : ""}`;
|
|
221
|
+
}
|
|
222
|
+
buildIframeUrlWithView(view, channelId) {
|
|
223
|
+
const params = new URLSearchParams();
|
|
224
|
+
params.set("view", view);
|
|
225
|
+
if (channelId) params.set("channel", channelId);
|
|
226
|
+
if (this.sessionToken) params.set("session", this.sessionToken);
|
|
227
|
+
return `${this.embedUrl}/${this.embedId}?${params}`;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Mount a single-channel chat view.
|
|
231
|
+
*/
|
|
232
|
+
mountChat(container, options) {
|
|
233
|
+
if (this.iframe) this.destroy();
|
|
234
|
+
this.container = container;
|
|
235
|
+
this.iframe = document.createElement("iframe");
|
|
236
|
+
this.iframe.src = this.buildIframeUrlWithView("chat", options?.channelId);
|
|
237
|
+
this.iframe.style.width = "100%";
|
|
238
|
+
this.iframe.style.height = `${options?.height ?? 600}px`;
|
|
239
|
+
this.iframe.style.border = "none";
|
|
240
|
+
this.iframe.style.borderRadius = "8px";
|
|
241
|
+
this.iframe.setAttribute("allow", "clipboard-write");
|
|
242
|
+
if (options?.className) this.iframe.className = options.className;
|
|
243
|
+
this.messageHandler = (e) => {
|
|
244
|
+
if (!e.data?.type?.startsWith("bc:")) return;
|
|
245
|
+
this.handleIframeMessage(e.data);
|
|
246
|
+
};
|
|
247
|
+
window.addEventListener("message", this.messageHandler);
|
|
248
|
+
container.appendChild(this.iframe);
|
|
249
|
+
}
|
|
250
|
+
handleIframeMessage(msg) {
|
|
251
|
+
switch (msg.type) {
|
|
252
|
+
case "bc:ready":
|
|
253
|
+
this.emit("ready", void 0);
|
|
254
|
+
break;
|
|
255
|
+
case "bc:auth":
|
|
256
|
+
if (msg.tokens && typeof msg.tokens === "object") {
|
|
257
|
+
const tokens = msg.tokens;
|
|
258
|
+
if (tokens.user) this.emit("auth", tokens.user);
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
case "bc:unread":
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
emit(event, data) {
|
|
266
|
+
for (const handler of this.listeners.get(event) ?? []) {
|
|
267
|
+
handler(data);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
export {
|
|
272
|
+
Bridgecord
|
|
273
|
+
};
|
|
274
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridgecord.ts"],"sourcesContent":["import type {\n\tBridgecordChannel,\n\tBridgecordConnection,\n\tBridgecordEmbedConfig,\n\tBridgecordEventMap,\n\tBridgecordLeaderboardEntry,\n\tBridgecordMessage,\n\tBridgecordOptions,\n\tBridgecordRewardProfile,\n\tBridgecordUser,\n\tConnectOptions,\n} from \"./types\";\n\nconst DEFAULT_API_URL = \"https://api.bridgecord.io\";\nconst DEFAULT_EMBED_URL = \"https://embed.bridgecord.io\";\n\ntype EventHandler = (...args: unknown[]) => void;\n\n/**\n * Framework-agnostic Bridgecord SDK.\n *\n * @example\n * ```ts\n * const bc = new Bridgecord({ embedId: 'abc123' });\n * bc.mount(document.getElementById('chat'));\n * bc.on('message', (msg) => console.log(msg));\n * // later...\n * bc.destroy();\n * ```\n */\nexport class Bridgecord {\n\tprivate readonly embedId: string;\n\tprivate sessionToken?: string;\n\tprivate readonly displayName?: string;\n\tprivate readonly apiUrl: string;\n\tprivate readonly embedUrl: string;\n\tprivate iframe: HTMLIFrameElement | null = null;\n\tprivate container: HTMLElement | null = null;\n\tprivate listeners = new Map<string, Set<EventHandler>>();\n\tprivate messageHandler: ((e: MessageEvent) => void) | null = null;\n\n\tconstructor(options: BridgecordOptions) {\n\t\tthis.embedId = options.embedId;\n\t\tthis.sessionToken = options.sessionToken;\n\t\tthis.displayName = options.displayName;\n\t\tthis.apiUrl = options.apiUrl ?? DEFAULT_API_URL;\n\t\tthis.embedUrl = options.embedUrl ?? DEFAULT_EMBED_URL;\n\t}\n\n\t/**\n\t * Authenticate as a guest with the configured displayName.\n\t * Called automatically by mount() if displayName is set and no sessionToken exists.\n\t * Returns the session token.\n\t */\n\tasync authenticateGuest(displayName?: string): Promise<string> {\n\t\tconst name = displayName ?? this.displayName;\n\t\tif (!name) throw new Error(\"displayName is required for guest authentication\");\n\n\t\tconst res = await fetch(`${this.apiUrl}/api/v1/auth/embed/guest`, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify({ displayName: name, embedId: this.embedId }),\n\t\t});\n\t\tconst json = await res.json();\n\t\tif (!json.ok) throw new Error(json.error ?? \"Guest authentication failed\");\n\n\t\tthis.sessionToken = json.data.accessToken;\n\t\tthis.emit(\"auth\", json.data.user);\n\t\treturn json.data.accessToken;\n\t}\n\n\t/**\n\t * Mount the Bridgecord embed into a container element.\n\t * Creates an iframe pointing to the embed app.\n\t *\n\t * If `displayName` was provided in the constructor and no `sessionToken` exists,\n\t * the SDK will first authenticate as a guest, then mount the iframe with the session.\n\t */\n\tasync mount(\n\t\tcontainer: HTMLElement,\n\t\toptions?: { height?: number; className?: string },\n\t): Promise<void> {\n\t\tif (this.iframe) {\n\t\t\tthis.destroy();\n\t\t}\n\n\t\t// Auto-authenticate as guest if displayName is set and no session token\n\t\tif (this.displayName && !this.sessionToken) {\n\t\t\tawait this.authenticateGuest();\n\t\t}\n\n\t\tthis.container = container;\n\t\tthis.iframe = document.createElement(\"iframe\");\n\t\tthis.iframe.src = this.buildIframeUrl();\n\t\tthis.iframe.style.width = \"100%\";\n\t\tthis.iframe.style.height = `${options?.height ?? 600}px`;\n\t\tthis.iframe.style.border = \"none\";\n\t\tthis.iframe.style.borderRadius = \"8px\";\n\t\tthis.iframe.setAttribute(\"allow\", \"clipboard-write\");\n\n\t\tif (options?.className) {\n\t\t\tthis.iframe.className = options.className;\n\t\t}\n\n\t\t// Listen for postMessage from iframe\n\t\tthis.messageHandler = (e: MessageEvent) => {\n\t\t\tif (!e.data?.type?.startsWith(\"bc:\")) return;\n\t\t\tthis.handleIframeMessage(e.data);\n\t\t};\n\t\twindow.addEventListener(\"message\", this.messageHandler);\n\n\t\tcontainer.appendChild(this.iframe);\n\t}\n\n\t/**\n\t * Subscribe to Bridgecord events.\n\t */\n\ton<K extends keyof BridgecordEventMap>(\n\t\tevent: K,\n\t\thandler: (data: BridgecordEventMap[K]) => void,\n\t): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)?.add(handler as EventHandler);\n\t}\n\n\t/**\n\t * Unsubscribe from Bridgecord events.\n\t */\n\toff<K extends keyof BridgecordEventMap>(\n\t\tevent: K,\n\t\thandler: (data: BridgecordEventMap[K]) => void,\n\t): void {\n\t\tthis.listeners.get(event)?.delete(handler as EventHandler);\n\t}\n\n\t/**\n\t * Destroy the embed and clean up all resources.\n\t */\n\tdestroy(): void {\n\t\tif (this.messageHandler) {\n\t\t\twindow.removeEventListener(\"message\", this.messageHandler);\n\t\t\tthis.messageHandler = null;\n\t\t}\n\t\tif (this.iframe && this.container) {\n\t\t\tthis.container.removeChild(this.iframe);\n\t\t\tthis.iframe = null;\n\t\t}\n\t\tthis.listeners.clear();\n\t}\n\n\t/**\n\t * Open a direct Socket.io connection (no iframe).\n\t * For developers who want complete rendering control.\n\t */\n\tstatic async connect(options: ConnectOptions): Promise<BridgecordConnection> {\n\t\tconst apiUrl = options.apiUrl ?? DEFAULT_API_URL;\n\t\tlet { sessionToken } = options;\n\n\t\t// Auto-authenticate as guest if displayName is provided and no session token\n\t\tif (options.displayName && !sessionToken) {\n\t\t\tconst res = await fetch(`${apiUrl}/api/v1/auth/embed/guest`, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tdisplayName: options.displayName,\n\t\t\t\t\tembedId: options.embedId,\n\t\t\t\t}),\n\t\t\t});\n\t\t\tconst json = await res.json();\n\t\t\tif (!json.ok) throw new Error(json.error ?? \"Guest authentication failed\");\n\t\t\tsessionToken = json.data.accessToken;\n\t\t}\n\n\t\t// Fetch embed config\n\t\tconst configRes = await fetch(`${apiUrl}/api/v1/embeds/${options.embedId}`, {\n\t\t\theaders: sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {},\n\t\t});\n\t\tconst configJson = await configRes.json();\n\t\tif (!configJson.ok) throw new Error(configJson.error ?? \"Failed to load embed config\");\n\t\tconst config: BridgecordEmbedConfig = configJson.data;\n\n\t\t// Dynamic import socket.io-client — keeps SDK zero-dep for iframe-only users\n\t\tconst { io } = await import(/* webpackIgnore: true */ \"socket.io-client\");\n\n\t\tconst socket = io(apiUrl, {\n\t\t\tautoConnect: true,\n\t\t\tauth: sessionToken ? { token: sessionToken } : undefined,\n\t\t});\n\n\t\tconst listeners = new Map<string, Set<EventHandler>>();\n\t\tlet currentChannel: string | null = null;\n\n\t\tfunction emit(event: string, data: unknown) {\n\t\t\tfor (const handler of listeners.get(event) ?? []) {\n\t\t\t\thandler(data);\n\t\t\t}\n\t\t}\n\n\t\tsocket.on(\"connect\", () => emit(\"ready\", undefined));\n\t\tsocket.on(\"message:initial\", (msgs: BridgecordMessage[]) => emit(\"messages:initial\", msgs));\n\t\tsocket.on(\"message:new\", (msg: BridgecordMessage) => emit(\"message\", msg));\n\t\tsocket.on(\"connect_error\", (err: Error) => emit(\"error\", err));\n\n\t\tasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n\t\t\tconst headers = new Headers(init?.headers);\n\t\t\theaders.set(\"Content-Type\", \"application/json\");\n\t\t\tif (sessionToken) {\n\t\t\t\theaders.set(\"Authorization\", `Bearer ${sessionToken}`);\n\t\t\t}\n\t\t\tconst res = await fetch(`${apiUrl}${path}`, { ...init, headers });\n\t\t\tconst json = await res.json();\n\t\t\tif (!json.ok) throw new Error(json.error);\n\t\t\treturn json.data;\n\t\t}\n\n\t\tconst connection: BridgecordConnection = {\n\t\t\tconfig,\n\t\t\tget currentChannel() {\n\t\t\t\treturn currentChannel;\n\t\t\t},\n\n\t\t\tjoinChannel(channelId: string) {\n\t\t\t\tif (currentChannel) {\n\t\t\t\t\tsocket.emit(\"channel:leave\", { serverId: config.serverId, channelId: currentChannel });\n\t\t\t\t}\n\t\t\t\tcurrentChannel = channelId;\n\t\t\t\tsocket.emit(\"channel:join\", { serverId: config.serverId, channelId }, (ok: boolean) => {\n\t\t\t\t\tif (!ok) emit(\"error\", new Error(`Failed to join channel ${channelId}`));\n\t\t\t\t});\n\t\t\t\tconst ch = config.channels.find((c) => c.id === channelId);\n\t\t\t\tif (ch) emit(\"channel:changed\", ch);\n\t\t\t},\n\n\t\t\tleaveChannel() {\n\t\t\t\tif (currentChannel) {\n\t\t\t\t\tsocket.emit(\"channel:leave\", { serverId: config.serverId, channelId: currentChannel });\n\t\t\t\t\tcurrentChannel = null;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\tasync sendMessage(content: string): Promise<BridgecordMessage> {\n\t\t\t\tif (!currentChannel) throw new Error(\"No channel joined\");\n\t\t\t\treturn request(`/api/v1/channels/${currentChannel}/messages`, {\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: JSON.stringify({ content }),\n\t\t\t\t});\n\t\t\t},\n\n\t\t\ton(event, handler) {\n\t\t\t\tif (!listeners.has(event)) listeners.set(event, new Set());\n\t\t\t\tlisteners.get(event)?.add(handler as EventHandler);\n\t\t\t},\n\n\t\t\toff(event, handler) {\n\t\t\t\tlisteners.get(event)?.delete(handler as EventHandler);\n\t\t\t},\n\n\t\t\tasync fetchRewards(): Promise<BridgecordRewardProfile> {\n\t\t\t\treturn request(`/api/v1/rewards/${config.serverId}/me`);\n\t\t\t},\n\n\t\t\tasync fetchLeaderboard(\n\t\t\t\tlimit = 25,\n\t\t\t\toffset = 0,\n\t\t\t): Promise<{ entries: BridgecordLeaderboardEntry[]; total: number }> {\n\t\t\t\treturn request(\n\t\t\t\t\t`/api/v1/rewards/${config.serverId}/leaderboard?limit=${limit}&offset=${offset}`,\n\t\t\t\t);\n\t\t\t},\n\n\t\t\tdestroy() {\n\t\t\t\tif (currentChannel) {\n\t\t\t\t\tsocket.emit(\"channel:leave\", {\n\t\t\t\t\t\tserverId: config.serverId,\n\t\t\t\t\t\tchannelId: currentChannel,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tsocket.disconnect();\n\t\t\t\tlisteners.clear();\n\t\t\t},\n\t\t};\n\n\t\treturn connection;\n\t}\n\n\tprivate buildIframeUrl(): string {\n\t\tconst params = new URLSearchParams();\n\t\tif (this.sessionToken) {\n\t\t\tparams.set(\"session\", this.sessionToken);\n\t\t}\n\t\tconst qs = params.toString();\n\t\treturn `${this.embedUrl}/${this.embedId}${qs ? `?${qs}` : \"\"}`;\n\t}\n\n\tprivate buildIframeUrlWithView(view: string, channelId?: string): string {\n\t\tconst params = new URLSearchParams();\n\t\tparams.set(\"view\", view);\n\t\tif (channelId) params.set(\"channel\", channelId);\n\t\tif (this.sessionToken) params.set(\"session\", this.sessionToken);\n\t\treturn `${this.embedUrl}/${this.embedId}?${params}`;\n\t}\n\n\t/**\n\t * Mount a single-channel chat view.\n\t */\n\tmountChat(\n\t\tcontainer: HTMLElement,\n\t\toptions?: { channelId?: string; height?: number; className?: string },\n\t): void {\n\t\tif (this.iframe) this.destroy();\n\t\tthis.container = container;\n\t\tthis.iframe = document.createElement(\"iframe\");\n\t\tthis.iframe.src = this.buildIframeUrlWithView(\"chat\", options?.channelId);\n\t\tthis.iframe.style.width = \"100%\";\n\t\tthis.iframe.style.height = `${options?.height ?? 600}px`;\n\t\tthis.iframe.style.border = \"none\";\n\t\tthis.iframe.style.borderRadius = \"8px\";\n\t\tthis.iframe.setAttribute(\"allow\", \"clipboard-write\");\n\t\tif (options?.className) this.iframe.className = options.className;\n\n\t\tthis.messageHandler = (e: MessageEvent) => {\n\t\t\tif (!e.data?.type?.startsWith(\"bc:\")) return;\n\t\t\tthis.handleIframeMessage(e.data);\n\t\t};\n\t\twindow.addEventListener(\"message\", this.messageHandler);\n\t\tcontainer.appendChild(this.iframe);\n\t}\n\n\tprivate handleIframeMessage(msg: { type: string; [key: string]: unknown }) {\n\t\tswitch (msg.type) {\n\t\t\tcase \"bc:ready\":\n\t\t\t\tthis.emit(\"ready\", undefined);\n\t\t\t\tbreak;\n\t\t\tcase \"bc:auth\":\n\t\t\t\tif (msg.tokens && typeof msg.tokens === \"object\") {\n\t\t\t\t\tconst tokens = msg.tokens as { user?: BridgecordUser };\n\t\t\t\t\tif (tokens.user) this.emit(\"auth\", tokens.user);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"bc:unread\":\n\t\t\t\t// Unread events are iframe-internal, but we expose them\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprivate emit(event: string, data: unknown) {\n\t\tfor (const handler of this.listeners.get(event) ?? []) {\n\t\t\thandler(data);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bridgecord/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Framework-agnostic SDK for embedding Bridgecord chat",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"require": {
|
|
16
|
+
"types": "./dist/index.d.cts",
|
|
17
|
+
"default": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": ["dist"],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"dev": "tsup --watch",
|
|
25
|
+
"test": "bun test"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"socket.io-client": "^4.0.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"socket.io-client": {
|
|
32
|
+
"optional": true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"socket.io-client": "^4.8.3",
|
|
37
|
+
"tsup": "^8.0.0",
|
|
38
|
+
"typescript": "^5.7.3"
|
|
39
|
+
},
|
|
40
|
+
"keywords": ["bridgecord", "discord", "embed", "chat", "sdk"],
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/bridgecord/bridgecord",
|
|
45
|
+
"directory": "packages/sdk"
|
|
46
|
+
}
|
|
47
|
+
}
|