Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@internxt/sdk",
"author": "Internxt <[email protected]>",
"version": "1.9.11",
"version": "1.9.12",
"description": "An sdk for interacting with Internxt's services",
"repository": {
"type": "git",
Expand Down
9 changes: 8 additions & 1 deletion src/drive/payments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ApiSecurity, ApiUrl, AppDetails } from '../../shared';
import { headersWithToken } from '../../shared/headers';
import { HttpClient } from '../../shared/http/client';
import AppError from '../../shared/types/errors';
import { Tier } from './types/tiers';
import {
AvailableProducts,
CreateCheckoutSessionPayload,
Expand All @@ -18,7 +19,7 @@ import {
UpdateSubscriptionPaymentMethod,
UserSubscription,
UserType,
} from './types';
} from './types/types';

export class Payments {
private readonly client: HttpClient;
Expand Down Expand Up @@ -228,6 +229,12 @@ export class Payments {
return this.client.get('/products', this.headers());
}

public getUserTier(tierType?: UserType): Promise<Tier> {
const query = new URLSearchParams();
if (tierType !== undefined) query.set('tierType', tierType);
return this.client.get<Tier>(`/products/tier?${query.toString()}`, this.headers());
}

/**
* Returns the needed headers for the module requests
* @private
Expand Down
2 changes: 1 addition & 1 deletion src/drive/payments/object-storage.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiUrl, AppDetails } from '../../shared';
import { basicHeaders } from '../../shared/headers';
import { HttpClient } from '../../shared/http/client';
import { CreatedSubscriptionData } from './types';
import { CreatedSubscriptionData } from './types/types';

interface ObjectStoragePlan {
id: string;
Expand Down
57 changes: 57 additions & 0 deletions src/drive/payments/types/tiers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
interface AntivirusFeatures {
enabled: boolean;
}

interface BackupsFeatures {
enabled: boolean;
}

export interface DriveFeatures {
enabled: boolean;
maxSpaceBytes: number;
workspaces: {
enabled: boolean;
minimumSeats: number;
maximumSeats: number;
maxSpaceBytesPerSeat: number;
};
}

interface MeetFeatures {
enabled: boolean;
paxPerCall: number;
}

interface MailFeatures {
enabled: boolean;
addressesPerUser: number;
}

export interface VpnFeatures {
enabled: boolean;
featureId: string;
}

export enum Service {
Drive = 'drive',
Backups = 'backups',
Antivirus = 'antivirus',
Meet = 'meet',
Mail = 'mail',
Vpn = 'vpn',
}

export interface Tier {
id: string;
label: string;
productId: string;
billingType: 'subscription' | 'lifetime';
featuresPerService: {
[Service.Drive]: DriveFeatures;
[Service.Backups]: BackupsFeatures;
[Service.Antivirus]: AntivirusFeatures;
[Service.Meet]: MeetFeatures;
[Service.Mail]: MailFeatures;
[Service.Vpn]: VpnFeatures;
};
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AppSumoDetails } from './../../shared/types/appsumo';
import { AppSumoDetails } from '../../../shared/types/appsumo';
export interface ProductData {
id: string;
name: string;
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * as Network from './network';
export * as photos from './photos';
export * as Shared from './shared';
export * as Workspaces from './workspaces';
export * from './meet';
175 changes: 175 additions & 0 deletions src/meet/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import sinon from 'sinon';
import { ApiSecurity, AppDetails } from '../shared';
import { basicHeaders, headersWithToken } from '../shared/headers';
import { HttpClient } from '../shared/http/client';
import { Meet } from './index';
import { CreateCallResponse, JoinCallPayload, JoinCallResponse, UsersInCallResponse } from './types';

const httpClient = HttpClient.create('');

describe('Meet service tests', () => {
beforeEach(() => {
sinon.stub(HttpClient, 'create').returns(httpClient);
});

afterEach(() => {
sinon.restore();
});

describe('createCall method', () => {
it('should successfully create a call with token', async () => {
// Arrange
const expectedResponse: CreateCallResponse = {
token: 'call-token',
room: 'room-id',
paxPerCall: 10,
};

const { client, headers } = clientAndHeadersWithToken();
const postCall = sinon.stub(httpClient, 'post').resolves(expectedResponse);

// Act
const response = await client.createCall();

// Assert
expect(postCall.firstCall.args).toEqual(['call', {}, headers]);
expect(response).toEqual(expectedResponse);
});

it('should throw an error when token is missing', async () => {
// Arrange
const { client } = clientAndHeadersWithoutToken();

// Act & Assert
await expect(client.createCall()).rejects.toThrow('Token is required for Meet operations');
});
});

describe('joinCall method', () => {
const callId = 'call-123';
const payload: JoinCallPayload = {
name: 'John',
lastname: 'Doe',
anonymous: false,
};

const joinCallResponse: JoinCallResponse = {
token: 'join-token',
room: 'room-id',
userId: 'user-123',
};

it('should join a call successfully with token', async () => {
// Arrange
const { client, headers } = clientAndHeadersWithToken();
const postCall = sinon.stub(httpClient, 'post').resolves(joinCallResponse);

// Act
const response = await client.joinCall(callId, payload);

// Assert
expect(postCall.firstCall.args).toEqual([`call/${callId}/users/join`, payload, headers]);
expect(response).toEqual(joinCallResponse);
});

it('should join a call successfully without token', async () => {
// Arrange
const { client, headers } = clientAndHeadersWithoutToken();
const postCall = sinon.stub(httpClient, 'post').resolves(joinCallResponse);

// Act
const response = await client.joinCall(callId, payload);

// Assert
expect(postCall.firstCall.args[0]).toEqual(`call/${callId}/users/join`);
expect(postCall.firstCall.args[1]).toEqual(payload);
expect(postCall.firstCall.args[2]).toEqual(headers);
expect(response).toEqual(joinCallResponse);
});
});

describe('getCurrentUsersInCall method', () => {
const callId = 'call-123';
const usersInCallResponse: UsersInCallResponse[] = [
{
userId: 'user-123',
name: 'John',
lastname: 'Doe',
anonymous: false,
avatar: 'avatar-url-1',
},
{
userId: 'user-456',
name: 'Jane',
lastname: 'Smith',
anonymous: true,
avatar: 'avatar-url-2',
},
];

it('should get current users in call successfully with token', async () => {
// Arrange
const { client, headers } = clientAndHeadersWithToken();
const getCall = sinon.stub(httpClient, 'get').resolves(usersInCallResponse);

// Act
const response = await client.getCurrentUsersInCall(callId);

// Assert
expect(getCall.firstCall.args).toEqual([`call/${callId}/users`, headers]);
expect(response).toEqual(usersInCallResponse);
});

it('should get current users in call successfully without token', async () => {
// Arrange
const { client, headers } = clientAndHeadersWithoutToken();
const getCall = sinon.stub(httpClient, 'get').resolves(usersInCallResponse);

// Act
const response = await client.getCurrentUsersInCall(callId);

// Assert
expect(getCall.firstCall.args[0]).toEqual(`call/${callId}/users`);
expect(getCall.firstCall.args[1]).toEqual(headers);
expect(response).toEqual(usersInCallResponse);
});
});
});

function clientAndHeadersWithToken(
apiUrl = '',
clientName = 'c-name',
clientVersion = '0.1',
token = 'my-token',
): {
client: Meet;
headers: object;
} {
const appDetails: AppDetails = {
clientName,
clientVersion,
};
const apiSecurity: ApiSecurity = {
token,
};
const client = Meet.client(apiUrl, appDetails, apiSecurity);
const headers = headersWithToken(clientName, clientVersion, token);
return { client, headers };
}

function clientAndHeadersWithoutToken(
apiUrl = '',
clientName = 'c-name',
clientVersion = '0.1',
): {
client: Meet;
headers: object;
} {
const appDetails: AppDetails = {
clientName,
clientVersion,
};
const client = Meet.client(apiUrl, appDetails);
const headers = basicHeaders(clientName, clientVersion);
return { client, headers };
}
47 changes: 47 additions & 0 deletions src/meet/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ApiSecurity, ApiUrl, AppDetails } from '../shared';
import { basicHeaders, headersWithToken } from '../shared/headers';
import { HttpClient } from '../shared/http/client';
import { CreateCallResponse, JoinCallPayload, JoinCallResponse, UsersInCallResponse } from './types';

export class Meet {
private readonly client: HttpClient;
private readonly appDetails: AppDetails;
private readonly apiSecurity?: ApiSecurity;

private constructor(apiUrl: ApiUrl, appDetails: AppDetails, apiSecurity?: ApiSecurity) {
this.client = HttpClient.create(apiUrl, apiSecurity?.unauthorizedCallback);
this.appDetails = appDetails;
this.apiSecurity = apiSecurity;
}

public static client(apiUrl: ApiUrl, appDetails: AppDetails, apiSecurity?: ApiSecurity) {
return new Meet(apiUrl, appDetails, apiSecurity);
}

async createCall(): Promise<CreateCallResponse> {
return this.client.post<CreateCallResponse>('call', {}, this.headersWithToken());
}

async joinCall(callId: string, payload: JoinCallPayload): Promise<JoinCallResponse> {
const headers = this.apiSecurity?.token ? this.headersWithToken() : this.basicHeaders();

return this.client.post<JoinCallResponse>(`call/${callId}/users/join`, { ...payload }, headers);
}

async getCurrentUsersInCall(callId: string): Promise<UsersInCallResponse[]> {
const headers = this.apiSecurity?.token ? this.headersWithToken() : this.basicHeaders();

return this.client.get(`call/${callId}/users`, headers);
}

private headersWithToken() {
if (!this.apiSecurity?.token) {
throw new Error('Token is required for Meet operations');
}
return headersWithToken(this.appDetails.clientName, this.appDetails.clientVersion, this.apiSecurity.token);
}

private basicHeaders() {
return basicHeaders(this.appDetails.clientName, this.appDetails.clientVersion);
}
}
25 changes: 25 additions & 0 deletions src/meet/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export interface CreateCallResponse {
token: string;
room: string;
paxPerCall: number;
}

export interface JoinCallPayload {
name: string;
lastname: string;
anonymous: boolean;
}

export interface JoinCallResponse {
token: string;
room: string;
userId: string;
}

export interface UsersInCallResponse {
userId: string;
name: string;
lastname: string;
anonymous: boolean;
avatar: string;
}
4 changes: 2 additions & 2 deletions test/drive/payments/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import sinon from 'sinon';
import { Payments } from '../../../src/drive';
import { CreatePaymentSessionPayload, StripeSessionMode, UserType } from '../../../src/drive/payments/types/types';
import { ApiSecurity, AppDetails } from '../../../src/shared';
import { headersWithToken } from '../../../src/shared/headers';
import { Payments } from '../../../src/drive';
import { CreatePaymentSessionPayload, StripeSessionMode, UserType } from '../../../src/drive/payments/types';
import { HttpClient } from '../../../src/shared/http/client';

const httpClient = HttpClient.create('');
Expand Down