@huddle01/server-sdk
Version:
The Huddle01 Server SDK allows you to perform protected admin actions on your server side, like generating peer access tokens and starting and stopping meeting recordings and livestreams.
324 lines (318 loc) • 8.85 kB
JavaScript
;
var chunkNJUEGIAO_cjs = require('./chunk-NJUEGIAO.cjs');
var chunkUYXCM27V_cjs = require('./chunk-UYXCM27V.cjs');
var chunkCGGVUTEY_cjs = require('./chunk-CGGVUTEY.cjs');
var jose = require('jose');
var zod = require('zod');
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var jose__namespace = /*#__PURE__*/_interopNamespace(jose);
var logger = chunkUYXCM27V_cjs.mainLogger.createSubLogger("AccessToken");
function isDefaultRole(role) {
return Object.values(Role).includes(role);
}
var RoleSchema = zod.z.union([
zod.z.literal("host"),
zod.z.literal("coHost"),
zod.z.literal("speaker"),
zod.z.literal("listener"),
zod.z.literal("guest"),
zod.z.literal("bot")
]);
var Role = {
HOST: "host",
CO_HOST: "coHost",
SPEAKER: "speaker",
LISTENER: "listener",
GUEST: "guest",
BOT: "bot"
};
var Permissions = zod.z.object({
admin: zod.z.boolean(),
canConsume: zod.z.boolean(),
canProduce: zod.z.boolean(),
canProduceSources: zod.z.object({
cam: zod.z.boolean(),
mic: zod.z.boolean(),
screen: zod.z.boolean()
}),
canSendData: zod.z.boolean(),
canRecvData: zod.z.boolean(),
canUpdateMetadata: zod.z.boolean()
});
var DEFAULT_PERMISSIONS = {
admin: false,
canConsume: true,
canProduce: true,
canProduceSources: {
cam: true,
mic: true,
screen: true
},
canRecvData: true,
canSendData: true,
canUpdateMetadata: true
};
var ROLE_PERMISSIONS = {
[Role.HOST]: {
...DEFAULT_PERMISSIONS,
admin: true
},
[Role.CO_HOST]: {
...DEFAULT_PERMISSIONS,
admin: true
/* ... other permissions for the co-host ... */
},
[Role.LISTENER]: {
admin: false,
canConsume: true,
canProduce: false,
canProduceSources: {
cam: false,
mic: false,
screen: false
},
canRecvData: true,
canSendData: true,
canUpdateMetadata: true
},
[Role.SPEAKER]: {
admin: false,
canConsume: true,
canProduce: true,
canProduceSources: {
cam: false,
mic: true,
screen: true
},
canRecvData: true,
canSendData: true,
canUpdateMetadata: true
},
[Role.BOT]: {
admin: false,
canConsume: true,
canProduce: false,
canProduceSources: {
cam: false,
mic: false,
screen: false
},
canRecvData: false,
canSendData: false,
canUpdateMetadata: false
},
[Role.GUEST]: {
...DEFAULT_PERMISSIONS
}
};
var MeetingTokenInputSchema = zod.z.object({
roomId: zod.z.string().min(1, {
message: "RoomId must provided"
}),
muteOnEntry: zod.z.boolean().optional(),
videoOnEntry: zod.z.boolean().optional(),
roomType: zod.z.enum(["AUDIO", "VIDEO"]).optional(),
role: zod.z.string().optional(),
permissions: zod.z.object({
admin: zod.z.boolean(),
canConsume: zod.z.boolean(),
canProduce: zod.z.boolean(),
canProduceSources: zod.z.object({
cam: zod.z.boolean(),
mic: zod.z.boolean(),
screen: zod.z.boolean()
}),
canSendData: zod.z.boolean(),
canRecvData: zod.z.boolean(),
canUpdateMetadata: zod.z.boolean()
}),
ttl: zod.z.number().optional(),
metadata: zod.z.string().optional(),
options: zod.z.object({
maxPeersAllowed: zod.z.number().optional()
}).optional()
});
var AccessToken = class {
apiKey;
roomId;
role;
/**
* Permissions for the token
*/
permissions;
/**
* Token Options object
*
* - `ttl`: Time to live for the token or expiration time. can be a number of seconds or a string describing a time span zeit/ms
* @example 6 * 60 * 60, "2 days", "10h", "7d"`
* @default 4h
*
* - `maxPeersAllowed`: Maximum number of peers allowed in the room (Optional)
*/
options = {
ttl: "4h"
};
/**
* custom app data for the peer
*/
metadata;
constructor(data) {
if (typeof document !== "undefined") {
logger.error(
"You should not include your API secret in your web client bundle.\n\nYour web client should request a token from your backend server which should then use "
);
}
if (!data.apiKey) {
throw new Error("api-key required");
}
if (!data.roomId) {
throw new Error("roomId required");
}
if (data.options?.metadata && chunkNJUEGIAO_cjs.estimateSize(data.options?.metadata) > chunkCGGVUTEY_cjs.MAX_METADATA_SIZE) {
throw new Error("Metadata size exceeds the limit of 5kb");
}
this.apiKey = data.apiKey;
this.roomId = data.roomId;
this.metadata = data.options?.metadata;
if (data.options?.ttl) this.options.ttl = data.options?.ttl;
if (data.options?.maxPeersAllowed !== void 0) {
if (typeof data.options.maxPeersAllowed !== "number" || data.options.maxPeersAllowed <= 0) {
throw new Error("maxPeersAllowed must be a positive number");
}
}
if (data.options?.maxPeersAllowed) {
this.options.maxPeersAllowed = data.options?.maxPeersAllowed;
}
if ("role" in data) {
if (isDefaultRole(data.role) && !("permissions" in data)) {
this.role = data.role;
this.permissions = ROLE_PERMISSIONS[data.role];
} else if (isDefaultRole(data.role) && "permissions" in data) {
this.role = data.role;
this.permissions = {
...ROLE_PERMISSIONS[data.role],
...data.permissions
};
} else if (typeof data.role === "string" && "permissions" in data) {
if (data.role.length > 20) {
throw new Error(`Custom role exceeds the limit of ${20} characters.`);
}
this.role = data.role;
this.permissions = {
...DEFAULT_PERMISSIONS,
...data.permissions
};
} else {
throw new Error(
`Permissions must be provided for custom role: ${data.role}.`
);
}
} else if ("permissions" in data) {
this.permissions = {
...DEFAULT_PERMISSIONS,
...data.permissions
};
} else {
throw new Error("Either a role or permissions must be provided.");
}
}
set updatePermissions(permissions) {
this.permissions = permissions;
}
set updateMetaData(data) {
if (data && chunkNJUEGIAO_cjs.estimateSize(data) > chunkCGGVUTEY_cjs.MAX_METADATA_SIZE) {
throw new Error("Metadata size exceeds the limit of 5kb");
}
this.metadata = data;
}
/**
* Generate a JWT token
* @returns JWT token
* @example
* ```typescript
* const accessToken = new AccessToken({...})
* accessToken.toJwt()
* ```
*/
async toJwt() {
const payload = {
roomId: this.roomId,
permissions: this.permissions,
role: this.role,
metadata: JSON.stringify(this.metadata)
};
if (this.options?.maxPeersAllowed) {
payload.options = {
maxPeersAllowed: this.options.maxPeersAllowed
};
}
const resp = await fetch(`${chunkCGGVUTEY_cjs.INFRA_URL}/api/v2/sdk/create-peer-token`, {
method: "POST",
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
"x-api-key": this.apiKey,
"Cache-Control": "no-store, max-age=0",
Pragma: "no-cache",
"x-sdk-version": chunkCGGVUTEY_cjs.SDK_VERSION
}
});
if (resp.status === 401) {
throw new Error("API key missing or invalid");
}
if (resp.status === 404) {
throw new Error("Room not found to be associated with the given API key");
}
const { token } = await resp.json();
return token;
}
};
var TokenVerifier = class {
JWKS;
constructor() {
this.JWKS = jose__namespace.createRemoteJWKSet(
new URL("https://huddle01.app/jwks.json")
);
}
/**
* Verify a JWT token
* @param token JWT token
* @returns decoded token
* @example
* ```typescript
* const verifier = new TokenVerifier()
* verifier.verify(token)
* ```
* @throws {jose.errors.JWTExpired} if the token is expired
* @throws {jose.errors.JWTClaimValidationFailed} if the token is invalid
*/
async verify(token) {
const { payload } = await jose__namespace.jwtVerify(token, this.JWKS);
return payload;
}
};
exports.AccessToken = AccessToken;
exports.DEFAULT_PERMISSIONS = DEFAULT_PERMISSIONS;
exports.MeetingTokenInputSchema = MeetingTokenInputSchema;
exports.Permissions = Permissions;
exports.ROLE_PERMISSIONS = ROLE_PERMISSIONS;
exports.Role = Role;
exports.RoleSchema = RoleSchema;
exports.TokenVerifier = TokenVerifier;
exports.isDefaultRole = isDefaultRole;