feat: 11642 app shimmer video (#12159)
* added initial app * created basic functionality for Shimmer Video app with tracking of Daily rooms * changed the type config value in the shimmer video config.json * re-fixed update to shimmer-video config type * updated static images for shimmer video app * fixed tracking Shimmer video event parameter * Add zod files * Allow query for "conferencing" apps * Move to shimmer video * Redirect to shimmer app * Remove console.logs * Remove legacy use of seed-app-store. --------- Co-authored-by: Peer Richelsen <peer@cal.com> Co-authored-by: Vik <vsreed@stanford.edu> Co-authored-by: pathaksarvesh <sarvesh@incrediblevisibility.com> Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: Hariom <hariombalhara@gmail.com>
This commit is contained in:
parent
09fc7e1a4c
commit
6848362683
|
@ -23,6 +23,7 @@ import { appKeysSchema as plausible_zod_ts } from "./plausible/zod";
|
|||
import { appKeysSchema as qr_code_zod_ts } from "./qr_code/zod";
|
||||
import { appKeysSchema as routing_forms_zod_ts } from "./routing-forms/zod";
|
||||
import { appKeysSchema as salesforce_zod_ts } from "./salesforce/zod";
|
||||
import { appKeysSchema as shimmervideo_zod_ts } from "./shimmervideo/zod";
|
||||
import { appKeysSchema as stripepayment_zod_ts } from "./stripepayment/zod";
|
||||
import { appKeysSchema as tandemvideo_zod_ts } from "./tandemvideo/zod";
|
||||
import { appKeysSchema as booking_pages_tag_zod_ts } from "./templates/booking-pages-tag/zod";
|
||||
|
@ -58,6 +59,7 @@ export const appKeysSchemas = {
|
|||
qr_code: qr_code_zod_ts,
|
||||
"routing-forms": routing_forms_zod_ts,
|
||||
salesforce: salesforce_zod_ts,
|
||||
shimmervideo: shimmervideo_zod_ts,
|
||||
stripe: stripepayment_zod_ts,
|
||||
tandemvideo: tandemvideo_zod_ts,
|
||||
"booking-pages-tag": booking_pages_tag_zod_ts,
|
||||
|
|
|
@ -47,6 +47,7 @@ import riverside_config_json from "./riverside/config.json";
|
|||
import routing_forms_config_json from "./routing-forms/config.json";
|
||||
import salesforce_config_json from "./salesforce/config.json";
|
||||
import sendgrid_config_json from "./sendgrid/config.json";
|
||||
import shimmervideo_config_json from "./shimmervideo/config.json";
|
||||
import signal_config_json from "./signal/config.json";
|
||||
import sirius_video_config_json from "./sirius_video/config.json";
|
||||
import skiff_config_json from "./skiff/config.json";
|
||||
|
@ -121,6 +122,7 @@ export const appStoreMetadata = {
|
|||
"routing-forms": routing_forms_config_json,
|
||||
salesforce: salesforce_config_json,
|
||||
sendgrid: sendgrid_config_json,
|
||||
shimmervideo: shimmervideo_config_json,
|
||||
signal: signal_config_json,
|
||||
sirius_video: sirius_video_config_json,
|
||||
skiff: skiff_config_json,
|
||||
|
|
|
@ -23,6 +23,7 @@ import { appDataSchema as plausible_zod_ts } from "./plausible/zod";
|
|||
import { appDataSchema as qr_code_zod_ts } from "./qr_code/zod";
|
||||
import { appDataSchema as routing_forms_zod_ts } from "./routing-forms/zod";
|
||||
import { appDataSchema as salesforce_zod_ts } from "./salesforce/zod";
|
||||
import { appDataSchema as shimmervideo_zod_ts } from "./shimmervideo/zod";
|
||||
import { appDataSchema as stripepayment_zod_ts } from "./stripepayment/zod";
|
||||
import { appDataSchema as tandemvideo_zod_ts } from "./tandemvideo/zod";
|
||||
import { appDataSchema as booking_pages_tag_zod_ts } from "./templates/booking-pages-tag/zod";
|
||||
|
@ -58,6 +59,7 @@ export const appDataSchemas = {
|
|||
qr_code: qr_code_zod_ts,
|
||||
"routing-forms": routing_forms_zod_ts,
|
||||
salesforce: salesforce_zod_ts,
|
||||
shimmervideo: shimmervideo_zod_ts,
|
||||
stripe: stripepayment_zod_ts,
|
||||
tandemvideo: tandemvideo_zod_ts,
|
||||
"booking-pages-tag": booking_pages_tag_zod_ts,
|
||||
|
|
|
@ -47,6 +47,7 @@ export const apiHandlers = {
|
|||
"routing-forms": import("./routing-forms/api"),
|
||||
salesforce: import("./salesforce/api"),
|
||||
sendgrid: import("./sendgrid/api"),
|
||||
shimmervideo: import("./shimmervideo/api"),
|
||||
signal: import("./signal/api"),
|
||||
sirius_video: import("./sirius_video/api"),
|
||||
skiff: import("./skiff/api"),
|
||||
|
|
|
@ -21,6 +21,7 @@ import office365video_config_json from "./office365video/config.json";
|
|||
import ping_config_json from "./ping/config.json";
|
||||
import plausible_config_json from "./plausible/config.json";
|
||||
import riverside_config_json from "./riverside/config.json";
|
||||
import shimmervideo_config_json from "./shimmervideo/config.json";
|
||||
import signal_config_json from "./signal/config.json";
|
||||
import sirius_video_config_json from "./sirius_video/config.json";
|
||||
import sylapsvideo_config_json from "./sylapsvideo/config.json";
|
||||
|
@ -53,6 +54,7 @@ export const appStoreMetadata = {
|
|||
ping: ping_config_json,
|
||||
plausible: plausible_config_json,
|
||||
riverside: riverside_config_json,
|
||||
shimmervideo: shimmervideo_config_json,
|
||||
signal: signal_config_json,
|
||||
sirius_video: sirius_video_config_json,
|
||||
sylapsvideo: sylapsvideo_config_json,
|
||||
|
|
|
@ -37,6 +37,7 @@ const appStore = {
|
|||
"zoho-bigin": () => import("./zoho-bigin"),
|
||||
basecamp3: () => import("./basecamp3"),
|
||||
telegramvideo: () => import("./telegram"),
|
||||
shimmervideo: () => import("./shimmervideo"),
|
||||
};
|
||||
|
||||
export default appStore;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
items:
|
||||
- 1.jpeg
|
||||
- 2.jpeg
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
|
@ -0,0 +1,16 @@
|
|||
import { createDefaultInstallation } from "@calcom/app-store/_utils/installation";
|
||||
import type { AppDeclarativeHandler } from "@calcom/types/AppHandler";
|
||||
|
||||
import appConfig from "../config.json";
|
||||
|
||||
const handler: AppDeclarativeHandler = {
|
||||
appType: appConfig.type,
|
||||
variant: appConfig.variant,
|
||||
slug: appConfig.slug,
|
||||
supportsMultipleInstalls: false,
|
||||
handlerType: "add",
|
||||
createCredential: ({ appType, user, slug, teamId }) =>
|
||||
createDefaultInstallation({ appType, userId: user.id, slug, key: {}, teamId }),
|
||||
};
|
||||
|
||||
export default handler;
|
|
@ -0,0 +1 @@
|
|||
export { default as add } from "./add";
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Shimmer Video",
|
||||
"slug": "shimmervideo",
|
||||
"type": "shimmer_video",
|
||||
"logo": "icon.png",
|
||||
"url": "https://shimmer.care",
|
||||
"variant": "conferencing",
|
||||
"categories": ["conferencing"],
|
||||
"publisher": "Shimmer.care",
|
||||
"email": "support@shimmer.care",
|
||||
"description": "The #1 Expert ADHD Coach. Weekly calls and in-app support so that you can reach your full potential",
|
||||
"isTemplate": false,
|
||||
"__createdUsingCli": true,
|
||||
"__template": "basic",
|
||||
"appData": {
|
||||
"location": {
|
||||
"linkType": "dynamic",
|
||||
"type": "integrations:shimmer_video",
|
||||
"label": "Shimmer Video"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * as api from "./api";
|
||||
export * as lib from "./lib";
|
|
@ -0,0 +1,195 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { handleErrorsJson } from "@calcom/lib/errors";
|
||||
import type { GetRecordingsResponseSchema, GetAccessLinkResponseSchema } from "@calcom/prisma/zod-utils";
|
||||
import { getRecordingsResponseSchema, getAccessLinkResponseSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
import { getShimmerAppKeys } from "./getShimmerAppKeys";
|
||||
|
||||
/** Shimmer Video app type in the config.json
|
||||
* changed to 'shimmer_video' to support video conferencing
|
||||
*/
|
||||
|
||||
/** @link https://docs.daily.co/reference/rest-api/rooms/create-room */
|
||||
const dailyReturnTypeSchema = z.object({
|
||||
/** Long UID string ie: 987b5eb5-d116-4a4e-8e2c-14fcb5710966 */
|
||||
id: z.string(),
|
||||
/** Not a real name, just a random generated string ie: "ePR84NQ1bPigp79dDezz" */
|
||||
name: z.string(),
|
||||
api_created: z.boolean(),
|
||||
privacy: z.union([z.literal("private"), z.literal("public")]),
|
||||
/** https://api-demo.daily.co/ePR84NQ1bPigp79dDezz */
|
||||
url: z.string(),
|
||||
created_at: z.string(),
|
||||
config: z.object({
|
||||
enable_prejoin_ui: z.boolean(),
|
||||
enable_people_ui: z.boolean(),
|
||||
enable_emoji_reactions: z.boolean(),
|
||||
enable_pip_ui: z.boolean(),
|
||||
enable_hand_raising: z.boolean(),
|
||||
enable_network_ui: z.boolean(),
|
||||
enable_video_processing_ui: z.boolean(),
|
||||
enable_noise_cancellation_ui: z.boolean(),
|
||||
enable_advanced_chat: z.boolean(),
|
||||
//above flags are for prebuilt daily
|
||||
enable_chat: z.boolean(),
|
||||
enable_knocking: z.boolean(),
|
||||
}),
|
||||
});
|
||||
|
||||
export interface DailyEventResult {
|
||||
id: string;
|
||||
name: string;
|
||||
api_created: boolean;
|
||||
privacy: string;
|
||||
url: string;
|
||||
created_at: string;
|
||||
config: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface DailyVideoCallData {
|
||||
type: string;
|
||||
id: string;
|
||||
password: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export const fetcher = async (endpoint: string, init?: RequestInit | undefined) => {
|
||||
const { api_key } = await getShimmerAppKeys();
|
||||
const response = await fetch(`https://api.daily.co/v1${endpoint}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${api_key}`,
|
||||
"Content-Type": "application/json",
|
||||
...init?.headers,
|
||||
},
|
||||
...init,
|
||||
}).then(handleErrorsJson);
|
||||
return response;
|
||||
};
|
||||
|
||||
export const fetcherShimmer = async (endpoint: string, init?: RequestInit | undefined) => {
|
||||
const { api_key, api_route } = await getShimmerAppKeys();
|
||||
|
||||
if (!api_route) {
|
||||
//if no api_route, then we wont push to shimmer
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
const response = await fetch(`${api_route}${endpoint}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
Authorization: `Bearer ${api_key}`,
|
||||
"Content-Type": "application/json",
|
||||
...init?.headers,
|
||||
},
|
||||
...init,
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
export const postToShimmerAPI = async (
|
||||
event: CalendarEvent,
|
||||
endpoint: string,
|
||||
body: Record<string, unknown>
|
||||
) => {
|
||||
return fetcherShimmer(endpoint, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
cal: event,
|
||||
daily: body,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
function postToDailyAPI(endpoint: string, body: Record<string, unknown>) {
|
||||
return fetcher(endpoint, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
}
|
||||
|
||||
const ShimmerDailyVideoApiAdapter = (): VideoApiAdapter => {
|
||||
async function createOrUpdateMeeting(endpoint: string, event: CalendarEvent): Promise<VideoCallData> {
|
||||
if (!event.uid) {
|
||||
throw new Error("We need need the booking uid to create the Daily reference in DB");
|
||||
}
|
||||
const body = await translateEvent();
|
||||
const dailyEvent = await postToDailyAPI(endpoint, body).then(dailyReturnTypeSchema.parse);
|
||||
// const meetingToken = await postToDailyAPI("/meeting-tokens", {
|
||||
// properties: { room_name: dailyEvent.name, exp: dailyEvent.config.exp, is_owner: true },
|
||||
// }).then(meetingTokenSchema.parse);
|
||||
await postToShimmerAPI(event, "trackDailyRoom", dailyEvent);
|
||||
|
||||
return Promise.resolve({
|
||||
type: "shimmer_video",
|
||||
id: dailyEvent.name,
|
||||
password: "",
|
||||
// password: meetingToken.token,
|
||||
url: `https://app.shimmer.care?videoId=${dailyEvent.name}`,
|
||||
});
|
||||
}
|
||||
|
||||
const translateEvent = async () => {
|
||||
return {
|
||||
privacy: "private",
|
||||
properties: {
|
||||
enable_prejoin_ui: true,
|
||||
enable_people_ui: true,
|
||||
enable_emoji_reactions: true,
|
||||
enable_pip_ui: true,
|
||||
enable_hand_raising: true,
|
||||
enable_network_ui: true,
|
||||
enable_video_processing_ui: true,
|
||||
enable_noise_cancellation_ui: true,
|
||||
enable_advanced_chat: true,
|
||||
//above flags are for prebuilt daily
|
||||
enable_knocking: true,
|
||||
enable_screenshare: true,
|
||||
enable_chat: true,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
/** Daily doesn't need to return busy times, so we return empty */
|
||||
getAvailability: () => {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
createMeeting: async (event: CalendarEvent): Promise<VideoCallData> =>
|
||||
createOrUpdateMeeting("/rooms", event),
|
||||
deleteMeeting: async (uid: string): Promise<void> => {
|
||||
await fetcher(`/rooms/${uid}`, { method: "DELETE" });
|
||||
return Promise.resolve();
|
||||
},
|
||||
updateMeeting: (bookingRef: PartialReference, event: CalendarEvent): Promise<VideoCallData> =>
|
||||
createOrUpdateMeeting(`/rooms/${bookingRef.uid}`, event),
|
||||
getRecordings: async (roomName: string): Promise<GetRecordingsResponseSchema> => {
|
||||
try {
|
||||
const res = await fetcher(`/recordings?room_name=${roomName}`).then(
|
||||
getRecordingsResponseSchema.parse
|
||||
);
|
||||
return Promise.resolve(res);
|
||||
} catch (err) {
|
||||
throw new Error("Something went wrong! Unable to get recording");
|
||||
}
|
||||
},
|
||||
getRecordingDownloadLink: async (recordingId: string): Promise<GetAccessLinkResponseSchema> => {
|
||||
try {
|
||||
const res = await fetcher(`/recordings/${recordingId}/access-link?valid_for_secs=172800`).then(
|
||||
getAccessLinkResponseSchema.parse
|
||||
);
|
||||
return Promise.resolve(res);
|
||||
} catch (err) {
|
||||
console.log("err", err);
|
||||
throw new Error("Something went wrong! Unable to get recording access link");
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default ShimmerDailyVideoApiAdapter;
|
|
@ -0,0 +1,13 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
|
||||
const shimmerAppKeysSchema = z.object({
|
||||
api_key: z.string(),
|
||||
api_route: z.string(),
|
||||
});
|
||||
|
||||
export const getShimmerAppKeys = async () => {
|
||||
const appKeys = await getAppKeysFromSlug("shimmer-video");
|
||||
return shimmerAppKeysSchema.parse(appKeys);
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export { default as VideoApiAdapter } from "./VideoApiAdapter";
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/shimmer-video",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "The #1 Expert ADHD Coach. Weekly calls and in-app support so that you can reach your full potential"
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.1 KiB |
|
@ -0,0 +1,8 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const appKeysSchema = z.object({
|
||||
api_key: z.string(),
|
||||
api_route: z.string(),
|
||||
});
|
||||
|
||||
export const appDataSchema = z.object({});
|
|
@ -97,7 +97,7 @@ export default class EventManager {
|
|||
// (type closecom_other_calendar)
|
||||
this.calendarCredentials = appCredentials.filter((cred) => cred.type.endsWith("_calendar"));
|
||||
this.videoCredentials = appCredentials
|
||||
.filter((cred) => cred.type.endsWith("_video"))
|
||||
.filter((cred) => cred.type.endsWith("_video") || cred.type.endsWith("_conferencing"))
|
||||
// Whenever a new video connection is added, latest credentials are added with the highest ID.
|
||||
// Because you can't rely on having them in the highest first order here, ensure this by sorting in DESC order
|
||||
// We also don't have updatedAt or createdAt dates on credentials so this is the best we can do
|
||||
|
@ -654,7 +654,6 @@ export default class EventManager {
|
|||
*/
|
||||
private async createVideoEvent(event: CalendarEvent) {
|
||||
const credential = this.getVideoCredential(event);
|
||||
|
||||
if (credential) {
|
||||
return createMeeting(credential, event);
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue
Block a user