chore: Platform SDK (#14154)
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
git: {
|
||||
commitMessage: "chore: SDK Release v${version}",
|
||||
requireUpstream: true,
|
||||
requireCommits: true,
|
||||
requireBranch: "feat/platform-sdk",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
<p align="center">
|
||||
<a href="https://github.com/calcom/cal.com">
|
||||
<img src="https://user-images.githubusercontent.com/8019099/210054112-5955e812-a76e-4160-9ddd-58f2c72f1cce.png" alt="Logo">
|
||||
</a>
|
||||
<br/>
|
||||
<strong>Cal.com Atoms SDK</strong>
|
||||
</p>
|
||||
@@ -0,0 +1,54 @@
|
||||
import { CalSdk } from "../src/cal";
|
||||
|
||||
(async () => {
|
||||
const sdk = new CalSdk(
|
||||
"cltd1rit60001p51en7z0sq75",
|
||||
{
|
||||
accessToken:
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoiYWNjZXNzX3Rva2VuIiwiY2xpZW50SWQiOiJjbHRkMXJpdDYwMDAxcDUxZW43ejBzcTc1Iiwib3duZXJJZCI6OSwiaWF0IjoxNzA5NTYzNTMyfQ.tpUSk0gREAZPJA2aXnHPpVc-9Bbcj2w90kVH2Qii6RE",
|
||||
clientSecret:
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCBjbGllbnQiLCJwZXJtaXNzaW9ucyI6MjU1LCJyZWRpcmVjdFVyaXMiOlsiaHR0cDovL2xvY2FsaG9zdDozMDAwL2F1dGgvcGxhdGZvcm0vYXV0aG9yaXplIl0sImlhdCI6MTcwOTU2MzE1N30.j8cxam5pfPG45BAMCuXt7bm3GM7JO_UnWL9wPVGcr5U",
|
||||
refreshToken:
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicmVmcmVzaF90b2tlbiIsImNsaWVudElkIjoiY2x0ZDFyaXQ2MDAwMXA1MWVuN3owc3E3NSIsIm93bmVySWQiOjksImlhdCI6MTcwOTU2MzUzMn0.SHMikF_9uJvqJFAiWAnI0BsRQSJCTbaAXPi1B99sWNk",
|
||||
},
|
||||
{
|
||||
baseUrl: "http://localhost:5555/api",
|
||||
handleRefresh: true,
|
||||
httpRetries: {
|
||||
maxAmount: 5,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
const oauth = await sdk.oauth.exchange({
|
||||
authorizationCode: "cltd1wlqn0002p51ektm2byp6",
|
||||
});
|
||||
|
||||
console.log("finalized oauth", oauth.accessToken);
|
||||
|
||||
// force updating
|
||||
sdk.secrets().updateAccessToken(oauth.accessToken, oauth.refreshToken);
|
||||
|
||||
const schedule = await sdk.schedules.createSchedule({
|
||||
availabilities: [
|
||||
{
|
||||
days: [1, 2, 3, 4, 5, 6],
|
||||
startTime: "10:00:00",
|
||||
endTime: "14:00:00",
|
||||
},
|
||||
],
|
||||
isDefault: true,
|
||||
name: "Default Schedule Test",
|
||||
timeZone: "America/Argentina/Buenos_Aires",
|
||||
});
|
||||
|
||||
console.log(schedule);
|
||||
|
||||
const deleted = await sdk.schedules.deleteSchedule(schedule.id);
|
||||
|
||||
console.log(deleted);
|
||||
} catch (err) {
|
||||
console.log("error", err);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "@calcom/sdk",
|
||||
"version": "0.0.1",
|
||||
"description": "Cal.com platform SDK.",
|
||||
"main": "dist/index.cjs.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rollup -c --bundleConfigAsCjs",
|
||||
"release": "release-it --only-version"
|
||||
},
|
||||
"packageManager": "yarn@3.4.1",
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-typescript": "^11.1.6",
|
||||
"release-it": "^17.1.1",
|
||||
"rollup": "^4.12.0",
|
||||
"rollup-plugin-bundle-size": "^1.0.3",
|
||||
"ts-essentials": "^9.4.1",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.7",
|
||||
"axios-retry": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"zod": "^3.22.4"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import commonjs from "@rollup/plugin-commonjs";
|
||||
import json from "@rollup/plugin-json";
|
||||
import { nodeResolve } from "@rollup/plugin-node-resolve";
|
||||
import typescript from "@rollup/plugin-typescript";
|
||||
import bundleSize from "rollup-plugin-bundle-size";
|
||||
|
||||
import pkg from "./package.json";
|
||||
|
||||
/** @type {import('rollup').RollupOptions} */
|
||||
// eslint-disable-next-line import/no-anonymous-default-export
|
||||
export default {
|
||||
input: "src/index.ts",
|
||||
output: [
|
||||
{
|
||||
file: pkg.main,
|
||||
format: "cjs",
|
||||
},
|
||||
{
|
||||
file: pkg.module,
|
||||
format: "es",
|
||||
},
|
||||
],
|
||||
plugins: [
|
||||
json(),
|
||||
commonjs(),
|
||||
nodeResolve(),
|
||||
typescript({ tsconfig: "./tsconfig.build.json" }),
|
||||
bundleSize(),
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
import axios from "axios";
|
||||
import axiosRetry from "axios-retry";
|
||||
|
||||
import { Bookings } from "./endpoints/bookings";
|
||||
import { Events } from "./endpoints/events";
|
||||
import { EventTypes } from "./endpoints/events/event-types";
|
||||
import { OAuthFlow } from "./endpoints/oauth-flow";
|
||||
import { Schedules } from "./endpoints/schedules";
|
||||
import { Slots } from "./endpoints/slots";
|
||||
import { SdkInitializationError } from "./lib/errors/sdk-initialization-error";
|
||||
import { HttpCaller } from "./lib/http-caller";
|
||||
import { SdkSecrets } from "./lib/sdk-secrets";
|
||||
import type { CalSdkConstructorOptions, SdkAuthOptions } from "./types";
|
||||
|
||||
export class CalSdk {
|
||||
public httpCaller: HttpCaller;
|
||||
|
||||
slots: Slots;
|
||||
bookings: Bookings;
|
||||
events: Events;
|
||||
oauth: OAuthFlow;
|
||||
eventTypes: EventTypes;
|
||||
schedules: Schedules;
|
||||
|
||||
private _secrets: SdkSecrets;
|
||||
|
||||
constructor(
|
||||
public readonly clientId: string,
|
||||
protected readonly authOptions: SdkAuthOptions,
|
||||
protected readonly options: CalSdkConstructorOptions = {
|
||||
baseUrl: "https://api.cal.com/", // don't set api version here as endpoints may have version-neutral or specific values.
|
||||
}
|
||||
) {
|
||||
if (!authOptions.accessToken && !authOptions.clientSecret) {
|
||||
throw new SdkInitializationError("Either 'accessToken' or 'clientSecret' are required in authOptions");
|
||||
}
|
||||
|
||||
this.httpCaller = new HttpCaller(this.clientId, this._createAxiosClientBase());
|
||||
this._secrets = new SdkSecrets(
|
||||
authOptions.clientSecret ?? "",
|
||||
authOptions.accessToken ?? "",
|
||||
authOptions.refreshToken ?? "",
|
||||
this.httpCaller
|
||||
);
|
||||
|
||||
// avoid cyclic referencing.
|
||||
this.httpCaller.secrets = this._secrets;
|
||||
|
||||
this.slots = new Slots(this);
|
||||
this.bookings = new Bookings(this);
|
||||
this.events = new Events(this);
|
||||
this.oauth = new OAuthFlow(this);
|
||||
this.eventTypes = new EventTypes(this);
|
||||
this.schedules = new Schedules(this);
|
||||
}
|
||||
|
||||
private _createAxiosClientBase() {
|
||||
const axiosClient = axios.create({
|
||||
baseURL: this.options.baseUrl,
|
||||
});
|
||||
|
||||
// implement retry logic with an exponential back-off delay
|
||||
axiosRetry(axiosClient, {
|
||||
retries: this.options.httpRetries?.maxAmount ?? 3,
|
||||
retryDelay: axiosRetry.exponentialDelay,
|
||||
retryCondition: axiosRetry.isNetworkOrIdempotentRequestError,
|
||||
});
|
||||
|
||||
return axiosClient;
|
||||
}
|
||||
|
||||
public secrets() {
|
||||
return this._secrets;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { CalSdk } from "../../cal";
|
||||
import { EndpointHandler } from "../endpoint-handler";
|
||||
|
||||
export class Bookings extends EndpointHandler {
|
||||
constructor(private readonly sdk: CalSdk) {
|
||||
super("bookings", sdk);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { AxiosRequestConfig } from "axios";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import merge from "lodash/merge";
|
||||
import { assert } from "ts-essentials";
|
||||
|
||||
import type { CalSdk } from "../cal";
|
||||
|
||||
export abstract class EndpointHandler {
|
||||
constructor(private readonly key: string, private readonly calSdk: CalSdk) {}
|
||||
|
||||
withForAtomParam(forAtom: boolean, config?: AxiosRequestConfig<unknown>) {
|
||||
if (!forAtom) return config;
|
||||
|
||||
return merge(config, {
|
||||
params: {
|
||||
for: "atom",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
protected assertAccessToken(methodName: string) {
|
||||
assert(
|
||||
this.calSdk.secrets().isAccessTokenSet(),
|
||||
`Access token must be set to call the ${this.key}/${methodName} function.`
|
||||
);
|
||||
}
|
||||
|
||||
protected assertClientSecret(methodName: string) {
|
||||
assert(
|
||||
!!this.calSdk.secrets().getClientSecret(),
|
||||
`Client secret must be set to use the ${this.key}/${methodName} function.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { CalSdk } from "../../../cal";
|
||||
import { Endpoints } from "../../../lib/endpoints";
|
||||
import type { BasicPlatformResponse } from "../../../types";
|
||||
import { EndpointHandler } from "../../endpoint-handler";
|
||||
import type { CreateEventTypeArgs, EventType, GetEventTypeByIdArgs } from "./types";
|
||||
|
||||
type BodyWithEventTypeResponse = {
|
||||
eventType: EventType;
|
||||
};
|
||||
|
||||
export class EventTypes extends EndpointHandler {
|
||||
constructor(private readonly sdk: CalSdk) {
|
||||
super("event-types", sdk);
|
||||
}
|
||||
|
||||
async createEventType(args: CreateEventTypeArgs): Promise<EventType> {
|
||||
this.assertAccessToken("createEventType");
|
||||
|
||||
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<BodyWithEventTypeResponse>>(
|
||||
Endpoints.CREATE_EVENT_TYPE,
|
||||
{
|
||||
body: args,
|
||||
}
|
||||
);
|
||||
|
||||
return data.eventType;
|
||||
}
|
||||
|
||||
async getEventType(args: GetEventTypeByIdArgs): Promise<EventType> {
|
||||
this.assertAccessToken("getEventType");
|
||||
|
||||
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<BodyWithEventTypeResponse>>(
|
||||
Endpoints.GET_EVENT_TYPE_BY_ID,
|
||||
{
|
||||
urlParams: [args.id],
|
||||
config: this.withForAtomParam(args.forAtom ?? false),
|
||||
}
|
||||
);
|
||||
|
||||
return data.eventType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const CreateEventTypesSchema = z.object({
|
||||
length: z.number().min(1),
|
||||
slug: z.string(),
|
||||
title: z.string(),
|
||||
});
|
||||
|
||||
const GetEventTypeByIdSchema = z.object({
|
||||
id: z.string(),
|
||||
forAtom: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export type CreateEventTypeArgs = z.infer<typeof CreateEventTypesSchema>;
|
||||
export type GetEventTypeByIdArgs = z.infer<typeof GetEventTypeByIdSchema>;
|
||||
|
||||
export type EventType = {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
description: string | null;
|
||||
position: number;
|
||||
locations: string[] | null;
|
||||
length: number;
|
||||
offsetStart: number;
|
||||
hidden: boolean;
|
||||
userId: number;
|
||||
profileId: number | null;
|
||||
teamId: number | null;
|
||||
eventName: string | null;
|
||||
parentId: number | null;
|
||||
bookingFields: unknown | null;
|
||||
timeZone: string | null;
|
||||
periodType: "UNLIMITED" | "ROLLING" | "RANGE";
|
||||
periodStartDate: string | null;
|
||||
periodEndDate: string | null;
|
||||
periodDays: number | null;
|
||||
periodCountCalendarDays: boolean | null;
|
||||
lockTimeZoneToggleOnBookingPage: boolean;
|
||||
requiresConfirmation: boolean;
|
||||
requiresBookerEmailVerification: boolean;
|
||||
recurringEvent?: boolean;
|
||||
disableGuests: boolean;
|
||||
hideCalendarNotes: boolean;
|
||||
minimumBookingNotice: number;
|
||||
beforeEventBuffer: number;
|
||||
afterEventBuffer: number;
|
||||
seatsPerTimeSlot: number | null;
|
||||
onlyShowFirstAvailableSlot: boolean;
|
||||
seatsShowAttendees: boolean;
|
||||
seatsShowAvailabilityCount: boolean;
|
||||
schedulingType: unknown | null;
|
||||
scheduleId: number | null;
|
||||
price: number;
|
||||
currency: "usd";
|
||||
slotInterval: number | null;
|
||||
metadata: Record<string, unknown> | null;
|
||||
successRedirectUrl: string | null;
|
||||
bookingLimits: number | null;
|
||||
durationLimits: number | null;
|
||||
isInstantEvent: boolean;
|
||||
assignAllTeamMembers: boolean;
|
||||
useEventTypeDestinationCalendarEmail: boolean;
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { CalSdk } from "../../cal";
|
||||
import { Endpoints } from "../../lib/endpoints";
|
||||
import type { BasicPlatformResponse } from "../../types";
|
||||
import { EndpointHandler } from "../endpoint-handler";
|
||||
import type { Event, GetPublicEventArgs } from "./types";
|
||||
|
||||
export class Events extends EndpointHandler {
|
||||
constructor(private readonly sdk: CalSdk) {
|
||||
super("events", sdk);
|
||||
}
|
||||
|
||||
async getPublicEvent(input: GetPublicEventArgs): Promise<Event> {
|
||||
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<Event>>(Endpoints.GET_PUBLIC_EVENT, {
|
||||
config: {
|
||||
params: input,
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const GetPublicEventSchema = z.object({
|
||||
username: z.string().transform((s) => s.toLowerCase()),
|
||||
eventSlug: z.string(),
|
||||
isTeamEvent: z.boolean().optional(),
|
||||
org: z.string().optional(),
|
||||
});
|
||||
|
||||
export type GetPublicEventArgs = z.infer<typeof GetPublicEventSchema>;
|
||||
|
||||
type User = {
|
||||
// todo expand
|
||||
};
|
||||
|
||||
type TeamParent = {
|
||||
slug: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
type Team = {
|
||||
parentId: string;
|
||||
metadata: Record<string, unknown>;
|
||||
brandColor: string;
|
||||
darkBrandColor: string;
|
||||
slug: string;
|
||||
name: string;
|
||||
logo: string;
|
||||
theme: string;
|
||||
parent: TeamParent;
|
||||
};
|
||||
|
||||
type WorkflowStep = {
|
||||
// todo expand
|
||||
};
|
||||
|
||||
type Workflow = {
|
||||
steps: WorkflowStep[];
|
||||
};
|
||||
|
||||
export type Event = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
eventName: string;
|
||||
slug: string;
|
||||
isInstantEvent: boolean;
|
||||
schedulingType: string;
|
||||
length: number;
|
||||
locations: string[]; // Define more specifically if possible
|
||||
customInputs: unknown; // Define more specifically if possible
|
||||
disableGuests: boolean;
|
||||
lockTimeZoneToggleOnBookingPage: boolean;
|
||||
requiresConfirmation: boolean;
|
||||
requiresBookerEmailVerification: boolean;
|
||||
recurringEvent: boolean;
|
||||
price: number;
|
||||
currency: string;
|
||||
seatsPerTimeSlot: number;
|
||||
seatsShowAvailabilityCount: boolean;
|
||||
bookingFields: unknown; // Define more specifically if possible
|
||||
team: Team;
|
||||
successRedirectUrl: string;
|
||||
workflows: Workflow[];
|
||||
hosts: {
|
||||
user: User;
|
||||
}[];
|
||||
owner: User;
|
||||
hidden: boolean;
|
||||
assignAllTeamMembers: boolean;
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import type { CalSdk } from "../../cal";
|
||||
import { Endpoints } from "../../lib/endpoints";
|
||||
import type { BasicPlatformResponse } from "../../types";
|
||||
import { EndpointHandler } from "../endpoint-handler";
|
||||
import type { ExchangeCodeParams, ExchangeCodeResponse } from "./types";
|
||||
|
||||
export class OAuthFlow extends EndpointHandler {
|
||||
constructor(private readonly sdk: CalSdk) {
|
||||
super("oauth", sdk);
|
||||
}
|
||||
|
||||
async exchange(params: ExchangeCodeParams): Promise<ExchangeCodeResponse> {
|
||||
this.assertClientSecret("exchange");
|
||||
|
||||
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<ExchangeCodeResponse>>(
|
||||
Endpoints.EXCHANGE_OAUTH_AUTH_TOKEN,
|
||||
{
|
||||
urlParams: [this.sdk.clientId],
|
||||
body: {
|
||||
clientSecret: this.sdk.secrets().getClientSecret(),
|
||||
},
|
||||
config: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${params.authorizationCode}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const ExchangeCodeSchema = z.object({
|
||||
authorizationCode: z.string(),
|
||||
});
|
||||
|
||||
export type ExchangeCodeParams = z.infer<typeof ExchangeCodeSchema>;
|
||||
|
||||
export type ExchangeCodeResponse = {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
};
|
||||
@@ -0,0 +1,106 @@
|
||||
import type { CalSdk } from "../../cal";
|
||||
import { Endpoints } from "../../lib/endpoints";
|
||||
import type { BasicPlatformResponse } from "../../types";
|
||||
import { EndpointHandler } from "../endpoint-handler";
|
||||
import type {
|
||||
CreateScheduleArgs,
|
||||
FormattedSchedule,
|
||||
GetScheduleByIdArgs,
|
||||
Schedule,
|
||||
SupportedTimezone,
|
||||
UpdateScheduleArgs,
|
||||
} from "./types";
|
||||
|
||||
export class Schedules extends EndpointHandler {
|
||||
constructor(private readonly sdk: CalSdk) {
|
||||
super("schedules", sdk);
|
||||
}
|
||||
|
||||
async createSchedule(args: CreateScheduleArgs): Promise<Schedule> {
|
||||
this.assertAccessToken("createSchedule");
|
||||
|
||||
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<Schedule>>(
|
||||
Endpoints.CREATE_SCHEDULE,
|
||||
{
|
||||
body: args,
|
||||
config: this.withForAtomParam(args.forAtom ?? false),
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getDefaultSchedule(forAtom?: boolean): Promise<Schedule | FormattedSchedule> {
|
||||
this.assertAccessToken("getDefaultSchedule");
|
||||
|
||||
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<Schedule | FormattedSchedule>>(
|
||||
Endpoints.GET_DEFAULT_SCHEDULE,
|
||||
{
|
||||
config: this.withForAtomParam(forAtom ?? false),
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getSchedules(forAtom?: boolean): Promise<Schedule[] | FormattedSchedule[]> {
|
||||
this.assertAccessToken("getSchedules");
|
||||
|
||||
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<Schedule[] | FormattedSchedule[]>>(
|
||||
Endpoints.GET_ALL_SCHEDULES,
|
||||
{
|
||||
config: this.withForAtomParam(forAtom ?? false),
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getScheduleById(args: GetScheduleByIdArgs): Promise<Schedule | FormattedSchedule> {
|
||||
this.assertAccessToken("getScheduleById");
|
||||
|
||||
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<Schedule | FormattedSchedule>>(
|
||||
Endpoints.GET_SCHEDULE_BY_ID,
|
||||
{
|
||||
urlParams: [args.id.toString()],
|
||||
config: this.withForAtomParam(args.forAtom ?? false),
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async getSupportedTimezones(): Promise<SupportedTimezone[]> {
|
||||
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<SupportedTimezone[]>>(
|
||||
Endpoints.GET_SUPPORTED_TIMEZONES
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async updateSchedule(scheduleId: number, args: UpdateScheduleArgs) {
|
||||
this.assertAccessToken("updateSchedule");
|
||||
|
||||
const { data } = await this.sdk.httpCaller.patch<BasicPlatformResponse<Schedule>>(
|
||||
Endpoints.UPDATE_SCHEDULE_BY_ID,
|
||||
{
|
||||
urlParams: [scheduleId.toString()],
|
||||
body: args,
|
||||
}
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
async deleteSchedule(scheduleId: number): Promise<boolean> {
|
||||
this.assertAccessToken("deleteSchedule");
|
||||
|
||||
const { status } = await this.sdk.httpCaller.delete<BasicPlatformResponse>(
|
||||
Endpoints.DELETE_SCHEDULE_BY_ID,
|
||||
{
|
||||
urlParams: [scheduleId.toString()],
|
||||
}
|
||||
);
|
||||
|
||||
return status === "success";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { ElementOf } from "ts-essentials";
|
||||
import { z } from "zod";
|
||||
|
||||
const CreateScheduleSchema = z.object({
|
||||
name: z.string(),
|
||||
timeZone: z.string(),
|
||||
isDefault: z.boolean().default(true),
|
||||
availabilities: z
|
||||
.object({
|
||||
days: z.number().array(),
|
||||
startTime: z.string(),
|
||||
endTime: z.string(),
|
||||
})
|
||||
.array(),
|
||||
forAtom: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const GetScheduleByIdSchema = z.object({
|
||||
id: z.number(),
|
||||
forAtom: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const UpdateScheduleSchema = z.object({
|
||||
timeZone: z.string().optional(),
|
||||
name: z.string().optional(),
|
||||
isDefault: z.boolean().optional(),
|
||||
schedule: z
|
||||
.object({
|
||||
start: z.date(),
|
||||
end: z.date(),
|
||||
})
|
||||
.array()
|
||||
.optional(),
|
||||
dateOverrides: z.object({
|
||||
ranges: z.object({}),
|
||||
}),
|
||||
});
|
||||
|
||||
export type CreateScheduleArgs = z.infer<typeof CreateScheduleSchema>;
|
||||
export type GetScheduleByIdArgs = z.infer<typeof GetScheduleByIdSchema>;
|
||||
export type UpdateScheduleArgs = z.infer<typeof UpdateScheduleSchema>;
|
||||
|
||||
export type Schedule = CreateScheduleArgs & {
|
||||
id: number;
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export type FormattedSchedule = {
|
||||
id: number;
|
||||
name: string;
|
||||
isManaged: boolean;
|
||||
schedule: ElementOf<Schedule["availabilities"]>;
|
||||
timeZone: string;
|
||||
isDefault: boolean;
|
||||
isLastSchedule: boolean;
|
||||
readOnly: boolean;
|
||||
};
|
||||
|
||||
export type SupportedTimezone = {
|
||||
city: string;
|
||||
timezone: string;
|
||||
pop: number;
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import type { CalSdk } from "../../cal";
|
||||
import { Endpoints } from "../../lib/endpoints";
|
||||
import { type BasicPlatformResponse, type ResponseStatus } from "../../types";
|
||||
import { EndpointHandler } from "../endpoint-handler";
|
||||
import type {
|
||||
AvailableSlots,
|
||||
GetAvaialbleSlotsArgs,
|
||||
RemoveSelectedSlotArgs,
|
||||
ReserveSlotArgs,
|
||||
SlotUID,
|
||||
} from "./types";
|
||||
|
||||
export class Slots extends EndpointHandler {
|
||||
constructor(private readonly sdk: CalSdk) {
|
||||
super("slots", sdk);
|
||||
}
|
||||
|
||||
async reserveSlot(args: ReserveSlotArgs): Promise<SlotUID> {
|
||||
const { data } = await this.sdk.httpCaller.post<BasicPlatformResponse<SlotUID>>(Endpoints.RESERVE_SLOT, {
|
||||
body: args,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
async removeSelectedSlot(args: RemoveSelectedSlotArgs): Promise<ResponseStatus> {
|
||||
const { status } = await this.sdk.httpCaller.delete<BasicPlatformResponse>(
|
||||
Endpoints.DELETE_SELECTED_SLOT,
|
||||
{
|
||||
config: { params: args },
|
||||
}
|
||||
);
|
||||
return status === "success" ? "success" : "error";
|
||||
}
|
||||
|
||||
async getAvailableSlots(args: GetAvaialbleSlotsArgs): Promise<AvailableSlots> {
|
||||
const { data } = await this.sdk.httpCaller.get<BasicPlatformResponse<AvailableSlots>>(
|
||||
Endpoints.AVAILABLE_SLOTS,
|
||||
{
|
||||
config: {
|
||||
params: args,
|
||||
},
|
||||
}
|
||||
);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export type SlotUID = string;
|
||||
|
||||
type Slot = Record<
|
||||
SlotUID,
|
||||
{
|
||||
time: string;
|
||||
attendees?: number;
|
||||
bookingUid?: string;
|
||||
}
|
||||
>;
|
||||
|
||||
export type AvailableSlots = Slot[];
|
||||
|
||||
const ReserveSlotSchema = z.object({
|
||||
eventTypeId: z.number(),
|
||||
slotUtcStartDate: z.date(),
|
||||
slotUtcEndDate: z.date(),
|
||||
bookingUid: z.string().optional(),
|
||||
});
|
||||
|
||||
const RemoveSelectedSlotSchema = z.object({
|
||||
uid: z.string().optional(),
|
||||
});
|
||||
|
||||
const GetAvailableSlotsSchema = z.object({
|
||||
startTime: z.date(),
|
||||
endTime: z.date(),
|
||||
eventTypeId: z.number().optional(),
|
||||
usernameList: z.string().array().optional(),
|
||||
debug: z.boolean().optional(),
|
||||
duration: z.number().optional(),
|
||||
});
|
||||
|
||||
export type ReserveSlotArgs = z.infer<typeof ReserveSlotSchema>;
|
||||
export type RemoveSelectedSlotArgs = z.infer<typeof RemoveSelectedSlotSchema>;
|
||||
export type GetAvaialbleSlotsArgs = z.infer<typeof GetAvailableSlotsSchema>;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { CalSdk as Cal } from "./cal";
|
||||
export * from "./types";
|
||||
@@ -0,0 +1,176 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { ApiVersion } from "../types";
|
||||
|
||||
type BaseEndpointDeclaration = {
|
||||
auth: "public" | "access_token" | "secret";
|
||||
apiVersion: ApiVersion;
|
||||
};
|
||||
|
||||
// Define a type for static URIs
|
||||
type StaticUriEndpoint = BaseEndpointDeclaration & {
|
||||
uri: string;
|
||||
constructUri?: never; // Ensure constructUri is not used here
|
||||
};
|
||||
|
||||
// Introduce a generic type for dynamic URIs, allowing for explicit parameter type declaration
|
||||
type DynamicUriEndpoint<ParamsType extends Record<string, string> | string[]> = BaseEndpointDeclaration & {
|
||||
uri?: never; // Ensure uri is not used here
|
||||
constructUri: (params: ParamsType) => string;
|
||||
};
|
||||
|
||||
// Create a discriminated union of the two types, incorporating generics
|
||||
type EndpointDeclaration<ParamsType extends Record<string, string> | string[] = string[]> =
|
||||
| StaticUriEndpoint
|
||||
| DynamicUriEndpoint<ParamsType>;
|
||||
|
||||
export enum Endpoints {
|
||||
RESERVE_SLOT = "RESERVE_SLOT",
|
||||
DELETE_SELECTED_SLOT = "DELETE_SELECTED_SLOT",
|
||||
AVAILABLE_SLOTS = "AVAILABLE_SLOTS",
|
||||
GET_PUBLIC_EVENT = "GET_PUBLIC_EVENT",
|
||||
EXCHANGE_OAUTH_AUTH_TOKEN = "EXCHANGE_OAUTH_AUTH_TOKEN",
|
||||
REFRESH_OAUTH_TOKEN = "REFRESH_OAUTH_TOKEN",
|
||||
CREATE_EVENT_TYPE = "CREATE_EVENT_TYPE",
|
||||
GET_EVENT_TYPE_BY_ID = "GET_EVENT_TYPE_BY_ID",
|
||||
CREATE_SCHEDULE = "CREATE_SCHEDULE",
|
||||
GET_DEFAULT_SCHEDULE = "GET_DEFAULT_SCHEDULE",
|
||||
GET_ALL_SCHEDULES = "GET_ALL_SCHEDULES",
|
||||
GET_SCHEDULE_BY_ID = "GET_SCHEDULE_BY_ID",
|
||||
GET_SUPPORTED_TIMEZONES = "GET_SUPPORTED_TIMEZONES",
|
||||
UPDATE_SCHEDULE_BY_ID = "UPDATE_SCHEDULE_BY_ID",
|
||||
DELETE_SCHEDULE_BY_ID = "DELETE_SCHEDULE_BY_ID",
|
||||
}
|
||||
|
||||
const publicEndpoint = (uri: string, version = ApiVersion.NEUTRAL): EndpointDeclaration => ({
|
||||
uri,
|
||||
auth: "public",
|
||||
apiVersion: version,
|
||||
});
|
||||
|
||||
// Adjust the constructor to use generics for flexible parameter typing
|
||||
const publicEndpointConstructor = <ParamsType extends Record<string, string> | string[]>(
|
||||
constructUri: (params: ParamsType) => string,
|
||||
version = ApiVersion.NEUTRAL
|
||||
): EndpointDeclaration<ParamsType> => ({
|
||||
constructUri,
|
||||
auth: "public",
|
||||
apiVersion: version,
|
||||
});
|
||||
|
||||
const ENDPOINTS: Record<Endpoints, EndpointDeclaration> = {
|
||||
RESERVE_SLOT: publicEndpoint("slots/reserve", ApiVersion.V2),
|
||||
AVAILABLE_SLOTS: publicEndpoint("slots/available", ApiVersion.V2),
|
||||
DELETE_SELECTED_SLOT: publicEndpoint("slots/delete", ApiVersion.V2),
|
||||
GET_PUBLIC_EVENT: publicEndpoint("events/"),
|
||||
EXCHANGE_OAUTH_AUTH_TOKEN: publicEndpointConstructor<string[]>(
|
||||
([clientId]) => `oauth/${clientId}/exchange`,
|
||||
ApiVersion.V2
|
||||
),
|
||||
REFRESH_OAUTH_TOKEN: publicEndpointConstructor<string[]>(
|
||||
([clientId]) => `oauth/${clientId}/refresh`,
|
||||
ApiVersion.V2
|
||||
),
|
||||
CREATE_EVENT_TYPE: {
|
||||
apiVersion: ApiVersion.V2,
|
||||
auth: "access_token",
|
||||
uri: `event-types`,
|
||||
},
|
||||
GET_EVENT_TYPE_BY_ID: {
|
||||
apiVersion: ApiVersion.V2,
|
||||
auth: "access_token",
|
||||
constructUri([eventTypeId]) {
|
||||
return `event-types/${eventTypeId}`;
|
||||
},
|
||||
},
|
||||
CREATE_SCHEDULE: {
|
||||
apiVersion: ApiVersion.V2,
|
||||
auth: "access_token",
|
||||
uri: "schedules",
|
||||
},
|
||||
GET_DEFAULT_SCHEDULE: {
|
||||
apiVersion: ApiVersion.V2,
|
||||
auth: "access_token",
|
||||
uri: "schedules/default",
|
||||
},
|
||||
GET_ALL_SCHEDULES: {
|
||||
apiVersion: ApiVersion.V2,
|
||||
auth: "access_token",
|
||||
uri: "schedules",
|
||||
},
|
||||
GET_SCHEDULE_BY_ID: {
|
||||
apiVersion: ApiVersion.V2,
|
||||
auth: "access_token",
|
||||
constructUri([scheduleId]) {
|
||||
return `schedules/${scheduleId}`;
|
||||
},
|
||||
},
|
||||
GET_SUPPORTED_TIMEZONES: publicEndpoint("schedules/time-zones", ApiVersion.V2),
|
||||
UPDATE_SCHEDULE_BY_ID: {
|
||||
apiVersion: ApiVersion.V2,
|
||||
auth: "access_token",
|
||||
constructUri([scheduleId]) {
|
||||
return `schedules/${scheduleId}`;
|
||||
},
|
||||
},
|
||||
DELETE_SCHEDULE_BY_ID: {
|
||||
apiVersion: ApiVersion.V2,
|
||||
auth: "access_token",
|
||||
constructUri([scheduleId]) {
|
||||
return `schedules/${scheduleId}`;
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
const isParamsRecord = (params: unknown): params is Record<string, string> => {
|
||||
return params !== null && typeof params === "object" && !Array.isArray(params);
|
||||
};
|
||||
|
||||
export const getEndpointDefinition = (endpoint: Endpoints): BaseEndpointDeclaration => {
|
||||
return ENDPOINTS[endpoint];
|
||||
};
|
||||
|
||||
export const getEndpointData = (
|
||||
endpoint: Endpoints,
|
||||
params?: Record<string, string> | string[]
|
||||
): {
|
||||
uri: string;
|
||||
version: ApiVersion;
|
||||
auth: EndpointDeclaration["auth"];
|
||||
} => {
|
||||
const endpointData = ENDPOINTS[endpoint];
|
||||
|
||||
// Determine if the endpoint expects a dynamic URI construction
|
||||
if (typeof endpointData.constructUri === "function") {
|
||||
if (!params) {
|
||||
throw new Error(`Parameters are required for dynamic ${endpoint} endpoint.`);
|
||||
}
|
||||
|
||||
// Here, we need to determine the correct type of params at runtime
|
||||
let constructedUri: string;
|
||||
if (isParamsRecord(params)) {
|
||||
// Params is a Record<string, string>, handle accordingly
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
constructedUri = endpointData.constructUri(params as Record<string, string>);
|
||||
} else if (Array.isArray(params)) {
|
||||
// Params is string[], handle accordingly
|
||||
constructedUri = endpointData.constructUri(params as string[]);
|
||||
} else {
|
||||
throw new Error("Invalid parameter type for dynamic endpoint.");
|
||||
}
|
||||
|
||||
return {
|
||||
version: endpointData.apiVersion,
|
||||
uri: constructedUri,
|
||||
auth: endpointData.auth,
|
||||
};
|
||||
} else if (endpointData.uri) {
|
||||
return {
|
||||
version: endpointData.apiVersion,
|
||||
uri: endpointData.uri,
|
||||
auth: endpointData.auth,
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Endpoint configuration error for ${endpoint}`);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export class CalApiError extends Error {
|
||||
constructor(public message: string, public statusCode: number) {
|
||||
super(message);
|
||||
this.name = "CalApiError";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export class SdkInitializationError extends Error {
|
||||
constructor(message: string) {
|
||||
super(`Failed to initialize SDK: ${message}`);
|
||||
this.name = "SdkInitializationError";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import type { AxiosError, AxiosRequestConfig } from "axios";
|
||||
import { type AxiosInstance } from "axios";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import merge from "lodash/merge";
|
||||
|
||||
import type { Endpoints } from "./endpoints";
|
||||
import { getEndpointData, getEndpointDefinition } from "./endpoints";
|
||||
import { CalApiError } from "./errors/cal-api-error";
|
||||
import type { SdkSecrets } from "./sdk-secrets";
|
||||
|
||||
interface HttpCallerOptions {
|
||||
shouldHandleRefresh?: boolean;
|
||||
}
|
||||
|
||||
export const X_CAL_SECRET_KEY = "x-cal-secret-key";
|
||||
|
||||
type CallOptions = {
|
||||
urlParams?: Record<string, string> | string[];
|
||||
config?: AxiosRequestConfig<unknown>;
|
||||
};
|
||||
|
||||
type CallOptionsWithBody<T = unknown> = CallOptions & {
|
||||
body: T;
|
||||
};
|
||||
|
||||
export class HttpCaller {
|
||||
private awaitingRefresh = false;
|
||||
|
||||
secrets: SdkSecrets | null = null;
|
||||
|
||||
private requestQueue: Array<() => void> = [];
|
||||
|
||||
constructor(
|
||||
private readonly clientId: string,
|
||||
private readonly axiosClient: AxiosInstance,
|
||||
private readonly options?: HttpCallerOptions
|
||||
) {
|
||||
this.setupInterceptors();
|
||||
}
|
||||
|
||||
private async retryQueuedRequests() {
|
||||
while (this.requestQueue.length > 0) {
|
||||
const retryRequest = this.requestQueue.shift();
|
||||
if (retryRequest) {
|
||||
retryRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setupInterceptors() {
|
||||
this.axiosClient.interceptors.request.use(async (config) => {
|
||||
if (this.awaitingRefresh) {
|
||||
// Return a new promise that resolves when the refresh is complete
|
||||
// and then retries the request.
|
||||
return new Promise((resolve) => {
|
||||
this.requestQueue.push(() => resolve(config));
|
||||
});
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
this.axiosClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
if (error.response?.status === 498) {
|
||||
if (!this.awaitingRefresh) {
|
||||
this.awaitingRefresh = true;
|
||||
|
||||
try {
|
||||
await this.secrets?.refreshAccessToken(this.clientId);
|
||||
this.retryQueuedRequests();
|
||||
} catch (refreshError) {
|
||||
console.error("Failed to refresh token:", refreshError);
|
||||
// Optionally, clear the queue on failure to prevent hanging requests
|
||||
this.requestQueue = [];
|
||||
} finally {
|
||||
this.awaitingRefresh = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-queue the failed request for retry after refresh
|
||||
return new Promise((resolve, reject) => {
|
||||
this.requestQueue.push(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
this.axiosClient.request(error.config!).then(resolve).catch(reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (error.response) {
|
||||
throw new CalApiError(error.response.statusText, error.response.status);
|
||||
} else if (error.request) {
|
||||
throw new Error("The request was made but no response was received");
|
||||
} else {
|
||||
throw new Error("An error occurred during the request setup");
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async post<T>(endpoint: Endpoints, options: CallOptionsWithBody): Promise<T> {
|
||||
const { data } = await this.axiosClient.post<T>(
|
||||
this.createCallUrl(endpoint, options.urlParams),
|
||||
options.body,
|
||||
this.wrapConfigWithAuth(endpoint, options.config)
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async get<T>(endpoint: Endpoints, options?: CallOptions): Promise<T> {
|
||||
const { data } = await this.axiosClient.get<T>(
|
||||
this.createCallUrl(endpoint, options?.urlParams),
|
||||
this.wrapConfigWithAuth(endpoint, options?.config)
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async delete<T>(endpoint: Endpoints, options?: CallOptions): Promise<T> {
|
||||
const { data } = await this.axiosClient.delete<T>(
|
||||
this.createCallUrl(endpoint, options?.urlParams),
|
||||
this.wrapConfigWithAuth(endpoint, options?.config)
|
||||
);
|
||||
return data;
|
||||
}
|
||||
|
||||
async patch<T>(endpoint: Endpoints, options: CallOptionsWithBody): Promise<T> {
|
||||
const { data } = await this.axiosClient.patch<T>(
|
||||
this.createCallUrl(endpoint, options.urlParams),
|
||||
options.body,
|
||||
this.wrapConfigWithAuth(endpoint, options.config)
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private createCallUrl(endpoint: Endpoints, params?: Record<string, string> | string[]): string {
|
||||
const { version, uri } = getEndpointData(endpoint, params);
|
||||
return `${version}${uri}`;
|
||||
}
|
||||
|
||||
private wrapConfigWithAuth(
|
||||
endpoint: Endpoints,
|
||||
config?: AxiosRequestConfig<unknown>
|
||||
): AxiosRequestConfig<unknown> {
|
||||
const { auth } = getEndpointDefinition(endpoint);
|
||||
|
||||
const headers: Record<string, unknown> = {};
|
||||
const params: Record<string, unknown> = {};
|
||||
|
||||
if (this.secrets?.getAccessToken() && auth === "access_token") {
|
||||
headers["Authorization"] = `Bearer ${this.secrets.getAccessToken()}`;
|
||||
}
|
||||
|
||||
if (this.secrets?.getClientSecret() && auth === "secret") {
|
||||
headers[X_CAL_SECRET_KEY] = this.secrets.getClientSecret();
|
||||
params["clientId"] = this.clientId;
|
||||
}
|
||||
|
||||
return merge(config, {
|
||||
headers,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
isAwaitingRefresh() {
|
||||
return this.awaitingRefresh;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import type { ExchangeCodeResponse } from "../endpoints/oauth-flow/types";
|
||||
import type { BasicPlatformResponse } from "../types";
|
||||
import { Endpoints } from "./endpoints";
|
||||
import type { HttpCaller } from "./http-caller";
|
||||
import { X_CAL_SECRET_KEY } from "./http-caller";
|
||||
|
||||
export class SdkSecrets {
|
||||
private refreshedAt: Date | null;
|
||||
|
||||
constructor(
|
||||
private readonly clientSecret: string,
|
||||
private accessToken: string,
|
||||
private refreshToken: string,
|
||||
private readonly httpCaller: HttpCaller
|
||||
) {
|
||||
this.refreshedAt = null;
|
||||
}
|
||||
|
||||
updateAccessToken(accessToken: string, refreshToken?: string) {
|
||||
this.accessToken = accessToken;
|
||||
|
||||
if (refreshToken) {
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
}
|
||||
|
||||
async refreshAccessToken(clientId: string) {
|
||||
const { data } = await this.httpCaller.post<BasicPlatformResponse<ExchangeCodeResponse>>(
|
||||
Endpoints.REFRESH_OAUTH_TOKEN,
|
||||
{
|
||||
urlParams: [clientId],
|
||||
body: {
|
||||
refreshToken: this.refreshToken,
|
||||
},
|
||||
config: {
|
||||
headers: {
|
||||
[X_CAL_SECRET_KEY]: this.clientSecret,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.accessToken = data.accessToken;
|
||||
this.refreshToken = data.refreshToken;
|
||||
|
||||
this.refreshedAt = new Date();
|
||||
}
|
||||
|
||||
public getAccessToken(): Readonly<string> {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
public getClientSecret(): Readonly<string> {
|
||||
return this.clientSecret;
|
||||
}
|
||||
|
||||
public isAccessTokenSet(): boolean {
|
||||
return !!this.accessToken;
|
||||
}
|
||||
|
||||
public _debug() {
|
||||
return {
|
||||
refreshedAt: this.refreshedAt,
|
||||
accessToken: this.accessToken,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
export interface CalSdkConstructorOptions {
|
||||
baseUrl?: string;
|
||||
handleRefresh?: boolean;
|
||||
httpRetries?: CalSdkHttpRetries;
|
||||
}
|
||||
|
||||
interface CalSdkHttpRetries {
|
||||
maxAmount: number;
|
||||
}
|
||||
|
||||
export type ResponseStatus = "success" | "error";
|
||||
|
||||
export interface BasicPlatformResponse<T = unknown> {
|
||||
status: ResponseStatus;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export enum ApiVersion {
|
||||
V1 = "v1/",
|
||||
V2 = "v2/",
|
||||
NEUTRAL = "/",
|
||||
}
|
||||
|
||||
export type SdkAuthOptions = {
|
||||
clientSecret: string;
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
|
||||
export * from "./endpoints/slots/types";
|
||||
export * from "./endpoints/schedules/types";
|
||||
export * from "./endpoints/events/types";
|
||||
export * from "./endpoints/oauth-flow/types";
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "ESNext"
|
||||
},
|
||||
"exclude": ["src/__tests__/**/*", "example/*"]
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||
|
||||
/* Modules */
|
||||
"module": "commonjs" /* Specify what module code is generated. */,
|
||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */,
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user