@unboundcx/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/base.js ADDED
@@ -0,0 +1,263 @@
1
+ /* eslint-disable indent */
2
+ /* eslint-disable prettier/prettier */
3
+
4
+ /*
5
+ * Base SDK Class with Transport Plugin System
6
+ *
7
+ * Optional Dependencies:
8
+ * - mime-types: For accurate MIME type detection (automatically imported if available)
9
+ *
10
+ * This package is optional and the SDK will gracefully fall back to built-in MIME type
11
+ * detection if not installed. For best MIME type accuracy, it's recommended to install:
12
+ *
13
+ * npm install mime-types
14
+ */
15
+
16
+ export class BaseSDK {
17
+ constructor(options = {}) {
18
+ // Support both object and legacy positional parameters for backwards compatibility
19
+ if (typeof options === 'string') {
20
+ // Legacy positional parameters: (namespace, callId, token, fwRequestId)
21
+ this.namespace = options || process?.env?.namespace;
22
+ this.callId = arguments[1];
23
+ this.token = arguments[2];
24
+ this.fwRequestId = arguments[3];
25
+ } else {
26
+ // New object-based parameters
27
+ const { namespace, callId, token, fwRequestId } = options;
28
+ this.namespace = namespace || process?.env?.namespace;
29
+ this.callId = callId;
30
+ this.token = token;
31
+ this.fwRequestId = fwRequestId;
32
+ }
33
+
34
+ this.transports = new Map();
35
+ this._initializeEnvironment();
36
+ }
37
+
38
+ _initializeEnvironment() {
39
+ if (typeof window === 'undefined') {
40
+ // Server-side (Node.js)
41
+ this.environment = 'node';
42
+ this.baseURL = `https://${this.namespace ? this.namespace : 'login'}.${
43
+ process.env?.API_BASE_URL
44
+ }`;
45
+ } else {
46
+ // Client-side (browser)
47
+ this.environment = 'browser';
48
+ this.baseUrl = this.baseUrl || process?.env?.API_BASE_URL;
49
+ if (this.baseUrl && !this.baseUrl.startsWith('api.')) {
50
+ this.baseUrl = `api.${this.baseUrl}`;
51
+ }
52
+ this.setNamespace(this.namespace);
53
+ }
54
+ }
55
+
56
+ setToken(token) {
57
+ this.token = token;
58
+ }
59
+
60
+ setNamespace(namespace) {
61
+ this.namespace = namespace;
62
+
63
+ if (this.environment === 'node') {
64
+ this.baseURL = `https://${this.namespace ? this.namespace : 'login'}.${
65
+ process.env?.API_BASE_URL
66
+ }`;
67
+ } else {
68
+ this.fullUrl = `https://${this.namespace}.${this.baseUrl}`;
69
+ }
70
+ }
71
+
72
+ addTransport(transport) {
73
+ if (!transport || typeof transport.request !== 'function') {
74
+ throw new Error('Transport must have a request method');
75
+ }
76
+
77
+ const priority = transport.getPriority ? transport.getPriority() : 50;
78
+ const name = transport.name || `transport_${Date.now()}`;
79
+
80
+ this.transports.set(name, {
81
+ transport,
82
+ priority
83
+ });
84
+ }
85
+
86
+ removeTransport(name) {
87
+ this.transports.delete(name);
88
+ }
89
+
90
+ async _getAvailableTransport(forceFetch = false) {
91
+ if (forceFetch) {
92
+ return null; // Use built-in HTTP
93
+ }
94
+
95
+ // Sort transports by priority (lower number = higher priority)
96
+ const sortedTransports = Array.from(this.transports.values())
97
+ .sort((a, b) => a.priority - b.priority);
98
+
99
+ for (const { transport } of sortedTransports) {
100
+ try {
101
+ if (transport.isAvailable && await transport.isAvailable()) {
102
+ return transport;
103
+ }
104
+ } catch (err) {
105
+ console.debug(`Transport ${transport.name} not available:`, err.message);
106
+ continue;
107
+ }
108
+ }
109
+
110
+ return null; // Fall back to HTTP
111
+ }
112
+
113
+ validateParams(params, schema) {
114
+ for (const key in schema) {
115
+ if (params[key] === undefined && schema[key].required) {
116
+ throw new Error(`Missing required parameter ${key}`);
117
+ }
118
+
119
+ if (params[key] !== undefined) {
120
+ const expectedType = schema[key].type;
121
+ const actualValue = params[key];
122
+ let isValidType = false;
123
+
124
+ if (expectedType === 'array') {
125
+ isValidType = Array.isArray(actualValue);
126
+ } else {
127
+ isValidType = typeof actualValue === expectedType;
128
+ }
129
+
130
+ if (!isValidType) {
131
+ const actualType = Array.isArray(actualValue)
132
+ ? 'array'
133
+ : typeof actualValue;
134
+ throw new Error(
135
+ `Invalid type for parameter ${key}: expected ${expectedType}, got ${actualType}`,
136
+ );
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ async _fetch(endpoint, method, params = {}, forceFetch = false) {
143
+ const { body, query, headers = {} } = params;
144
+
145
+ this.validateParams(
146
+ { endpoint, method, body, query, headers },
147
+ {
148
+ endpoint: { type: 'string', required: true },
149
+ method: { type: 'string', required: true },
150
+ body: { type: 'object', required: false },
151
+ query: { type: 'object', required: false },
152
+ headers: { type: 'object', required: false },
153
+ },
154
+ );
155
+
156
+ // Try transport plugins first
157
+ const transport = await this._getAvailableTransport(forceFetch);
158
+ if (transport) {
159
+ try {
160
+ const result = await transport.request(endpoint, method, params, {
161
+ namespace: this.namespace,
162
+ token: this.token,
163
+ callId: this.callId,
164
+ fwRequestId: this.fwRequestId,
165
+ baseURL: this.baseURL || this.fullUrl,
166
+ });
167
+ return result;
168
+ } catch (err) {
169
+ console.warn(`Transport ${transport.name} failed, falling back to HTTP:`, err.message);
170
+ // Fall through to HTTP
171
+ }
172
+ }
173
+
174
+ // Built-in HTTP transport (fallback)
175
+ return this._httpRequest(endpoint, method, params);
176
+ }
177
+
178
+ async _httpRequest(endpoint, method, params = {}) {
179
+ const { body, query, headers = {} } = params;
180
+
181
+ const options = {
182
+ method,
183
+ headers: {
184
+ // Only set Content-Type if not already provided (check both cases)
185
+ ...(headers?.['Content-Type'] || headers?.['content-type']
186
+ ? {}
187
+ : { 'Content-Type': 'application/json' }),
188
+ ...headers,
189
+ },
190
+ };
191
+
192
+ // Add auth headers
193
+ if (this.token) {
194
+ options.headers.Authorization = `Bearer ${this.token}`;
195
+ }
196
+ if (this.fwRequestId) {
197
+ options.headers['x-request-id-fw'] = this.fwRequestId;
198
+ }
199
+ if (this.callId) {
200
+ options.headers['x-call-id'] = this.callId;
201
+ }
202
+
203
+ // Set credentials for browser environment
204
+ if (this.environment === 'browser') {
205
+ options.credentials = 'include';
206
+ }
207
+
208
+ let url;
209
+ if (this.environment === 'node') {
210
+ url = `${this.baseURL}${endpoint}`;
211
+ } else {
212
+ url = `${this.fullUrl}${endpoint}`;
213
+ }
214
+
215
+ // Add query parameters
216
+ if (query) {
217
+ const params = new URLSearchParams(query).toString();
218
+ url += `?${params}`;
219
+ }
220
+
221
+ // Handle body
222
+ if (options.method.toLowerCase() === 'get') {
223
+ delete options.body;
224
+ } else if (body) {
225
+ // For FormData or Buffer, pass directly without JSON.stringify
226
+ const isFormData =
227
+ body &&
228
+ (body.constructor.name === 'FormData' ||
229
+ typeof body.getBoundary === 'function');
230
+ const isBuffer = Buffer && Buffer.isBuffer && Buffer.isBuffer(body);
231
+
232
+ if (isFormData || isBuffer) {
233
+ options.body = body;
234
+ } else {
235
+ options.body = JSON.stringify(body);
236
+ }
237
+ }
238
+
239
+ const response = await fetch(url, options);
240
+ const bodyResponse = await response.json();
241
+
242
+ const responseHeaders = response.headers;
243
+ const responseRequestId =
244
+ responseHeaders?.get?.('x-request-id') ||
245
+ responseHeaders?.['x-request-id'];
246
+
247
+ if (!response.ok) {
248
+ throw {
249
+ name: `API :: Error :: https :: ${method} :: ${endpoint} :: ${responseRequestId} :: ${response?.status} :: ${response?.statusText}`,
250
+ message: bodyResponse?.message || `API Error occured.`,
251
+ method,
252
+ endpoint,
253
+ status: response?.status,
254
+ statusText: response?.statusText,
255
+ };
256
+ }
257
+
258
+ console.log(
259
+ `API :: https :: ${method} :: ${endpoint} :: ${responseRequestId}`,
260
+ );
261
+ return bodyResponse;
262
+ }
263
+ }
package/index.js ADDED
@@ -0,0 +1,181 @@
1
+ /* eslint-disable indent */
2
+ /* eslint-disable prettier/prettier */
3
+
4
+ import { BaseSDK } from './base.js';
5
+ import { LoginService } from './services/login.js';
6
+ import { ObjectsService } from './services/objects.js';
7
+ import { MessagingService } from './services/messaging.js';
8
+ import { VideoService } from './services/video.js';
9
+ import { VoiceService } from './services/voice.js';
10
+ import { AIService } from './services/ai.js';
11
+ import { LookupService } from './services/lookup.js';
12
+ import { LayoutsService } from './services/layouts.js';
13
+ import { SubscriptionsService } from './services/subscriptions.js';
14
+ import { WorkflowsService } from './services/workflows.js';
15
+ import { NotesService } from './services/notes.js';
16
+ import { StorageService } from './services/storage.js';
17
+ import { VerificationService } from './services/verification.js';
18
+ import { PortalsService } from './services/portals.js';
19
+ import { SipEndpointsService } from './services/sipEndpoints.js';
20
+ import { ExternalOAuthService } from './services/externalOAuth.js';
21
+ import { GoogleCalendarService } from './services/googleCalendar.js';
22
+ import { EnrollService } from './services/enroll.js';
23
+ import { PhoneNumbersService } from './services/phoneNumbers.js';
24
+ import { RecordTypesService } from './services/recordTypes.js';
25
+ import { GenerateIdService } from './services/generateId.js';
26
+
27
+ class UnboundSDK extends BaseSDK {
28
+ constructor(options = {}) {
29
+ // Support both object and legacy positional parameters for backwards compatibility
30
+ if (typeof options === 'string') {
31
+ // Legacy positional parameters: (namespace, callId, token, fwRequestId, url, socketStore)
32
+ const namespace = options;
33
+ const callId = arguments[1];
34
+ const token = arguments[2];
35
+ const fwRequestId = arguments[3];
36
+ const url = arguments[4];
37
+ const socketStore = arguments[5];
38
+
39
+ super({ namespace, callId, token, fwRequestId });
40
+
41
+ // Handle client-side specific parameters
42
+ if (url) {
43
+ this.baseUrl = url;
44
+ this._initializeEnvironment();
45
+ }
46
+
47
+ if (socketStore) {
48
+ this.socketStore = socketStore;
49
+ }
50
+ } else {
51
+ // New object-based parameters
52
+ const { namespace, callId, token, fwRequestId, url, socketStore } = options;
53
+
54
+ super({ namespace, callId, token, fwRequestId });
55
+
56
+ // Handle client-side specific parameters
57
+ if (url) {
58
+ this.baseUrl = url;
59
+ this._initializeEnvironment();
60
+ }
61
+
62
+ if (socketStore) {
63
+ this.socketStore = socketStore;
64
+ }
65
+ }
66
+
67
+ // Initialize all service modules
68
+ this.login = new LoginService(this);
69
+ this.objects = new ObjectsService(this);
70
+ this.messaging = new MessagingService(this);
71
+ this.video = new VideoService(this);
72
+ this.voice = new VoiceService(this);
73
+ this.ai = new AIService(this);
74
+ this.lookup = new LookupService(this);
75
+ this.layouts = new LayoutsService(this);
76
+ this.subscriptions = new SubscriptionsService(this);
77
+ this.workflows = new WorkflowsService(this);
78
+ this.notes = new NotesService(this);
79
+ this.storage = new StorageService(this);
80
+ this.verification = new VerificationService(this);
81
+ this.portals = new PortalsService(this);
82
+ this.sipEndpoints = new SipEndpointsService(this);
83
+ this.externalOAuth = new ExternalOAuthService(this);
84
+ this.googleCalendar = new GoogleCalendarService(this);
85
+ this.enroll = new EnrollService(this);
86
+ this.phoneNumbers = new PhoneNumbersService(this);
87
+ this.recordTypes = new RecordTypesService(this);
88
+ this.generateId = new GenerateIdService(this);
89
+
90
+ // Add additional services that might be missing
91
+ this._initializeAdditionalServices();
92
+ }
93
+
94
+ _initializeAdditionalServices() {
95
+ // Placeholder for additional services that might be discovered
96
+ // This will be populated as we find missing APIs
97
+ }
98
+
99
+ // Extension method for plugins (internal SDK, transport plugins, etc.)
100
+ use(plugin) {
101
+ if (typeof plugin === 'function') {
102
+ plugin(this);
103
+ } else if (plugin && typeof plugin.install === 'function') {
104
+ plugin.install(this);
105
+ } else {
106
+ throw new Error('Plugin must be a function or have an install method');
107
+ }
108
+ return this;
109
+ }
110
+
111
+ extend(extension) {
112
+ if (typeof extension === 'function') {
113
+ // Extension is a class constructor
114
+ const instance = new extension(this);
115
+
116
+ // Merge extension methods/properties into this SDK
117
+ for (const key in instance) {
118
+ if (instance.hasOwnProperty(key) && typeof instance[key] !== 'undefined') {
119
+ this[key] = instance[key];
120
+ }
121
+ }
122
+ } else if (typeof extension === 'object') {
123
+ // Extension is an object with methods
124
+ Object.assign(this, extension);
125
+ } else {
126
+ throw new Error('Extension must be a class constructor or object');
127
+ }
128
+ return this;
129
+ }
130
+
131
+ // Transport plugin methods
132
+ addTransport(transport) {
133
+ super.addTransport(transport);
134
+ return this;
135
+ }
136
+
137
+ removeTransport(name) {
138
+ super.removeTransport(name);
139
+ return this;
140
+ }
141
+
142
+ // Backwards compatibility helper methods
143
+ async buildMasterAuth({ namespace, accountId, userId }) {
144
+ // This method should only be available in Node.js environment
145
+ // and will be added via internal SDK extension
146
+ throw new Error('buildMasterAuth is only available with the internal SDK extension. Please use: sdk.use(InternalExtension)');
147
+ }
148
+ }
149
+
150
+ // Export both the class and a factory function for convenience
151
+ export default UnboundSDK;
152
+ export { UnboundSDK };
153
+
154
+ // Factory function for common usage patterns
155
+ export function createSDK(options = {}) {
156
+ return new UnboundSDK(options);
157
+ }
158
+
159
+ // Re-export service classes for advanced usage
160
+ export { LoginService } from './services/login.js';
161
+ export { ObjectsService } from './services/objects.js';
162
+ export { MessagingService } from './services/messaging.js';
163
+ export { VideoService } from './services/video.js';
164
+ export { VoiceService } from './services/voice.js';
165
+ export { AIService } from './services/ai.js';
166
+ export { LookupService } from './services/lookup.js';
167
+ export { LayoutsService } from './services/layouts.js';
168
+ export { SubscriptionsService } from './services/subscriptions.js';
169
+ export { WorkflowsService } from './services/workflows.js';
170
+ export { NotesService } from './services/notes.js';
171
+ export { StorageService } from './services/storage.js';
172
+ export { VerificationService } from './services/verification.js';
173
+ export { PortalsService } from './services/portals.js';
174
+ export { SipEndpointsService } from './services/sipEndpoints.js';
175
+ export { ExternalOAuthService } from './services/externalOAuth.js';
176
+ export { GoogleCalendarService } from './services/googleCalendar.js';
177
+ export { EnrollService } from './services/enroll.js';
178
+ export { PhoneNumbersService, PhoneNumberCarrierService } from './services/phoneNumbers.js';
179
+ export { RecordTypesService, UserRecordTypeDefaultsService } from './services/recordTypes.js';
180
+ export { GenerateIdService } from './services/generateId.js';
181
+ export { BaseSDK } from './base.js';
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@unboundcx/sdk",
3
+ "version": "1.0.0",
4
+ "description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=16.0.0"
9
+ },
10
+ "keywords": [
11
+ "unbound",
12
+ "api",
13
+ "sdk",
14
+ "messaging",
15
+ "voice",
16
+ "video",
17
+ "ai",
18
+ "communication",
19
+ "javascript",
20
+ "typescript",
21
+ "sms",
22
+ "email",
23
+ "workflows"
24
+ ],
25
+ "author": {
26
+ "name": "Unbound Team",
27
+ "email": "support@unbound.cx",
28
+ "url": "https://unbound.cx"
29
+ },
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/unbound/sdk-js.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/unbound/sdk-js/issues"
37
+ },
38
+ "homepage": "https://docs.unbound.cx/sdk",
39
+ "files": [
40
+ "*.js",
41
+ "services/**/*.js",
42
+ "transports/**/*.js",
43
+ "types/**/*.d.ts",
44
+ "README.md",
45
+ "LICENSE"
46
+ ],
47
+ "exports": {
48
+ ".": {
49
+ "import": "./index.js",
50
+ "require": "./index.cjs"
51
+ },
52
+ "./base": {
53
+ "import": "./base.js"
54
+ },
55
+ "./services/*": {
56
+ "import": "./services/*.js"
57
+ }
58
+ },
59
+ "scripts": {
60
+ "build": "echo 'Build complete - ESM modules ready'",
61
+ "test": "echo 'Tests would run here'",
62
+ "lint": "echo 'Linting would run here'",
63
+ "prepublishOnly": "npm run build"
64
+ },
65
+ "dependencies": {},
66
+ "optionalDependencies": {
67
+ "mime-types": "^2.1.35"
68
+ },
69
+ "peerDependencies": {
70
+ "socket.io-client": "^4.0.0"
71
+ },
72
+ "peerDependenciesMeta": {
73
+ "socket.io-client": {
74
+ "optional": true
75
+ }
76
+ },
77
+ "devDependencies": {},
78
+ "browserslist": [
79
+ "defaults",
80
+ "not IE 11",
81
+ "not IE_Mob 11",
82
+ "maintained node versions"
83
+ ],
84
+ "publishConfig": {
85
+ "registry": "https://registry.npmjs.org/",
86
+ "access": "public"
87
+ }
88
+ }
package/services/ai.js ADDED
@@ -0,0 +1,151 @@
1
+ export class AIService {
2
+ constructor(sdk) {
3
+ this.sdk = sdk;
4
+ this.generative = new GenerativeService(sdk);
5
+ this.tts = new TextToSpeechService(sdk);
6
+ }
7
+ }
8
+
9
+ export class GenerativeService {
10
+ constructor(sdk) {
11
+ this.sdk = sdk;
12
+ }
13
+
14
+ async chat({ prompt, messages, relatedId, model, temperature, subscriptionId, stream, method }) {
15
+ this.sdk.validateParams(
16
+ { method },
17
+ {
18
+ prompt: { type: 'string', required: false },
19
+ messages: { type: 'array', required: false },
20
+ relatedId: { type: 'string', required: false },
21
+ model: { type: 'string', required: false },
22
+ temperature: { type: 'number', required: false },
23
+ subscriptionId: { type: 'string', required: false },
24
+ stream: { type: 'boolean', required: false },
25
+ method: { type: 'string', required: true }
26
+ },
27
+ );
28
+
29
+ const params = {
30
+ body: {
31
+ prompt,
32
+ messages,
33
+ relatedId,
34
+ model,
35
+ temperature,
36
+ subscriptionId,
37
+ stream,
38
+ method,
39
+ }
40
+ }
41
+
42
+ const result = await this.sdk._fetch('/ai/generative/chat', 'POST', params);
43
+ return result;
44
+ }
45
+
46
+ async playbook({ prompt, messages, relatedId, model, temperature, subscriptionId, stream, playbookId, sessionId }) {
47
+ this.sdk.validateParams(
48
+ { playbookId },
49
+ {
50
+ prompt: { type: 'string', required: false },
51
+ messages: { type: 'array', required: false },
52
+ relatedId: { type: 'string', required: false },
53
+ model: { type: 'string', required: false },
54
+ temperature: { type: 'number', required: false },
55
+ subscriptionId: { type: 'string', required: false },
56
+ stream: { type: 'boolean', required: false },
57
+ playbookId: { type: 'string', required: true },
58
+ sessionId: { type: 'string', required: false }
59
+ },
60
+ );
61
+
62
+ const params = {
63
+ body: {
64
+ prompt,
65
+ messages,
66
+ relatedId,
67
+ model,
68
+ temperature,
69
+ subscriptionId,
70
+ stream,
71
+ playbookId,
72
+ sessionId,
73
+ }
74
+ }
75
+
76
+ const result = await this.sdk._fetch('/ai/generative/playbook', 'POST', params);
77
+ return result;
78
+ }
79
+
80
+ async chatOllama({ prompt, messages, relatedId, model, temperature, subscriptionId, stream, method }) {
81
+ this.sdk.validateParams(
82
+ { method },
83
+ {
84
+ prompt: { type: 'string', required: false },
85
+ messages: { type: 'array', required: false },
86
+ relatedId: { type: 'string', required: false },
87
+ model: { type: 'string', required: false },
88
+ temperature: { type: 'number', required: false },
89
+ subscriptionId: { type: 'string', required: false },
90
+ stream: { type: 'boolean', required: false },
91
+ method: { type: 'string', required: true }
92
+ },
93
+ );
94
+
95
+ const params = {
96
+ body: {
97
+ prompt,
98
+ messages,
99
+ relatedId,
100
+ model,
101
+ temperature,
102
+ subscriptionId,
103
+ stream,
104
+ method,
105
+ }
106
+ }
107
+
108
+ const result = await this.sdk._fetch('/ai/generative/ollama', 'POST', params);
109
+ return result;
110
+ }
111
+ }
112
+
113
+ export class TextToSpeechService {
114
+ constructor(sdk) {
115
+ this.sdk = sdk;
116
+ }
117
+
118
+ async create({ text, voice, languageCode, ssmlGender, audioEncoding, speakingRate, pitch, volumeGainDb, effectsProfileIds }) {
119
+ this.sdk.validateParams(
120
+ { text },
121
+ {
122
+ text: { type: 'string', required: true },
123
+ voice: { type: 'string', required: false },
124
+ languageCode: { type: 'string', required: false },
125
+ ssmlGender: { type: 'string', required: false },
126
+ audioEncoding: { type: 'string', required: false },
127
+ speakingRate: { type: 'number', required: false },
128
+ pitch: { type: 'number', required: false },
129
+ volumeGainDb: { type: 'number', required: false },
130
+ effectsProfileIds: { type: 'array', required: false },
131
+ },
132
+ );
133
+
134
+ const ttsData = { text };
135
+ if (voice) ttsData.voice = voice;
136
+ if (languageCode) ttsData.languageCode = languageCode;
137
+ if (ssmlGender) ttsData.ssmlGender = ssmlGender;
138
+ if (audioEncoding) ttsData.audioEncoding = audioEncoding;
139
+ if (speakingRate) ttsData.speakingRate = speakingRate;
140
+ if (pitch) ttsData.pitch = pitch;
141
+ if (volumeGainDb) ttsData.volumeGainDb = volumeGainDb;
142
+ if (effectsProfileIds) ttsData.effectsProfileIds = effectsProfileIds;
143
+
144
+ const params = {
145
+ body: ttsData,
146
+ };
147
+
148
+ const result = await this.sdk._fetch('/ai/tts', 'POST', params);
149
+ return result;
150
+ }
151
+ }