├── .eslintignore ├── .prettierrc.json ├── jest.setup.js ├── src ├── constants │ └── index.ts ├── modules │ ├── vms │ │ ├── types │ │ │ ├── VmsTtsLector.ts │ │ │ └── VmsDetails.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── contacts │ │ ├── types │ │ │ ├── ContactGender.ts │ │ │ ├── UpdateContact.ts │ │ │ ├── NewContact.ts │ │ │ ├── Contact.ts │ │ │ └── GetContactsQueryParams.ts │ │ ├── modules │ │ │ ├── fields │ │ │ │ ├── types │ │ │ │ │ ├── FieldType.ts │ │ │ │ │ └── Field.ts │ │ │ │ ├── index.ts │ │ │ │ └── index.spec.ts │ │ │ └── groups │ │ │ │ ├── types │ │ │ │ ├── CreateGroupDetails.ts │ │ │ │ ├── GroupPermission.ts │ │ │ │ ├── UpdateGroup.ts │ │ │ │ └── Group.ts │ │ │ │ ├── index.ts │ │ │ │ └── index.spec.ts │ │ ├── helpers │ │ │ └── formatDate │ │ │ │ └── index.ts │ │ ├── httpClient │ │ │ ├── formatResponseDates │ │ │ │ ├── index.ts │ │ │ │ └── index.spec.ts │ │ │ └── prepareParamsForRequest │ │ │ │ ├── index.ts │ │ │ │ └── index.spec.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── profile │ │ ├── types │ │ │ ├── PaymentType.ts │ │ │ └── ProfileResponse.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── subusers │ │ ├── types │ │ │ ├── SubuserPoints.ts │ │ │ ├── SubuserCredentials.ts │ │ │ ├── Subuser.ts │ │ │ ├── NewSubuser.ts │ │ │ └── UpdateSubuser.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── templates │ │ ├── types │ │ │ ├── Template.ts │ │ │ └── NewTemplate.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── hlr │ │ ├── types │ │ │ ├── HlrCheckError.ts │ │ │ ├── HlrCheck.ts │ │ │ └── HlrCheckResponse.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── sms │ │ ├── types │ │ │ ├── ScheduledSmsResponse.ts │ │ │ └── SmsDetails.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── mfa │ │ ├── types │ │ │ ├── MfaGenerateCodeOptions.ts │ │ │ └── MfaResponse.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── sendernames │ │ ├── types │ │ │ ├── SendernameStatus.ts │ │ │ └── Sendername.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── mms │ │ ├── types │ │ │ └── MmsDetails.ts │ │ ├── index.ts │ │ └── index.spec.ts │ ├── baseMessageModule │ │ ├── types │ │ │ ├── BaseMessageDetails.ts │ │ │ └── MessageContent.ts │ │ ├── index.spec.ts │ │ └── index.ts │ └── baseModule │ │ └── index.ts ├── types │ ├── ApiCollection.ts │ ├── MessageStatus.ts │ ├── MessageResponse.ts │ └── index.ts ├── index.ts ├── testHelpers │ ├── files │ │ └── vms.wav │ └── hexToString │ │ └── index.ts ├── helpers │ ├── isObject │ │ ├── index.ts │ │ └── index.spec.ts │ ├── snakeCase │ │ ├── index.ts │ │ └── index.spec.ts │ ├── camelCase │ │ ├── index.ts │ │ └── index.spec.ts │ ├── mapKeys │ │ ├── index.spec.ts │ │ └── index.ts │ └── mapValues │ │ ├── index.spec.ts │ │ └── index.ts ├── errors │ └── MessageError │ │ └── index.ts └── smsapi │ ├── index.spec.ts │ ├── httpClient │ ├── extractDataFromResponse │ │ ├── index.ts │ │ └── index.spec.ts │ ├── index.ts │ └── index.spec.ts │ └── index.ts ├── jest.config.js ├── .npmignore ├── .editorconfig ├── THANKS.md ├── babel.config.js ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── tsconfig.json ├── .eslintrc.json ├── README.md ├── package.json ├── .gitignore ├── CHANGELOG.md └── LICENSE /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | jest.setTimeout(10000); 4 | -------------------------------------------------------------------------------- /src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const API_URL = 'https://smsapi.io/api'; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setupFilesAfterEnv: ['./jest.setup.js'], 3 | }; 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !/dist/** 3 | !CHANGELOG.md 4 | !LICENSE 5 | !README.md 6 | !THANKS.md 7 | -------------------------------------------------------------------------------- /src/modules/vms/types/VmsTtsLector.ts: -------------------------------------------------------------------------------- 1 | export type VmsTtsLector = 'ewa' | 'jacek' | 'jan' | 'maja'; 2 | -------------------------------------------------------------------------------- /src/modules/contacts/types/ContactGender.ts: -------------------------------------------------------------------------------- 1 | export type ContactGender = 'female' | 'male' | 'undefined'; 2 | -------------------------------------------------------------------------------- /src/types/ApiCollection.ts: -------------------------------------------------------------------------------- 1 | export interface ApiCollection { 2 | collection: T[]; 3 | size: number; 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | indent_style = space 6 | indent_size = 2 7 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { SMSAPI } from './smsapi'; 2 | 3 | export * from './types/index'; 4 | 5 | export { SMSAPI }; 6 | -------------------------------------------------------------------------------- /src/modules/profile/types/PaymentType.ts: -------------------------------------------------------------------------------- 1 | export type PaymentType = 'prepaid' | 'postpaid' | 'subscription' | 'trial'; 2 | -------------------------------------------------------------------------------- /src/testHelpers/files/vms.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smsapi/smsapi-javascript-client/HEAD/src/testHelpers/files/vms.wav -------------------------------------------------------------------------------- /src/modules/contacts/modules/fields/types/FieldType.ts: -------------------------------------------------------------------------------- 1 | export type FieldType = 'text' | 'date' | 'email' | 'phone_number' | 'number'; 2 | -------------------------------------------------------------------------------- /src/modules/subusers/types/SubuserPoints.ts: -------------------------------------------------------------------------------- 1 | export interface SubuserPoints { 2 | fromAccount: number; 3 | perMonth: number; 4 | } 5 | -------------------------------------------------------------------------------- /THANKS.md: -------------------------------------------------------------------------------- 1 | # Podziękowania: 2 | 3 | * Kamil Kucharski https://github.com/maringan za udostępnienie nazwy "smsapi" na https://www.npmjs.com/ 4 | -------------------------------------------------------------------------------- /src/modules/templates/types/Template.ts: -------------------------------------------------------------------------------- 1 | export interface Template { 2 | id: string; 3 | name: string; 4 | template: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/hlr/types/HlrCheckError.ts: -------------------------------------------------------------------------------- 1 | export interface HlrCheckError { 2 | number: string; 3 | status: 'ERROR'; 4 | error: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/hlr/types/HlrCheck.ts: -------------------------------------------------------------------------------- 1 | export interface HlrCheck { 2 | id: string; 3 | number: string; 4 | price: number; 5 | status: 'OK'; 6 | } 7 | -------------------------------------------------------------------------------- /src/modules/contacts/helpers/formatDate/index.ts: -------------------------------------------------------------------------------- 1 | export const formatDate = (date: Date): string => { 2 | return date.toISOString().slice(0, 10); 3 | }; 4 | -------------------------------------------------------------------------------- /src/modules/templates/types/NewTemplate.ts: -------------------------------------------------------------------------------- 1 | export interface NewTemplate { 2 | name: string; 3 | template: string; 4 | normalize?: boolean; 5 | } 6 | -------------------------------------------------------------------------------- /src/helpers/isObject/index.ts: -------------------------------------------------------------------------------- 1 | export const isObject = (value: unknown): value is object => { 2 | return value !== null && typeof value === 'object'; 3 | }; 4 | -------------------------------------------------------------------------------- /src/modules/sms/types/ScheduledSmsResponse.ts: -------------------------------------------------------------------------------- 1 | export interface ScheduledSmsResponse { 2 | count: number; 3 | list: { 4 | id: string; 5 | }[]; 6 | } 7 | -------------------------------------------------------------------------------- /src/modules/mfa/types/MfaGenerateCodeOptions.ts: -------------------------------------------------------------------------------- 1 | export interface MfaGenerateCodeOptions { 2 | content?: string; 3 | fast?: boolean; 4 | from?: string; 5 | } 6 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', { targets: { node: 'current' } }], 4 | '@babel/preset-typescript', 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /src/modules/mfa/types/MfaResponse.ts: -------------------------------------------------------------------------------- 1 | export interface MfaResponse { 2 | id: string; 3 | code: string; 4 | phoneNumber: string; 5 | from: string | null; 6 | } 7 | -------------------------------------------------------------------------------- /src/modules/subusers/types/SubuserCredentials.ts: -------------------------------------------------------------------------------- 1 | export interface SubuserCredentials { 2 | username: string; 3 | password: string; 4 | apiPassword?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/sendernames/types/SendernameStatus.ts: -------------------------------------------------------------------------------- 1 | export type SendernameStatus = 2 | | 'ACTIVE' 3 | | 'INACTIVE' 4 | | 'DELETED' 5 | | 'PENDING' 6 | | 'REJECTED'; 7 | -------------------------------------------------------------------------------- /src/modules/mms/types/MmsDetails.ts: -------------------------------------------------------------------------------- 1 | import { BaseMessageDetails } from '../../baseMessageModule/types/BaseMessageDetails'; 2 | 3 | export type MmsDetails = BaseMessageDetails; 4 | -------------------------------------------------------------------------------- /src/modules/contacts/types/UpdateContact.ts: -------------------------------------------------------------------------------- 1 | import { NewContact } from './NewContact'; 2 | 3 | export interface UpdateContact extends NewContact { 4 | phoneNumber?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/contacts/modules/fields/types/Field.ts: -------------------------------------------------------------------------------- 1 | import { FieldType } from './FieldType'; 2 | 3 | export interface Field { 4 | id: string; 5 | name: string; 6 | type: FieldType; 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/contacts/modules/groups/types/CreateGroupDetails.ts: -------------------------------------------------------------------------------- 1 | export interface CreateGroupDetails { 2 | description?: string; 3 | idx?: string; 4 | contactExpireAfter?: number; 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/hlr/types/HlrCheckResponse.ts: -------------------------------------------------------------------------------- 1 | import { HlrCheck } from './HlrCheck'; 2 | import { HlrCheckError } from './HlrCheckError'; 3 | 4 | export type HlrCheckResponse = HlrCheck | HlrCheckError; 5 | -------------------------------------------------------------------------------- /src/modules/baseMessageModule/types/BaseMessageDetails.ts: -------------------------------------------------------------------------------- 1 | export interface BaseMessageDetails { 2 | date?: Date; 3 | idx?: string; 4 | checkIdx?: boolean; 5 | notifyUrl?: string; 6 | test?: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/contacts/modules/groups/types/GroupPermission.ts: -------------------------------------------------------------------------------- 1 | export interface GroupPermission { 2 | groupId: string; 3 | username: string; 4 | write: boolean; 5 | read: boolean; 6 | send: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/contacts/modules/groups/types/UpdateGroup.ts: -------------------------------------------------------------------------------- 1 | import { CreateGroupDetails } from './CreateGroupDetails'; 2 | 3 | export interface UpdateGroup extends Partial { 4 | name?: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/helpers/snakeCase/index.ts: -------------------------------------------------------------------------------- 1 | export const snakeCase = (value: string): string => { 2 | return value 3 | .replace(/[A-Z]/g, (c) => `_${c}`) 4 | .replace(/[-\s]/g, '_') 5 | .replace(/^_+|_+$/g, '') 6 | .toLowerCase(); 7 | }; 8 | -------------------------------------------------------------------------------- /src/modules/sendernames/types/Sendername.ts: -------------------------------------------------------------------------------- 1 | import { SendernameStatus } from './SendernameStatus'; 2 | 3 | export interface Sendername { 4 | createdAt: Date; 5 | isDefault: boolean; 6 | sender: string; 7 | status: SendernameStatus; 8 | } 9 | -------------------------------------------------------------------------------- /src/helpers/camelCase/index.ts: -------------------------------------------------------------------------------- 1 | export const camelCase = (value: string): string => { 2 | const a = value.replace(/[-_\s.]+(.)?/g, (_, c) => 3 | c ? c.toUpperCase() : '', 4 | ); 5 | return a.substring(0, 1).toLowerCase() + a.substring(1); 6 | }; 7 | -------------------------------------------------------------------------------- /src/modules/subusers/types/Subuser.ts: -------------------------------------------------------------------------------- 1 | import { SubuserPoints } from './SubuserPoints'; 2 | 3 | export interface Subuser { 4 | id: string; 5 | username: string; 6 | active: boolean; 7 | description: string | null; 8 | points: SubuserPoints; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/baseModule/index.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '../../smsapi/httpClient'; 2 | 3 | export class BaseModule { 4 | protected httpClient: HttpClient; 5 | 6 | constructor(httpClient: HttpClient) { 7 | this.httpClient = httpClient; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/vms/types/VmsDetails.ts: -------------------------------------------------------------------------------- 1 | import { BaseMessageDetails } from '../../baseMessageModule/types/BaseMessageDetails'; 2 | 3 | export interface VmsDetails extends BaseMessageDetails { 4 | try?: 1 | 2 | 3 | 4 | 5 | 6; 5 | interval?: number; 6 | skipGsm?: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/testHelpers/hexToString/index.ts: -------------------------------------------------------------------------------- 1 | export const hexToString = (hex: string): string => { 2 | let str = ''; 3 | 4 | for (let i = 0; i < hex.length; i += 2) { 5 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); 6 | } 7 | 8 | return str; 9 | }; 10 | -------------------------------------------------------------------------------- /src/types/MessageStatus.ts: -------------------------------------------------------------------------------- 1 | export type MessageStatus = 2 | | 'ACCEPTED' 3 | | 'DELIVERED' 4 | | 'EXPIRED' 5 | | 'FAILED' 6 | | 'NOT_FOUND' 7 | | 'QUEUE' 8 | | 'REJECTED' 9 | | 'RENEWAL' 10 | | 'SENT' 11 | | 'STOP' 12 | | 'UNDELIVERED' 13 | | 'UNKNOWN'; 14 | -------------------------------------------------------------------------------- /src/modules/subusers/types/NewSubuser.ts: -------------------------------------------------------------------------------- 1 | import { SubuserCredentials } from './SubuserCredentials'; 2 | import { SubuserPoints } from './SubuserPoints'; 3 | 4 | export interface NewSubuser { 5 | credentials: SubuserCredentials; 6 | active?: boolean; 7 | description?: string; 8 | points?: SubuserPoints; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/profile/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseModule } from '../baseModule'; 2 | 3 | import { ProfileResponse } from './types/ProfileResponse'; 4 | 5 | export class Profile extends BaseModule { 6 | async get(): Promise { 7 | return await this.httpClient.get('/profile'); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/contacts/types/NewContact.ts: -------------------------------------------------------------------------------- 1 | import { ContactGender } from './ContactGender'; 2 | 3 | export interface NewContact { 4 | firstName?: string; 5 | lastName?: string; 6 | email?: string; 7 | gender?: ContactGender; 8 | birthdayDate?: Date; 9 | description?: string; 10 | city?: string; 11 | source?: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/helpers/mapKeys/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { mapKeys } from '.'; 2 | 3 | describe('mapKeys', () => { 4 | it('should transform keys', () => { 5 | const obj = { 6 | a: 1, 7 | b: 2, 8 | }; 9 | 10 | expect(mapKeys(obj, (key) => key.toUpperCase())).toEqual({ 11 | A: 1, 12 | B: 2, 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/modules/profile/types/ProfileResponse.ts: -------------------------------------------------------------------------------- 1 | import { PaymentType } from './PaymentType'; 2 | 3 | export interface ProfileResponse { 4 | id: string; 5 | name: string; 6 | email: string; 7 | username: string; 8 | phoneNumber: string; 9 | paymentType: PaymentType; 10 | userType: 'native' | 'subuser'; 11 | points?: number; 12 | } 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | # Maintain dependencies for npm 10 | - package-ecosystem: "npm" 11 | directory: "/" 12 | schedule: 13 | interval: "daily" 14 | -------------------------------------------------------------------------------- /src/modules/subusers/types/UpdateSubuser.ts: -------------------------------------------------------------------------------- 1 | import { SubuserCredentials } from './SubuserCredentials'; 2 | import { SubuserPoints } from './SubuserPoints'; 3 | 4 | export interface UpdateSubuser { 5 | credentials: Partial>; 6 | active: boolean; 7 | description: string; 8 | points: Partial; 9 | } 10 | -------------------------------------------------------------------------------- /src/helpers/mapValues/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { mapValues } from '.'; 2 | 3 | describe('mapValues', () => { 4 | it('should transform values', () => { 5 | const obj = { 6 | a: 'a', 7 | b: 'b', 8 | }; 9 | 10 | expect(mapValues(obj, (value) => value.toUpperCase())).toEqual({ 11 | a: 'A', 12 | b: 'B', 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/modules/contacts/types/Contact.ts: -------------------------------------------------------------------------------- 1 | import { ContactGender } from './ContactGender'; 2 | 3 | export interface Contact { 4 | id: string; 5 | firstName: string; 6 | lastName: string; 7 | phoneNumber: string; 8 | email: string; 9 | gender: ContactGender; 10 | birthdayDate: Date; 11 | description: string; 12 | city: string; 13 | source: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/contacts/modules/groups/types/Group.ts: -------------------------------------------------------------------------------- 1 | import { GroupPermission } from './GroupPermission'; 2 | 3 | export interface Group { 4 | id: string; 5 | name: string; 6 | description: string; 7 | contactsCount: number; 8 | dateCreated: Date; 9 | dateUpdated: Date; 10 | createdBy: string; 11 | idx: string | null; 12 | permissions: GroupPermission[]; 13 | } 14 | -------------------------------------------------------------------------------- /src/helpers/isObject/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '.'; 2 | 3 | describe('isObject', () => { 4 | it.each([{}, new Date(), [1], new Object()])( 5 | 'should true for %o', 6 | (value) => { 7 | expect(isObject(value)).toBe(true); 8 | }, 9 | ); 10 | 11 | it.each([null, 'abc', 1])('should false for %o', (value) => { 12 | expect(isObject(value)).toBe(false); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/helpers/mapKeys/index.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '../isObject'; 2 | 3 | export const mapKeys = ( 4 | value: Record, 5 | callback: (key: string) => string, 6 | ): Record => { 7 | if (value === undefined || !isObject(value)) { 8 | return value; 9 | } 10 | 11 | const result = {}; 12 | 13 | Object.entries(value).forEach(([key, value]) => { 14 | result[callback(key)] = value; 15 | }); 16 | 17 | return result; 18 | }; 19 | -------------------------------------------------------------------------------- /src/helpers/camelCase/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { camelCase } from '.'; 2 | 3 | describe('camelCase', () => { 4 | it.each([ 5 | ['abc-def', 'abcDef'], 6 | ['abcdef', 'abcdef'], 7 | ['abcDef', 'abcDef'], 8 | ['AbcDef', 'abcDef'], 9 | ['abc def', 'abcDef'], 10 | ['abc_def', 'abcDef'], 11 | ['__abc-def_ghi--', 'abcDefGhi'], 12 | ])('should convert %s to %s', (value, expectedString) => { 13 | expect(camelCase(value)).toEqual(expectedString); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/helpers/snakeCase/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { snakeCase } from '.'; 2 | 3 | describe('snakeCase', () => { 4 | it.each([ 5 | ['abc-def', 'abc_def'], 6 | ['abcdef', 'abcdef'], 7 | ['abcDef', 'abc_def'], 8 | ['AbcDef', 'abc_def'], 9 | ['abc def', 'abc_def'], 10 | ['abc_def', 'abc_def'], 11 | ['__abc-def_ghi--', 'abc_def_ghi'], 12 | ['__Abc-def_ghi__', 'abc_def_ghi'], 13 | ])('should convert %s to %s', (value, expectedString) => { 14 | expect(snakeCase(value)).toEqual(expectedString); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/helpers/mapValues/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { isObject } from '../isObject'; 3 | 4 | export const mapValues = ( 5 | value: Record, 6 | callback: (value: any, key: string) => any, 7 | ): Record => { 8 | if (value === undefined || !isObject(value)) { 9 | return value; 10 | } 11 | 12 | const result = {}; 13 | 14 | Object.entries(value).forEach(([key, value]) => { 15 | result[key] = callback(value, key); 16 | }); 17 | 18 | return result; 19 | }; 20 | -------------------------------------------------------------------------------- /src/modules/contacts/types/GetContactsQueryParams.ts: -------------------------------------------------------------------------------- 1 | import { QueryParams } from '../../../smsapi/httpClient'; 2 | 3 | import { ContactGender } from './ContactGender'; 4 | 5 | export interface GetContactsQueryParams extends QueryParams { 6 | q?: string; 7 | offset?: number; 8 | limit?: number; 9 | orderBy?: 'first_name' | 'last_name' | 'date_updated' | 'date_created'; 10 | phoneNumber?: string | string[]; 11 | email?: string | string[]; 12 | firstName?: string | string[]; 13 | lastName?: string | string[]; 14 | groupId?: string | string[]; 15 | gender?: ContactGender; 16 | birthdayDate?: Date | Date[]; 17 | } 18 | -------------------------------------------------------------------------------- /src/errors/MessageError/index.ts: -------------------------------------------------------------------------------- 1 | export function isMessageErrorResponseData( 2 | response: any, // eslint-disable-line 3 | ): response is MessageErrorResponse { 4 | return response.error !== undefined && response.message !== undefined; 5 | } 6 | 7 | export interface MessageErrorResponse { 8 | error: number; 9 | message: string; 10 | } 11 | 12 | class MessageError extends Error { 13 | public data: MessageErrorResponse; 14 | 15 | constructor(data: MessageErrorResponse) { 16 | super(); 17 | 18 | Error.captureStackTrace(this, this.constructor); 19 | 20 | this.name = 'MessageError'; 21 | this.data = data; 22 | } 23 | } 24 | 25 | export { MessageError }; 26 | -------------------------------------------------------------------------------- /src/modules/baseMessageModule/types/MessageContent.ts: -------------------------------------------------------------------------------- 1 | import { VmsTtsLector } from '../../vms/types/VmsTtsLector'; 2 | 3 | export interface SmsContent { 4 | message: string; 5 | } 6 | 7 | export interface MmsContent { 8 | smil: string; 9 | subject: string; 10 | } 11 | 12 | export interface VmsTextContent { 13 | tts: string; 14 | ttsLector?: VmsTtsLector; 15 | } 16 | 17 | export interface VmsLocalFileContent { 18 | localPath: string; 19 | } 20 | 21 | export interface VmsRemoteFileContent { 22 | remotePath: string; 23 | } 24 | 25 | export type MessageContent = 26 | | MmsContent 27 | | SmsContent 28 | | VmsLocalFileContent 29 | | VmsRemoteFileContent 30 | | VmsTextContent; 31 | -------------------------------------------------------------------------------- /src/modules/mfa/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseModule } from '../baseModule'; 2 | 3 | import { MfaGenerateCodeOptions } from './types/MfaGenerateCodeOptions'; 4 | import { MfaResponse } from './types/MfaResponse'; 5 | 6 | export class Mfa extends BaseModule { 7 | async generateCode( 8 | number: string, 9 | options?: MfaGenerateCodeOptions, 10 | ): Promise { 11 | return await this.httpClient.post('/mfa/codes', { 12 | phone_number: number, 13 | ...options, 14 | }); 15 | } 16 | 17 | async verifyCode(number: string, code: string): Promise { 18 | return await this.httpClient.post('/mfa/codes/verifications', { 19 | code, 20 | phone_number: number, 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/types/MessageResponse.ts: -------------------------------------------------------------------------------- 1 | import { MessageStatus } from './MessageStatus'; 2 | 3 | export interface ApiMessageResponse { 4 | count: number; 5 | list: { 6 | id: string; 7 | points: number; 8 | number: string; 9 | dateSent: number; 10 | submittedNumber: string; 11 | status: MessageStatus; 12 | idx: string | null; 13 | parts?: number; 14 | }[]; 15 | message?: string; 16 | length: number; 17 | parts?: number; 18 | } 19 | 20 | export interface MessageResponse extends Omit { 21 | list: { 22 | id: string; 23 | points: number; 24 | number: string; 25 | dateSent: Date; 26 | submittedNumber: string; 27 | status: MessageStatus; 28 | idx: string | null; 29 | parts?: number; 30 | }[]; 31 | } 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "exclude": ["src/testHelpers", "src/**/*.spec.ts"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./src", 11 | "strict": true, 12 | "noImplicitAny": false, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "useUnknownInCatchVariables": false, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "moduleResolution": "node", 19 | "jsx": "react", 20 | "esModuleInterop": true, 21 | "skipLibCheck": true, 22 | "forceConsistentCasingInFileNames": true, 23 | "strictPropertyInitialization": false, 24 | "noEmit": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | node-version: [18.x, 20.x, 22.x, 24.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v5 20 | 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v5 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | 26 | - name: Install deps and build (with cache) 27 | uses: bahmutov/npm-install@v1 28 | 29 | - name: Typecheck 30 | run: npm run typecheck 31 | 32 | - name: Test package 33 | run: npm run test 34 | 35 | - name: Lint 36 | run: npm run lint 37 | -------------------------------------------------------------------------------- /src/modules/hlr/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseModule } from '../baseModule'; 2 | 3 | import { HlrCheckResponse } from './types/HlrCheckResponse'; 4 | 5 | export class Hlr extends BaseModule { 6 | async check( 7 | numbers: string | string[], 8 | idx?: string | string[], 9 | ): Promise { 10 | const params: Record = { 11 | number: Array.isArray(numbers) ? numbers.join(',') : numbers, 12 | }; 13 | 14 | if (idx) { 15 | params.idx = Array.isArray(idx) ? idx.join(',') : idx; 16 | } 17 | 18 | return await this.httpClient.post( 19 | '/hlr.do', 20 | undefined, 21 | { 22 | params: { 23 | format: 'json', 24 | ...params, 25 | }, 26 | }, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es2020": true, 4 | "node": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:import/errors", 9 | "plugin:import/warnings", 10 | "plugin:import/typescript", 11 | "plugin:@typescript-eslint/eslint-recommended", 12 | "plugin:@typescript-eslint/recommended", 13 | "plugin:prettier/recommended", 14 | "prettier" 15 | ], 16 | "parser": "@typescript-eslint/parser", 17 | "parserOptions": { 18 | "ecmaVersion": 11, 19 | "sourceType": "module" 20 | }, 21 | "rules": { 22 | "prettier/prettier": "error", 23 | "sort-keys": "error", 24 | "import/order": [ 25 | "error", 26 | { 27 | "newlines-between": "always" 28 | } 29 | ], 30 | "import/no-default-export": "error" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/mms/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseMessageModule } from '../baseMessageModule'; 2 | import { MessageResponse } from '../../types/MessageResponse'; 3 | 4 | import { MmsDetails } from './types/MmsDetails'; 5 | 6 | export class Mms extends BaseMessageModule { 7 | endpoint = '/mms.do'; 8 | 9 | async sendMms( 10 | numbers: string | string[], 11 | subject: string, 12 | smil: string, 13 | details?: MmsDetails, 14 | ): Promise { 15 | return await this.send( 16 | { 17 | smil, 18 | subject, 19 | }, 20 | { 21 | to: numbers, 22 | }, 23 | details, 24 | ); 25 | } 26 | 27 | async sendMmsToGroup( 28 | groups: string | string[], 29 | subject: string, 30 | smil: string, 31 | details?: MmsDetails, 32 | ): Promise { 33 | return await this.send( 34 | { 35 | smil, 36 | subject, 37 | }, 38 | { 39 | group: groups, 40 | }, 41 | details, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/modules/sms/types/SmsDetails.ts: -------------------------------------------------------------------------------- 1 | import { BaseMessageDetails } from '../../baseMessageModule/types/BaseMessageDetails'; 2 | 3 | type SmsEncoding = 4 | | 'iso-8859-1' 5 | | 'iso-8859-2' 6 | | 'iso-8859-3' 7 | | 'iso-8859-4' 8 | | 'iso-8859-5' 9 | | 'iso-8859-7' 10 | | 'windows-1250' 11 | | 'windows-1251' 12 | | 'utf-8'; 13 | 14 | export interface SmsDetails extends BaseMessageDetails { 15 | from?: string | '2way'; 16 | encoding?: SmsEncoding; 17 | flash?: boolean; 18 | timeRestriction?: 'follow' | 'ignore' | 'nearest_available'; 19 | udh?: string; 20 | skipForeign?: boolean; 21 | allowDuplicates?: boolean; 22 | checkIdx?: boolean; 23 | noUnicode?: boolean; 24 | normalize?: boolean; 25 | fast?: boolean; 26 | partnerId?: string; 27 | maxParts?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; 28 | expirationDate?: Date; 29 | discountGroup?: string; 30 | param1?: string; 31 | param2?: string; 32 | param3?: string; 33 | param4?: string; 34 | template?: string; 35 | datacoding?: 'bin'; 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/contacts/modules/fields/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseModule } from '../../../baseModule'; 2 | import { ApiCollection } from '../../../../types/ApiCollection'; 3 | 4 | import { Field } from './types/Field'; 5 | import { FieldType } from './types/FieldType'; 6 | 7 | export class Fields extends BaseModule { 8 | async get(): Promise> { 9 | return await this.httpClient.get>('/contacts/fields'); 10 | } 11 | 12 | async create( 13 | fieldName: string, 14 | fieldType: FieldType = 'text', 15 | ): Promise { 16 | return await this.httpClient.post('/contacts/fields', { 17 | name: fieldName, 18 | type: fieldType, 19 | }); 20 | } 21 | 22 | async update(fieldId: string, newName: string): Promise { 23 | return await this.httpClient.put(`/contacts/fields/${fieldId}`, { 24 | name: newName, 25 | }); 26 | } 27 | 28 | async remove(fieldId: string): Promise { 29 | await this.httpClient.delete(`/contacts/fields/${fieldId}`); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SMSAPI JavaScript (node.js) Client 2 | 3 | ## Installation 4 | 5 | ```bash 6 | npm install smsapi --save 7 | ``` 8 | 9 | ## Example 10 | 11 | ```ts 12 | import { SMSAPI } from 'smsapi'; 13 | 14 | const smsapi = new SMSAPI('oAuthToken'); 15 | 16 | try { 17 | const result = await smsapi.sms.sendSms('+48605xxxxxx', 'My first message!'); 18 | 19 | console.log(result); 20 | } catch (err) { 21 | console.log(err); 22 | } 23 | ``` 24 | 25 | ## Supported modules 26 | 27 | * Contacts 28 | * Contact fields 29 | * Contact groups 30 | * Hlr 31 | * MFA 32 | * MMS 33 | * Profile 34 | * Sendernames 35 | * SMS 36 | * Subusers 37 | * Templates 38 | * VMS 39 | 40 | ## Docs & Infos 41 | 42 | * [SMSAPI.com API documentation](https://www.smsapi.com/docs) 43 | * [SMSAPI.pl API documentation](https://www.smsapi.pl/docs) 44 | * [Repository on GitHub](https://github.com/smsapi/smsapi-javascript-client) 45 | * [Package on npm](https://www.npmjs.com/package/smsapi) 46 | * [SMSAPI.com web page](https://smsapi.com) 47 | * [SMSAPI.pl web page](https://smsapi.pl) 48 | 49 | ## License 50 | 51 | [Apache 2.0 License](LICENSE) 52 | -------------------------------------------------------------------------------- /src/modules/profile/index.spec.ts: -------------------------------------------------------------------------------- 1 | import nock from 'nock'; 2 | 3 | import { API_URL } from '../../constants'; 4 | import { SMSAPI } from '../../smsapi'; 5 | 6 | describe('Profile', () => { 7 | afterEach(() => { 8 | nock.cleanAll(); 9 | }); 10 | 11 | it('should get profile', async () => { 12 | // given 13 | const smsapi = new SMSAPI('someToken'); 14 | 15 | const req = nock(API_URL).get('/profile').reply(200, { 16 | email: 'someEmail', 17 | id: 'someId', 18 | name: 'someName', 19 | payment_type: 'trial', 20 | phone_number: 'somePhoneNumber', 21 | points: 1, 22 | user_type: 'native', 23 | username: 'someUsername', 24 | }); 25 | 26 | // when 27 | const profile = await smsapi.profile.get(); 28 | 29 | // then 30 | expect(req.isDone()).toBeTruthy(); 31 | expect(profile).toEqual({ 32 | email: 'someEmail', 33 | id: 'someId', 34 | name: 'someName', 35 | paymentType: 'trial', 36 | phoneNumber: 'somePhoneNumber', 37 | points: 1, 38 | userType: 'native', 39 | username: 'someUsername', 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/modules/contacts/httpClient/formatResponseDates/index.ts: -------------------------------------------------------------------------------- 1 | import { ApiCollection } from '../../../../types/ApiCollection'; 2 | 3 | interface ApiGroup { 4 | date_created: string; 5 | date_updated: string; 6 | } 7 | 8 | const isApiCollection = ( 9 | data: ApiGroup | ApiCollection, 10 | ): data is ApiCollection => { 11 | return ( 12 | !!(data as ApiCollection).size && 13 | !!(data as ApiCollection).collection 14 | ); 15 | }; 16 | 17 | const formatDates = (group: ApiGroup): Record => { 18 | if (!group.date_created && !group.date_updated) { 19 | return { 20 | ...group, 21 | }; 22 | } 23 | 24 | return { 25 | ...group, 26 | date_created: new Date(group.date_created), 27 | date_updated: new Date(group.date_updated), 28 | }; 29 | }; 30 | 31 | export const formatResponseDates = (response) => { 32 | const { data } = response; 33 | 34 | if (isApiCollection(data)) { 35 | return { 36 | ...response, 37 | data: { 38 | collection: data.collection.map((group) => formatDates(group)), 39 | size: data.size, 40 | }, 41 | }; 42 | } 43 | 44 | return { 45 | ...response, 46 | data: formatDates(data), 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/modules/templates/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseModule } from '../baseModule'; 2 | import { ApiCollection } from '../../types'; 3 | import { RequestConfig } from '../../smsapi/httpClient'; 4 | 5 | import { NewTemplate } from './types/NewTemplate'; 6 | import { Template } from './types/Template'; 7 | 8 | export class Templates extends BaseModule { 9 | async get(): Promise> { 10 | return await this.httpClient.get>('/sms/templates'); 11 | } 12 | 13 | async getById(templateId: string): Promise