Skip to content

Commit

Permalink
Resource documentation for SDK reference (#476)
Browse files Browse the repository at this point in the history
This PR has documentation adds for all the resources. Also changes the following:
- Refactor Auth to reflect the recent changes to the endpoints
- Refactor Errors to support API + OAuth errors as well as timeout errors
- Move availability into the calendar resource
  • Loading branch information
mrashed-dev authored Aug 14, 2023
1 parent d1c21e1 commit 2ea65dd
Show file tree
Hide file tree
Showing 18 changed files with 472 additions and 368 deletions.
21 changes: 10 additions & 11 deletions src/apiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import fetch, { Request, Response } from 'node-fetch';
import { NylasConfig, OverridableNylasConfig } from './config';
import {
NylasApiError,
NylasAuthError,
NylasTokenValidationError,
NylasOAuthError,
NylasSdkTimeoutError,
} from './models/error';
import { objKeysToCamelCase, objKeysToSnakeCase } from './utils';
import PACKAGE_JSON from '../package.json';

const PACKAGE_JSON = require('../package.json');
const SDK_VERSION = PACKAGE_JSON.version;

export interface RequestOptionsParams {
Expand Down Expand Up @@ -128,7 +128,10 @@ export default class APIClient {
async request<T>(options: RequestOptionsParams): Promise<T> {
const req = this.newRequest(options);
const controller: AbortController = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.timeout);
const timeout = setTimeout(() => {
controller.abort();
throw new NylasSdkTimeoutError(req.url, this.timeout);
}, this.timeout);

const response = await fetch(req, { signal: controller.signal });
clearTimeout(timeout);
Expand All @@ -143,20 +146,16 @@ export default class APIClient {
options.path.includes('connect/token') ||
options.path.includes('connect/revoke');

const tokenErrorResponse = options.path.includes('connect/tokeninfo');

const text = await response.text();
let error: Error;
try {
const parsedError = JSON.parse(text);
const camelCaseError = objKeysToCamelCase(parsedError);

if (authErrorResponse && !tokenErrorResponse) {
error = new NylasAuthError(camelCaseError);
} else if (tokenErrorResponse) {
error = new NylasTokenValidationError(camelCaseError);
if (authErrorResponse) {
error = new NylasOAuthError(camelCaseError, response.status);
} else {
error = new NylasApiError(camelCaseError);
error = new NylasApiError(camelCaseError, response.status);
}
} catch (e) {
throw new Error(
Expand Down
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export type NylasConfig = {

export type OverridableNylasConfig = Partial<NylasConfig>;

/**
* An object that can be used to override the default Nylas API client configuration on a per-request basis.
* @property overrides Overrides to the default Nylas API client configuration
*/
export interface Overrides {
overrides?: OverridableNylasConfig;
}
Expand Down
41 changes: 3 additions & 38 deletions src/models/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ type AccessType = 'online' | 'offline';
type Provider = 'google' | 'imap' | 'microsoft' | 'yahoo';

export type URLForAuthenticationConfig = {
clientId: string;
redirectUri: string;
provider?: Provider;
accessType?: AccessType;
Expand All @@ -17,25 +18,6 @@ export type URLForAdminConsentConfig = URLForAuthenticationConfig & {
credentialId: string;
};

export interface OpenID {
iss: string; // Issuer
aud: string; // Application Slug
sub?: string; // ID
email?: string;
emailVerified?: boolean;
atHash?: string;
iat: number; // Issued At
exp: number; // Expites At
// Profile
name?: string;
givenName?: string;
familyName?: string;
nickName?: string;
pictureUrl?: string;
gender?: string;
locale?: string;
}

export interface CodeExchangeRequest {
redirectUri: string;
code: string;
Expand All @@ -45,25 +27,8 @@ export interface CodeExchangeRequest {
export interface TokenExchangeRequest {
redirectUri: string;
refreshToken: string;
}

export type ServerSideHostedAuthRequest = {
redirectUri: string;
provider?: Provider;
state?: string;
loginHint?: string;
cookieNonce?: string;
grantId?: string;
scope?: string[];
expiresIn?: number;
settings?: Record<string, unknown>;
};

export interface ServerSideHostedAuthResponse {
url: string;
id: string;
expiresAt: number;
request: ServerSideHostedAuthRequest;
clientId: string;
clientSecret: string;
}

export interface PKCEAuthURL {
Expand Down
103 changes: 73 additions & 30 deletions src/models/error.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,93 @@
import {
NylasApiErrorResponse,
AuthErrorResponse,
TokenValidationErrorResponse,
} from './response';
import { NylasApiErrorResponse, AuthErrorResponse } from './response';

/**
* Extended Error class for errors returned from the Nylas API
*
* Properties:
* Base class for all Nylas API errors.
*/
export class NylasApiError extends Error {
export abstract class AbstractNylasApiError extends Error {
/**
* The unique identifier of the request.
*/
requestId?: string;
/**
* The HTTP status code of the error response.
*/
statusCode?: number;
}

/**
* Base class for all Nylas SDK errors.
*/
export abstract class AbstractNylasSdkError extends Error {}

/**
* Class representation of a general Nylas API error.
*/
export class NylasApiError extends AbstractNylasApiError {
/**
* Error type.
*/
type: string;
requestId: string;
/**
* Provider Error.
*/
providerError: any;

constructor(apiError: NylasApiErrorResponse) {
constructor(apiError: NylasApiErrorResponse, statusCode?: number) {
super(apiError.error.message);
this.type = apiError.error.type;
this.requestId = apiError.requestId;
this.providerError = apiError.error.providerError;
this.statusCode = statusCode;
}
}

export class NylasAuthError extends Error {
type: string;
requestId: string;
providerError: any;
/**
* Class representing an OAuth error returned by the Nylas API.
*/
export class NylasOAuthError extends AbstractNylasApiError {
/**
* Error type.
*/
error: string;
/**
* Error code used for referencing the docs, logs, and data stream.
*/
errorCode: number;
/**
* Human readable error description.
*/
errorDescription: string;
/**
* URL to the related documentation and troubleshooting regarding this error.
*/
errorUri: string;

constructor(apiError: AuthErrorResponse) {
constructor(apiError: AuthErrorResponse, statusCode?: number) {
super(apiError.errorDescription);
this.type = apiError.error;
this.requestId = apiError.requestId;
this.providerError = apiError.errorDescription;
this.error = apiError.error;
this.errorCode = apiError.errorCode;
this.errorDescription = apiError.errorDescription;
this.errorUri = apiError.errorUri;
this.statusCode = statusCode;
}
}
export class NylasTokenValidationError extends Error {
type: string;
requestId: string;
providerError: any;

constructor(apiError: TokenValidationErrorResponse) {
super(apiError.error.message);
this.type = apiError.error.type;
this.requestId = apiError.error.requestId;
this.providerError = apiError.error.message;
/**
* Error thrown when the Nylas SDK times out before receiving a response from the server
*/
export class NylasSdkTimeoutError extends AbstractNylasSdkError {
/**
* The URL that timed out
*/
url: string;
/**
* The timeout value set in the Nylas SDK, in seconds
*/
timeout: number;

constructor(url: string, timeout: number) {
super('Nylas SDK timed out before receiving a response from the server.');
this.url = url;
this.timeout = timeout;
}
}

// TODO: sdk error models?
15 changes: 4 additions & 11 deletions src/models/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@ export interface Event {
conferencing: Conferencing;
description?: string;
location?: string;
messageId?: string;
owner?: string;
icalUid?: string;
title?: string;
htmlLink?: string;
hideParticipants?: boolean;
metadata?: Record<string, string>;
creator?: EmailName;
organizer: EmailName;
recurrence?: Recurrence;
recurrence?: string[];
reminders?: Reminder[];
status?: Status;
visibility?: Visibility;
Expand All @@ -41,7 +39,7 @@ export interface CreateEventRequest {
reminderMethod?: string;
metadata?: Record<string, unknown>;
participants?: Participant[];
recurrence?: Recurrence;
recurrence?: string[];
calendarId?: string;
readOnly?: boolean;
roundRobinOrder?: string[];
Expand Down Expand Up @@ -118,8 +116,8 @@ export interface Time {
export interface Timespan {
startTime: number;
endTime: number;
startTimezone: string;
endTimezone: string;
startTimezone?: string;
endTimezone?: string;
}

export interface Date {
Expand All @@ -139,11 +137,6 @@ export interface Participant {
phoneNumber?: string;
}

export interface Recurrence {
rrule: string[];
timezone: string;
}

export interface Reminder {
reminderMinutes: string;
reminderMethod: ReminderMethod;
Expand Down
1 change: 1 addition & 0 deletions src/models/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface ProviderDetectParams {

export interface ProviderDetectResponse {
emailAddress: string;
detected: boolean;
provider?: string;
type?: string;
}
Expand Down
12 changes: 0 additions & 12 deletions src/models/response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,12 @@ export interface NylasDeleteResponse {
}

export interface AuthErrorResponse {
requestId: string;
error: string;
errorCode: number;
errorDescription: string;
errorUri: string;
}

export interface TokenValidationErrorResponse {
success: boolean;
error: {
httpCode: number;
eventCode: number;
type: string;
message: string;
requestId: string;
};
}

export type ListResponseInnerType<T> = T extends NylasListResponse<infer R>
? R
: never;
9 changes: 2 additions & 7 deletions src/nylas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ import { Calendars } from './resources/calendars';
import { Events } from './resources/events';
import { Auth } from './resources/auth';
import { Webhooks } from './resources/webhooks';
import { Availability } from './resources/availability';
import { Applications } from './resources/applications';

class Nylas {
public applications: Applications;
public availability: Availability;
public auth: Auth;
public calendars: Calendars;
public events: Events;
public webhooks: Webhooks;
Expand All @@ -24,17 +23,13 @@ class Nylas {
});

this.applications = new Applications(this.apiClient);
this.availability = new Availability(this.apiClient);
this.auth = new Auth(this.apiClient);
this.calendars = new Calendars(this.apiClient);
this.events = new Events(this.apiClient);
this.webhooks = new Webhooks(this.apiClient);

return this;
}

public auth(clientId: string, clientSecret: string): Auth {
return new Auth(this.apiClient, clientId, clientSecret);
}
}

export = Nylas;
15 changes: 15 additions & 0 deletions src/resources/applications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,29 @@ import { ApplicationDetails } from '../models/applicationDetails';
import { NylasResponse } from '../models/response';
import { Overrides } from '../config';

/**
* Nylas Applications API
*
* This endpoint allows for getting application details as well as redirect URI operations.
*/
export class Applications extends Resource {
/**
* Access the collection of redirect URI related API endpoints.
*/
public redirectUris: RedirectUris;

/**
* @param apiClient client The configured Nylas API client
*/
constructor(apiClient: APIClient) {
super(apiClient);
this.redirectUris = new RedirectUris(apiClient);
}

/**
* Get application details
* @returns The application details
*/
public getDetails({ overrides }: Overrides = {}): Promise<
NylasResponse<ApplicationDetails>
> {
Expand Down
Loading

0 comments on commit 2ea65dd

Please sign in to comment.