UNPKG

@longears-mobile/rcs-sdk

Version:

Provider-agnostic SDK for RCS (Rich Communication Services) messaging

3 lines (2 loc) 13.2 kB
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("pino"),require("axios"),require("crypto")):"function"==typeof define&&define.amd?define(["exports","pino","axios","crypto"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).LongearsRcsSDK={},e.pino,e.axios,e.crypto)}(this,(function(e,t,i,s){"use strict";function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n,a=r(t),o=r(i),c=r(s);e.RCSErrorCode=void 0,(n=e.RCSErrorCode||(e.RCSErrorCode={})).UNKNOWN="UNKNOWN",n.INITIALIZATION_FAILED="INITIALIZATION_FAILED",n.NOT_INITIALIZED="NOT_INITIALIZED",n.PROVIDER_NOT_FOUND="PROVIDER_NOT_FOUND",n.PROVIDER_ERROR="PROVIDER_ERROR",n.AUTH_FAILED="AUTH_FAILED",n.AUTH_EXPIRED="AUTH_EXPIRED",n.AUTH_INVALID="AUTH_INVALID",n.MESSAGE_SEND_FAILED="MESSAGE_SEND_FAILED",n.MESSAGE_TOO_LONG="MESSAGE_TOO_LONG",n.INVALID_CONTENT="INVALID_CONTENT",n.INVALID_PHONE_NUMBER="INVALID_PHONE_NUMBER",n.RCS_NOT_SUPPORTED="RCS_NOT_SUPPORTED",n.CAPABILITY_CHECK_FAILED="CAPABILITY_CHECK_FAILED",n.FEATURE_NOT_SUPPORTED="FEATURE_NOT_SUPPORTED",n.NETWORK_ERROR="NETWORK_ERROR",n.TIMEOUT="TIMEOUT",n.RATE_LIMIT_EXCEEDED="RATE_LIMIT_EXCEEDED",n.VALIDATION_FAILED="VALIDATION_FAILED",n.INVALID_CONFIGURATION="INVALID_CONFIGURATION";class d extends Error{code;provider;details;timestamp;constructor(t,i=e.RCSErrorCode.UNKNOWN,s,r){super(t),this.name="RCSError",this.code=i,this.provider=s,this.details=r,this.timestamp=new Date,Object.setPrototypeOf(this,d.prototype)}toJSON(){return{name:this.name,message:this.message,code:this.code,provider:this.provider,details:this.details,timestamp:this.timestamp,stack:this.stack}}static fromProviderError(t,i){let s=e.RCSErrorCode.PROVIDER_ERROR,r=i.message||"Provider error occurred";if(i.code)switch(i.code){case 404:s=e.RCSErrorCode.RCS_NOT_SUPPORTED,r="Phone number does not support RCS";break;case 401:case 403:s=e.RCSErrorCode.AUTH_FAILED,r="Authentication failed";break;case 429:s=e.RCSErrorCode.RATE_LIMIT_EXCEEDED,r="Rate limit exceeded";break;case 400:i.message?.includes("phone")?s=e.RCSErrorCode.INVALID_PHONE_NUMBER:i.message?.includes("content")&&(s=e.RCSErrorCode.INVALID_CONTENT)}return new d(r,s,t,i)}static isRCSError(e){return e instanceof d}}const h=process.env.LOG_LEVEL||"info",u="development"===process.env.NODE_ENV,p=a.default({level:h,transport:u?{target:"pino-pretty",options:{colorize:!0,translateTime:"HH:MM:ss",ignore:"pid,hostname"}}:void 0,formatters:{level:e=>({level:e})},base:{sdk:"@longears-mobile/rcs-sdk"}});class l{auth;config;http;constructor(t,i){this.auth=t,this.config=i,this.http=o.default.create({timeout:i.timeout||3e4,headers:{"Content-Type":"application/json","User-Agent":`@longears-mobile/rcs-sdk/${process.env.npm_package_version||"0.1.0"}`}}),this.http.interceptors.request.use((async t=>{try{const e=await this.auth.authenticate();return e.type&&"Bearer"===e.type?t.headers.Authorization=`Bearer ${e.token}`:e.type&&"Basic"===e.type?t.headers.Authorization=`Basic ${e.token}`:t.headers.Authorization=e.token,t}catch(t){throw p.error("Authentication failed:",t),new d("Failed to authenticate request",e.RCSErrorCode.AUTH_FAILED,this.name,t)}})),this.http.interceptors.response.use((e=>e),(t=>{if(p.error(`HTTP request failed for ${this.name} provider:`,t),!t.response)throw t.request?new d("No response received from provider",e.RCSErrorCode.NETWORK_ERROR,this.name,t):new d("Request setup failed",e.RCSErrorCode.PROVIDER_ERROR,this.name,t);{const i=t.response.status,s=t.response.data;switch(i){case 401:case 403:throw new d("Authentication failed",e.RCSErrorCode.AUTH_FAILED,this.name,{status:i,data:s});case 404:throw new d("Resource not found",e.RCSErrorCode.PROVIDER_ERROR,this.name,{status:i,data:s});case 429:throw new d("Rate limit exceeded",e.RCSErrorCode.RATE_LIMIT_EXCEEDED,this.name,{status:i,data:s});default:throw d.fromProviderError(this.name,{code:i,message:s?.error||"Provider request failed",details:s})}}}))}async makeRequest(e,t={}){try{return(await this.http.request({url:e,...t})).data}catch(e){throw e}}}const E={E164:/^\+[1-9]\d{1,14}$/,US:/^(\+1)?[2-9]\d{2}[2-9](?!11)\d{6}$/,GENERAL:/^[\d\s\-\(\)\+]+$/};function g(e){return E.E164.test(e)}function m(e,t="US"){const i=e.replace(/[^\d+]/g,"");if(g(i))return i;if("US"===t){const e=i.replace(/^\+/,"");if(10===e.length)return`+1${e}`;if(11===e.length&&e.startsWith("1"))return`+${e}`}return null}class I extends l{name="longears";apiEndpoint;initialized=!1;constructor(e,t){super(e,t),this.apiEndpoint=t.apiEndpoint||"https://api.longears.mobi/v1",this.apiEndpoint.endsWith("/")&&(this.apiEndpoint=this.apiEndpoint.slice(0,-1))}async initialize(t){if(!this.initialized)try{p.info("Initializing Longears RCS provider"),t.apiEndpoint&&(this.apiEndpoint=t.apiEndpoint,this.apiEndpoint.endsWith("/")&&(this.apiEndpoint=this.apiEndpoint.slice(0,-1))),await this.testConnection(),this.initialized=!0,p.info("Longears RCS provider initialized successfully")}catch(t){throw p.error("Failed to initialize Longears RCS provider:",t),new d("Failed to initialize Longears RCS provider",e.RCSErrorCode.INITIALIZATION_FAILED,this.name,t)}}async sendMessage(t){if(!this.initialized)throw new d("Provider not initialized. Call initialize() first.",e.RCSErrorCode.NOT_INITIALIZED,this.name);if(!g(t.to))throw new d("Invalid phone number format. Must be in E.164 format.",e.RCSErrorCode.INVALID_PHONE_NUMBER,this.name,{phoneNumber:t.to});try{p.debug("Sending message via Longears RCS",{to:t.to});const e=this.transformMessage(t),i=await this.makeRequest(`${this.apiEndpoint}/messages`,{method:"POST",data:e});return p.info("Message sent successfully via Longears RCS",{messageId:i.messageId}),{messageId:i.messageId,status:"success"===i.status?"sent":"pending",timestamp:i.timestamp?new Date(i.timestamp):new Date,providerResponse:i}}catch(t){if(p.error("Failed to send message via Longears RCS:",t),t instanceof d)throw t;throw new d("Failed to send message via Longears RCS",e.RCSErrorCode.MESSAGE_SEND_FAILED,this.name,t)}}async validatePhoneNumber(t,i){if(!this.initialized)throw new d("Provider not initialized. Call initialize() first.",e.RCSErrorCode.NOT_INITIALIZED,this.name);try{const e=i?.agentId;p.debug("Checking capabilities via Longears RCS",{phoneNumber:t,...e?{agentId:e}:{}});const s=m(t)||t;let r=`${this.apiEndpoint}/capabilities?phoneNumber=${encodeURIComponent(s)}`;e&&(r+=`&agentId=${encodeURIComponent(e)}`);const n=await this.makeRequest(r,{method:"GET"});p.debug("Phone number capability result from Longears RCS",{phoneNumber:t,isRcsSupported:n.isRcsSupported});const a=[];return n.features&&(n.features.richCards&&a.push("RICHCARD_STANDALONE"),n.features.carousels&&a.push("RICHCARD_CAROUSEL"),n.features.suggestions&&(a.push("ACTION_DIAL"),a.push("ACTION_OPEN_URL"),a.push("ACTION_OPEN_URL_IN_WEBVIEW"),a.push("ACTION_SHARE_LOCATION"),a.push("ACTION_VIEW_LOCATION"),a.push("ACTION_CREATE_CALENDAR_EVENT"),n.features.supportedMediaTypes?.includes("compose")&&a.push("ACTION_COMPOSE"))),{success:!0,capability:{phoneNumber:s,isCapable:n.isRcsSupported,features:a,timestamp:(new Date).toISOString()}}}catch(e){if(p.error("Failed to check capabilities via Longears RCS:",e),e instanceof d)throw e;return{success:!1,error:`Failed to check RCS capabilities: ${e instanceof Error?e.message:String(e)}`}}}async testConnection(){try{await this.makeRequest(`${this.apiEndpoint}/status`,{method:"GET"})}catch(t){throw new d("Failed to connect to Longears API",e.RCSErrorCode.NETWORK_ERROR,this.name,t)}}transformMessage(e){const t={destination:e.to,metadata:e.metadata||{}};if(e.content.text&&(t.text=e.content.text),e.content.media&&(t.media={url:e.content.media.url,type:e.content.media.type,thumbnailUrl:e.content.media.thumbnailUrl,mimeType:e.content.media.mimeType,fileName:e.content.media.fileName}),e.content.richCard){const i=e.content.richCard;"standalone"===i.type?t.richCard={type:"standalone",title:i.title,description:i.description,media:i.media,orientation:i.orientation||"vertical",suggestions:i.suggestions}:"carousel"===i.type&&(t.richCard={type:"carousel",cards:i.cards,width:i.width||"medium"})}return e.suggestions&&e.suggestions.length>0&&(t.suggestions=e.suggestions.map((e=>({type:e.type,text:e.text,postbackData:e.postbackData,action:e.action})))),t}}class R{type="custom";credentials;token=null;tokenExpiresAt=null;constructor(t){if(!t.apiKey||!t.apiSecret)throw new d("Longears authentication requires apiKey and apiSecret",e.RCSErrorCode.AUTH_INVALID,"longears");this.credentials=t}async authenticate(){try{if(p.debug("Authenticating with Longears"),this.isValid())return p.debug("Using existing Longears token"),this.token;const e=Date.now().toString(),t=this.generateSignature(e),i=new Date(Date.now()+36e5);return this.token={token:t,type:"Custom",expiresAt:i},this.tokenExpiresAt=i,p.debug("Longears authentication successful"),this.token}catch(t){throw p.error("Longears authentication failed:",t),new d("Failed to authenticate with Longears",e.RCSErrorCode.AUTH_FAILED,"longears",t)}}async refresh(){return p.debug("Refreshing Longears authentication"),this.token=null,this.tokenExpiresAt=null,this.authenticate()}isValid(){if(!this.token||!this.tokenExpiresAt)return!1;return new Date<this.tokenExpiresAt}generateSignature(e){const t=`${this.credentials.apiKey}:${e}`,i=c.default.createHmac("sha256",this.credentials.apiSecret);i.update(t);const s=i.digest("hex");return`${this.credentials.apiKey}:${e}:${s}`}getAuthHeaders(){if(!this.token)throw new d("No valid token available. Call authenticate() first.",e.RCSErrorCode.AUTH_FAILED,"longears");return{"X-Longears-Auth":this.token.token}}}e.MessageBuilder=class{message={};constructor(e){e&&(this.message.to=e),this.message.content={}}to(e){return this.message.to=e,this}setText(e){return this.message.content||(this.message.content={}),this.message.content.text=e,this}addMedia(e,t,i){return this.message.content||(this.message.content={}),this.message.content.media={url:e,type:t,thumbnailUrl:i},this}addReply(e,t){return this.message.suggestions||(this.message.suggestions=[]),this.message.suggestions.push({type:"reply",text:e,postbackData:t||e}),this}addAction(e,t,i){this.message.suggestions||(this.message.suggestions=[]);const s={type:"action",text:e,action:{type:t,data:i}};return this.message.suggestions.push(s),this}setMetadata(e){return this.message.metadata=e,this}build(){if(!this.message.to)throw new Error("Recipient phone number is required");if(!this.message.content||!this.message.content.text&&!this.message.content.media&&!this.message.content.richCard)throw new Error("Message must have content");return this.message}},e.RCSClient=class{provider;auth;config;initialized=!1;constructor(t){this.config=t,this.auth=function(t){if("longears"===t.type)return new R(t.credentials);throw new d(`Unknown auth type: ${t.type}`,e.RCSErrorCode.AUTH_INVALID,t.type)}(t.auth),this.provider=function(t,i,s){switch(t.toLowerCase()){case"longears":case"longears-rcs":return new I(i,s);default:throw new d(`Unknown provider: ${t}`,e.RCSErrorCode.PROVIDER_NOT_FOUND,t)}}(t.provider,this.auth,t.options||{})}async initialize(){if(!this.initialized)try{p.info(`Initializing RCS client with provider: ${this.config.provider}`),await this.auth.authenticate(),await this.provider.initialize(this.config.options||{}),this.initialized=!0,p.info("RCS client initialized successfully")}catch(t){throw p.error("Failed to initialize RCS client:",t),new d("Failed to initialize RCS client",e.RCSErrorCode.INITIALIZATION_FAILED,this.config.provider,t)}}async sendMessage(t){this.ensureInitialized();try{p.debug("Sending RCS message:",{to:t.to,provider:this.config.provider});const e=await this.provider.sendMessage({to:t.to,content:t.content,suggestions:t.suggestions,metadata:t.metadata});return p.info("Message sent successfully:",{messageId:e.messageId}),e}catch(t){if(p.error("Failed to send message:",t),t instanceof d)throw t;throw new d("Failed to send message",e.RCSErrorCode.MESSAGE_SEND_FAILED,this.config.provider,t)}}async validatePhoneNumber(e,t){this.ensureInitialized();try{const i=t?.agentId||this.config.options?.agentId;return p.debug("Checking RCS capability for phone number:",e,i?{agentId:i}:{}),await this.provider.validatePhoneNumber(e,{agentId:i})}catch(e){if(p.error("Failed to check RCS capability:",e),e instanceof d)throw e;return{success:!1,error:`Failed to check RCS capability: ${e instanceof Error?e.message:String(e)}`}}}getProvider(){return this.provider.name}isInitialized(){return this.initialized}ensureInitialized(){if(!this.initialized)throw new d("RCS client not initialized. Call initialize() first.",e.RCSErrorCode.NOT_INITIALIZED,this.config.provider)}},e.RCSError=d,e.SuggestionBuilder=class{static reply(e,t){return{type:"reply",text:e,postbackData:t||e}}static action(e,t,i){return{type:"action",text:e,action:{type:t,data:i}}}static dial(e,t){return this.action(e,"dial",t)}static openUrl(e,t){return this.action(e,"openUrl",t)}static shareLocation(e){return this.action(e,"shareLocation")}static createCalendarEvent(e,t){return this.action(e,"createCalendarEvent",t)}},e.formatPhoneNumber=m,e.isValidE164=g,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=index.umd.js.map