@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/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @bridgecord/sdk
|
|
2
|
+
|
|
3
|
+
Framework-agnostic SDK for embedding Bridgecord chat into any web application. Zero dependencies for iframe mode; uses `socket.io-client` only when using direct connections.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @bridgecord/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start — Iframe (Simple)
|
|
12
|
+
|
|
13
|
+
The easiest way to add Bridgecord to your app. The SDK renders an iframe that handles everything for you.
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { Bridgecord } from '@bridgecord/sdk';
|
|
17
|
+
|
|
18
|
+
const bc = new Bridgecord({ embedId: 'your-embed-id' });
|
|
19
|
+
|
|
20
|
+
// Mount the full embed (channels + chat + rewards)
|
|
21
|
+
bc.mount(document.getElementById('chat'), { height: 600 });
|
|
22
|
+
|
|
23
|
+
// Listen for events from the embed
|
|
24
|
+
bc.on('message', (msg) => {
|
|
25
|
+
console.log('New message:', msg.content);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
bc.on('auth', (user) => {
|
|
29
|
+
console.log('User authenticated:', user.displayName);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Clean up when done
|
|
33
|
+
bc.destroy();
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Mount a single-channel chat view
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
bc.mountChat(document.getElementById('chat'), {
|
|
40
|
+
channelId: 'channel-id', // optional — defaults to first channel
|
|
41
|
+
height: 400,
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Pre-authenticated users
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const bc = new Bridgecord({
|
|
49
|
+
embedId: 'your-embed-id',
|
|
50
|
+
sessionToken: 'user-jwt-token',
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Advanced — Direct Socket.io Connection (Headless)
|
|
55
|
+
|
|
56
|
+
For developers who want complete rendering control. No iframe — you get raw data and render it yourself.
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import { Bridgecord } from '@bridgecord/sdk';
|
|
60
|
+
|
|
61
|
+
// Requires socket.io-client as a peer dependency for this mode
|
|
62
|
+
const conn = await Bridgecord.connect({
|
|
63
|
+
embedId: 'your-embed-id',
|
|
64
|
+
sessionToken: 'user-jwt-token',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Access embed config
|
|
68
|
+
console.log(conn.config.channels);
|
|
69
|
+
|
|
70
|
+
// Join a channel
|
|
71
|
+
conn.joinChannel(conn.config.channels[0].id);
|
|
72
|
+
|
|
73
|
+
// Listen for messages
|
|
74
|
+
conn.on('messages:initial', (msgs) => {
|
|
75
|
+
console.log('History:', msgs);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
conn.on('message', (msg) => {
|
|
79
|
+
console.log('New:', msg.content);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Send a message
|
|
83
|
+
await conn.sendMessage('Hello from the SDK!');
|
|
84
|
+
|
|
85
|
+
// Fetch rewards
|
|
86
|
+
const rewards = await conn.fetchRewards();
|
|
87
|
+
const leaderboard = await conn.fetchLeaderboard(10);
|
|
88
|
+
|
|
89
|
+
// Clean up
|
|
90
|
+
conn.destroy();
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## API Reference
|
|
94
|
+
|
|
95
|
+
### `new Bridgecord(options)`
|
|
96
|
+
|
|
97
|
+
| Option | Type | Required | Description |
|
|
98
|
+
|--------|------|----------|-------------|
|
|
99
|
+
| `embedId` | `string` | Yes | Your embed ID from the dashboard |
|
|
100
|
+
| `sessionToken` | `string` | No | Pre-auth JWT token |
|
|
101
|
+
| `apiUrl` | `string` | No | API base URL (default: `https://api.bridgecord.io`) |
|
|
102
|
+
| `embedUrl` | `string` | No | Embed app URL (default: `https://embed.bridgecord.io`) |
|
|
103
|
+
|
|
104
|
+
### Instance Methods
|
|
105
|
+
|
|
106
|
+
- `mount(container, options?)` — Render full embed iframe
|
|
107
|
+
- `mountChat(container, options?)` — Render single-channel chat iframe
|
|
108
|
+
- `on(event, handler)` — Subscribe to events
|
|
109
|
+
- `off(event, handler)` — Unsubscribe from events
|
|
110
|
+
- `destroy()` — Remove iframe and clean up
|
|
111
|
+
|
|
112
|
+
### `Bridgecord.connect(options)` (Static)
|
|
113
|
+
|
|
114
|
+
Returns a `BridgecordConnection` with:
|
|
115
|
+
|
|
116
|
+
- `config` — Embed configuration
|
|
117
|
+
- `currentChannel` — Currently joined channel ID
|
|
118
|
+
- `joinChannel(id)` — Join a channel
|
|
119
|
+
- `leaveChannel()` — Leave current channel
|
|
120
|
+
- `sendMessage(content)` — Send a message
|
|
121
|
+
- `fetchRewards()` — Get user's reward profile
|
|
122
|
+
- `fetchLeaderboard(limit?, offset?)` — Get leaderboard
|
|
123
|
+
- `on(event, handler)` / `off(event, handler)` — Event listeners
|
|
124
|
+
- `destroy()` — Disconnect and clean up
|
|
125
|
+
|
|
126
|
+
### Events
|
|
127
|
+
|
|
128
|
+
| Event | Payload | Description |
|
|
129
|
+
|-------|---------|-------------|
|
|
130
|
+
| `message` | `BridgecordMessage` | New message received |
|
|
131
|
+
| `messages:initial` | `BridgecordMessage[]` | Channel history loaded |
|
|
132
|
+
| `auth` | `BridgecordUser` | User authenticated |
|
|
133
|
+
| `reward` | `BridgecordRewardEvent` | Reward earned |
|
|
134
|
+
| `ready` | `void` | Embed/connection ready |
|
|
135
|
+
| `error` | `Error` | Error occurred |
|
|
136
|
+
| `channel:changed` | `BridgecordChannel` | Channel switched |
|
|
137
|
+
|
|
138
|
+
## TypeScript
|
|
139
|
+
|
|
140
|
+
Full TypeScript support with exported types for all events, messages, users, channels, and rewards. Import any type directly:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
import type { BridgecordMessage, BridgecordUser, BridgecordChannel } from '@bridgecord/sdk';
|
|
144
|
+
```
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
Bridgecord: () => Bridgecord
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/bridgecord.ts
|
|
38
|
+
var DEFAULT_API_URL = "https://api.bridgecord.io";
|
|
39
|
+
var DEFAULT_EMBED_URL = "https://embed.bridgecord.io";
|
|
40
|
+
var Bridgecord = class {
|
|
41
|
+
embedId;
|
|
42
|
+
sessionToken;
|
|
43
|
+
displayName;
|
|
44
|
+
apiUrl;
|
|
45
|
+
embedUrl;
|
|
46
|
+
iframe = null;
|
|
47
|
+
container = null;
|
|
48
|
+
listeners = /* @__PURE__ */ new Map();
|
|
49
|
+
messageHandler = null;
|
|
50
|
+
constructor(options) {
|
|
51
|
+
this.embedId = options.embedId;
|
|
52
|
+
this.sessionToken = options.sessionToken;
|
|
53
|
+
this.displayName = options.displayName;
|
|
54
|
+
this.apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
55
|
+
this.embedUrl = options.embedUrl ?? DEFAULT_EMBED_URL;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Authenticate as a guest with the configured displayName.
|
|
59
|
+
* Called automatically by mount() if displayName is set and no sessionToken exists.
|
|
60
|
+
* Returns the session token.
|
|
61
|
+
*/
|
|
62
|
+
async authenticateGuest(displayName) {
|
|
63
|
+
const name = displayName ?? this.displayName;
|
|
64
|
+
if (!name) throw new Error("displayName is required for guest authentication");
|
|
65
|
+
const res = await fetch(`${this.apiUrl}/api/v1/auth/embed/guest`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: { "Content-Type": "application/json" },
|
|
68
|
+
body: JSON.stringify({ displayName: name, embedId: this.embedId })
|
|
69
|
+
});
|
|
70
|
+
const json = await res.json();
|
|
71
|
+
if (!json.ok) throw new Error(json.error ?? "Guest authentication failed");
|
|
72
|
+
this.sessionToken = json.data.accessToken;
|
|
73
|
+
this.emit("auth", json.data.user);
|
|
74
|
+
return json.data.accessToken;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Mount the Bridgecord embed into a container element.
|
|
78
|
+
* Creates an iframe pointing to the embed app.
|
|
79
|
+
*
|
|
80
|
+
* If `displayName` was provided in the constructor and no `sessionToken` exists,
|
|
81
|
+
* the SDK will first authenticate as a guest, then mount the iframe with the session.
|
|
82
|
+
*/
|
|
83
|
+
async mount(container, options) {
|
|
84
|
+
if (this.iframe) {
|
|
85
|
+
this.destroy();
|
|
86
|
+
}
|
|
87
|
+
if (this.displayName && !this.sessionToken) {
|
|
88
|
+
await this.authenticateGuest();
|
|
89
|
+
}
|
|
90
|
+
this.container = container;
|
|
91
|
+
this.iframe = document.createElement("iframe");
|
|
92
|
+
this.iframe.src = this.buildIframeUrl();
|
|
93
|
+
this.iframe.style.width = "100%";
|
|
94
|
+
this.iframe.style.height = `${options?.height ?? 600}px`;
|
|
95
|
+
this.iframe.style.border = "none";
|
|
96
|
+
this.iframe.style.borderRadius = "8px";
|
|
97
|
+
this.iframe.setAttribute("allow", "clipboard-write");
|
|
98
|
+
if (options?.className) {
|
|
99
|
+
this.iframe.className = options.className;
|
|
100
|
+
}
|
|
101
|
+
this.messageHandler = (e) => {
|
|
102
|
+
if (!e.data?.type?.startsWith("bc:")) return;
|
|
103
|
+
this.handleIframeMessage(e.data);
|
|
104
|
+
};
|
|
105
|
+
window.addEventListener("message", this.messageHandler);
|
|
106
|
+
container.appendChild(this.iframe);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Subscribe to Bridgecord events.
|
|
110
|
+
*/
|
|
111
|
+
on(event, handler) {
|
|
112
|
+
if (!this.listeners.has(event)) {
|
|
113
|
+
this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
114
|
+
}
|
|
115
|
+
this.listeners.get(event)?.add(handler);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Unsubscribe from Bridgecord events.
|
|
119
|
+
*/
|
|
120
|
+
off(event, handler) {
|
|
121
|
+
this.listeners.get(event)?.delete(handler);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Destroy the embed and clean up all resources.
|
|
125
|
+
*/
|
|
126
|
+
destroy() {
|
|
127
|
+
if (this.messageHandler) {
|
|
128
|
+
window.removeEventListener("message", this.messageHandler);
|
|
129
|
+
this.messageHandler = null;
|
|
130
|
+
}
|
|
131
|
+
if (this.iframe && this.container) {
|
|
132
|
+
this.container.removeChild(this.iframe);
|
|
133
|
+
this.iframe = null;
|
|
134
|
+
}
|
|
135
|
+
this.listeners.clear();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Open a direct Socket.io connection (no iframe).
|
|
139
|
+
* For developers who want complete rendering control.
|
|
140
|
+
*/
|
|
141
|
+
static async connect(options) {
|
|
142
|
+
const apiUrl = options.apiUrl ?? DEFAULT_API_URL;
|
|
143
|
+
let { sessionToken } = options;
|
|
144
|
+
if (options.displayName && !sessionToken) {
|
|
145
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/embed/guest`, {
|
|
146
|
+
method: "POST",
|
|
147
|
+
headers: { "Content-Type": "application/json" },
|
|
148
|
+
body: JSON.stringify({
|
|
149
|
+
displayName: options.displayName,
|
|
150
|
+
embedId: options.embedId
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
const json = await res.json();
|
|
154
|
+
if (!json.ok) throw new Error(json.error ?? "Guest authentication failed");
|
|
155
|
+
sessionToken = json.data.accessToken;
|
|
156
|
+
}
|
|
157
|
+
const configRes = await fetch(`${apiUrl}/api/v1/embeds/${options.embedId}`, {
|
|
158
|
+
headers: sessionToken ? { Authorization: `Bearer ${sessionToken}` } : {}
|
|
159
|
+
});
|
|
160
|
+
const configJson = await configRes.json();
|
|
161
|
+
if (!configJson.ok) throw new Error(configJson.error ?? "Failed to load embed config");
|
|
162
|
+
const config = configJson.data;
|
|
163
|
+
const { io } = await import(
|
|
164
|
+
/* webpackIgnore: true */
|
|
165
|
+
"socket.io-client"
|
|
166
|
+
);
|
|
167
|
+
const socket = io(apiUrl, {
|
|
168
|
+
autoConnect: true,
|
|
169
|
+
auth: sessionToken ? { token: sessionToken } : void 0
|
|
170
|
+
});
|
|
171
|
+
const listeners = /* @__PURE__ */ new Map();
|
|
172
|
+
let currentChannel = null;
|
|
173
|
+
function emit(event, data) {
|
|
174
|
+
for (const handler of listeners.get(event) ?? []) {
|
|
175
|
+
handler(data);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
socket.on("connect", () => emit("ready", void 0));
|
|
179
|
+
socket.on("message:initial", (msgs) => emit("messages:initial", msgs));
|
|
180
|
+
socket.on("message:new", (msg) => emit("message", msg));
|
|
181
|
+
socket.on("connect_error", (err) => emit("error", err));
|
|
182
|
+
async function request(path, init) {
|
|
183
|
+
const headers = new Headers(init?.headers);
|
|
184
|
+
headers.set("Content-Type", "application/json");
|
|
185
|
+
if (sessionToken) {
|
|
186
|
+
headers.set("Authorization", `Bearer ${sessionToken}`);
|
|
187
|
+
}
|
|
188
|
+
const res = await fetch(`${apiUrl}${path}`, { ...init, headers });
|
|
189
|
+
const json = await res.json();
|
|
190
|
+
if (!json.ok) throw new Error(json.error);
|
|
191
|
+
return json.data;
|
|
192
|
+
}
|
|
193
|
+
const connection = {
|
|
194
|
+
config,
|
|
195
|
+
get currentChannel() {
|
|
196
|
+
return currentChannel;
|
|
197
|
+
},
|
|
198
|
+
joinChannel(channelId) {
|
|
199
|
+
if (currentChannel) {
|
|
200
|
+
socket.emit("channel:leave", { serverId: config.serverId, channelId: currentChannel });
|
|
201
|
+
}
|
|
202
|
+
currentChannel = channelId;
|
|
203
|
+
socket.emit("channel:join", { serverId: config.serverId, channelId }, (ok) => {
|
|
204
|
+
if (!ok) emit("error", new Error(`Failed to join channel ${channelId}`));
|
|
205
|
+
});
|
|
206
|
+
const ch = config.channels.find((c) => c.id === channelId);
|
|
207
|
+
if (ch) emit("channel:changed", ch);
|
|
208
|
+
},
|
|
209
|
+
leaveChannel() {
|
|
210
|
+
if (currentChannel) {
|
|
211
|
+
socket.emit("channel:leave", { serverId: config.serverId, channelId: currentChannel });
|
|
212
|
+
currentChannel = null;
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
async sendMessage(content) {
|
|
216
|
+
if (!currentChannel) throw new Error("No channel joined");
|
|
217
|
+
return request(`/api/v1/channels/${currentChannel}/messages`, {
|
|
218
|
+
method: "POST",
|
|
219
|
+
body: JSON.stringify({ content })
|
|
220
|
+
});
|
|
221
|
+
},
|
|
222
|
+
on(event, handler) {
|
|
223
|
+
if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
|
|
224
|
+
listeners.get(event)?.add(handler);
|
|
225
|
+
},
|
|
226
|
+
off(event, handler) {
|
|
227
|
+
listeners.get(event)?.delete(handler);
|
|
228
|
+
},
|
|
229
|
+
async fetchRewards() {
|
|
230
|
+
return request(`/api/v1/rewards/${config.serverId}/me`);
|
|
231
|
+
},
|
|
232
|
+
async fetchLeaderboard(limit = 25, offset = 0) {
|
|
233
|
+
return request(
|
|
234
|
+
`/api/v1/rewards/${config.serverId}/leaderboard?limit=${limit}&offset=${offset}`
|
|
235
|
+
);
|
|
236
|
+
},
|
|
237
|
+
destroy() {
|
|
238
|
+
if (currentChannel) {
|
|
239
|
+
socket.emit("channel:leave", {
|
|
240
|
+
serverId: config.serverId,
|
|
241
|
+
channelId: currentChannel
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
socket.disconnect();
|
|
245
|
+
listeners.clear();
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
return connection;
|
|
249
|
+
}
|
|
250
|
+
buildIframeUrl() {
|
|
251
|
+
const params = new URLSearchParams();
|
|
252
|
+
if (this.sessionToken) {
|
|
253
|
+
params.set("session", this.sessionToken);
|
|
254
|
+
}
|
|
255
|
+
const qs = params.toString();
|
|
256
|
+
return `${this.embedUrl}/${this.embedId}${qs ? `?${qs}` : ""}`;
|
|
257
|
+
}
|
|
258
|
+
buildIframeUrlWithView(view, channelId) {
|
|
259
|
+
const params = new URLSearchParams();
|
|
260
|
+
params.set("view", view);
|
|
261
|
+
if (channelId) params.set("channel", channelId);
|
|
262
|
+
if (this.sessionToken) params.set("session", this.sessionToken);
|
|
263
|
+
return `${this.embedUrl}/${this.embedId}?${params}`;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Mount a single-channel chat view.
|
|
267
|
+
*/
|
|
268
|
+
mountChat(container, options) {
|
|
269
|
+
if (this.iframe) this.destroy();
|
|
270
|
+
this.container = container;
|
|
271
|
+
this.iframe = document.createElement("iframe");
|
|
272
|
+
this.iframe.src = this.buildIframeUrlWithView("chat", options?.channelId);
|
|
273
|
+
this.iframe.style.width = "100%";
|
|
274
|
+
this.iframe.style.height = `${options?.height ?? 600}px`;
|
|
275
|
+
this.iframe.style.border = "none";
|
|
276
|
+
this.iframe.style.borderRadius = "8px";
|
|
277
|
+
this.iframe.setAttribute("allow", "clipboard-write");
|
|
278
|
+
if (options?.className) this.iframe.className = options.className;
|
|
279
|
+
this.messageHandler = (e) => {
|
|
280
|
+
if (!e.data?.type?.startsWith("bc:")) return;
|
|
281
|
+
this.handleIframeMessage(e.data);
|
|
282
|
+
};
|
|
283
|
+
window.addEventListener("message", this.messageHandler);
|
|
284
|
+
container.appendChild(this.iframe);
|
|
285
|
+
}
|
|
286
|
+
handleIframeMessage(msg) {
|
|
287
|
+
switch (msg.type) {
|
|
288
|
+
case "bc:ready":
|
|
289
|
+
this.emit("ready", void 0);
|
|
290
|
+
break;
|
|
291
|
+
case "bc:auth":
|
|
292
|
+
if (msg.tokens && typeof msg.tokens === "object") {
|
|
293
|
+
const tokens = msg.tokens;
|
|
294
|
+
if (tokens.user) this.emit("auth", tokens.user);
|
|
295
|
+
}
|
|
296
|
+
break;
|
|
297
|
+
case "bc:unread":
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
emit(event, data) {
|
|
302
|
+
for (const handler of this.listeners.get(event) ?? []) {
|
|
303
|
+
handler(data);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
308
|
+
0 && (module.exports = {
|
|
309
|
+
Bridgecord
|
|
310
|
+
});
|
|
311
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/bridgecord.ts"],"sourcesContent":["export { Bridgecord } from \"./bridgecord\";\nexport type {\n\tBridgecordAttachment,\n\tBridgecordAuthor,\n\tBridgecordChannel,\n\tBridgecordConnection,\n\tBridgecordEmbed,\n\tBridgecordEmbedConfig,\n\tBridgecordEventMap,\n\tBridgecordLeaderboardEntry,\n\tBridgecordMessage,\n\tBridgecordOptions,\n\tBridgecordRewardEvent,\n\tBridgecordRewardProfile,\n\tBridgecordRole,\n\tBridgecordTheme,\n\tBridgecordUser,\n\tConnectOptions,\n} from \"./types\";\n","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":[]}
|