UNPKG

@vulform/js

Version:

JavaScript SDK for VulForm contact form management

5 lines (3 loc) 19.8 kB
var E=Object.create;var{getPrototypeOf:f,defineProperty:N,getOwnPropertyNames:w,getOwnPropertyDescriptor:A}=Object,I=Object.prototype.hasOwnProperty;var W=(q,z,B)=>{B=q!=null?E(f(q)):{};let G=z||!q||!q.__esModule?N(B,"default",{value:q,enumerable:!0}):B;for(let S of w(q))if(!I.call(G,S))N(G,S,{get:()=>q[S],enumerable:!0});return G},D=new WeakMap,g=(q)=>{var z=D.get(q),B;if(z)return z;if(z=N({},"__esModule",{value:!0}),q&&typeof q==="object"||typeof q==="function")w(q).map((G)=>!I.call(z,G)&&N(z,G,{get:()=>q[G],enumerable:!(B=A(q,G))||B.enumerable}));return D.set(q,z),z},u=(q,z)=>()=>(z||q((z={exports:{}}).exports,z),z.exports);var d=(q,z)=>{for(var B in z)N(q,B,{get:z[B],enumerable:!0,configurable:!0,set:(G)=>z[B]=()=>G})};var M=u((B0,v)=>{var{defineProperty:X,getOwnPropertyNames:i,getOwnPropertyDescriptor:m}=Object,n=Object.prototype.hasOwnProperty,y=new WeakMap,l=(q)=>{var z=y.get(q),B;if(z)return z;if(z=X({},"__esModule",{value:!0}),q&&typeof q==="object"||typeof q==="function")i(q).map((G)=>!n.call(z,G)&&X(z,G,{get:()=>q[G],enumerable:!(B=m(q,G))||B.enumerable}));return y.set(q,z),z},s=(q,z)=>{for(var B in z)X(q,B,{get:z[B],enumerable:!0,configurable:!0,set:(G)=>z[B]=()=>G})},J={};s(J,{resolveApiUrl:()=>c,normalizeApiUrl:()=>V,isValidVulFormUrl:()=>K,isSelfHosted:()=>r,isProduction:()=>x,isDevelopment:()=>o,getDefaultConfig:()=>R,createSelfHostedConfig:()=>b,createSaaSConfig:()=>a,createCustomConfig:()=>P,VulFormError:()=>O,VulFormApiClient:()=>j,VULFORM_CONFIG:()=>L,RealtimeValidator:()=>Q,FormValidator:()=>F,ConfigBuilder:()=>T});v.exports=l(J);var t=()=>{let q=typeof process!=="undefined"?process.env:{};return{baseUrl:q.VULFORM_BASE_URL||q.NEXT_PUBLIC_VULFORM_BASE_URL||q.REACT_APP_VULFORM_BASE_URL||q.VITE_VULFORM_BASE_URL,cdnUrl:q.VULFORM_CDN_URL||q.NEXT_PUBLIC_VULFORM_CDN_URL||q.REACT_APP_VULFORM_CDN_URL||q.VITE_VULFORM_CDN_URL,version:q.VULFORM_VERSION||"1.0.0",environment:q.NODE_ENV||"production"}},L={DEFAULT_BASE_URL:"https://api.vulform.dev",DEFAULT_CDN_URL:"https://cdn.vulform.dev",DEFAULT_TIMEOUT:1e4,DEFAULT_RETRIES:3,SELF_HOSTED_BASE_URL:"/api/v1",LOCAL_DEV_BASE_URL:"http://localhost:3000/api/v1",SDK_VERSION:"1.0.0",SDK_NAME:"vulform-sdk",AUTHOR:"Dogu Yilmaz",ORGANIZATION:"Dogu Yilmaz",HOMEPAGE:"https://vulform.dev",REPOSITORY:"https://github.com/doguyilmaz/vulform",...t()};class T{config;strategy="auto";constructor(q={},z="auto"){this.strategy=z,this.config={baseUrl:this.resolveBaseUrl(q.baseUrl),cdnUrl:this.resolveCdnUrl(q.cdnUrl),timeout:q.timeout||L.DEFAULT_TIMEOUT,retries:q.retries||L.DEFAULT_RETRIES,version:q.version||L.SDK_VERSION,environment:q.environment||L.environment||"production"}}resolveBaseUrl(q){if(q)return q;let z=L.baseUrl;if(z)return z;switch(this.strategy){case"saas":return L.DEFAULT_BASE_URL;case"self-hosted":return L.SELF_HOSTED_BASE_URL;case"custom":return L.DEFAULT_BASE_URL;case"auto":default:if(this.isLocalDevelopment())return L.LOCAL_DEV_BASE_URL;if(typeof window!=="undefined"){let B=window.location.origin;if(this.strategy==="auto")return L.SELF_HOSTED_BASE_URL}return L.DEFAULT_BASE_URL}}resolveCdnUrl(q){if(q)return q;let z=L.cdnUrl;if(z)return z;return L.DEFAULT_CDN_URL}isLocalDevelopment(){return this.config.environment==="development"||typeof window!=="undefined"&&(window.location.hostname==="localhost"||window.location.hostname==="127.0.0.1"||window.location.hostname.endsWith(".local"))}setBaseUrl(q){return this.config.baseUrl=q,this}setCdnUrl(q){return this.config.cdnUrl=q,this}setEnvironment(q){return this.config.environment=q,this}setStrategy(q){return this.strategy=q,this.config.baseUrl=this.resolveBaseUrl(),this}build(){return{...this.config}}}var R=(q={},z="auto")=>{return new T(q,z).build()},c=(q,z="auto")=>{return new T({baseUrl:q},z).build().baseUrl},a=(q={})=>{return R(q,"saas")},b=(q={})=>{return R(q,"self-hosted")},P=(q,z={})=>{return R({...z,baseUrl:q},"custom")},o=()=>{return L.environment==="development"},x=()=>{return L.environment==="production"},r=()=>{let q=R();return q.baseUrl.startsWith("/")||q.baseUrl.includes(typeof window!=="undefined"?window.location.hostname:"localhost")},K=(q)=>{try{if(q.startsWith("/"))return!0;let z=new URL(q);return z.protocol==="http:"||z.protocol==="https:"}catch{return!1}},V=(q)=>{if(!q)return L.DEFAULT_BASE_URL;if(q=q.replace(/\/$/,""),!q.startsWith("http")&&!q.startsWith("/"))q="/"+q;return q};class j{config;constructor(q){if(!q.apiKey)throw new O("MISSING_API_KEY","API key is required");if(!q.apiKey.startsWith("vf_"))throw new O("INVALID_API_KEY",'Invalid API key format. API key must start with "vf_"');let z=this.resolveBaseUrl(q.baseUrl);if(this.config={baseUrl:z,timeout:q.timeout||1e4,retries:q.retries||3,debug:q.debug||!1,apiKey:q.apiKey},this.config.debug)console.log("[VulForm API] Client initialized:",{apiKey:q.apiKey.substring(0,8)+"...",baseUrl:this.config.baseUrl,resolvedFrom:q.baseUrl?"provided":"auto-detected"})}resolveBaseUrl(q){if(q&&K(q))return V(q);let z=c(q,"auto");if(this.config?.debug)console.log("[VulForm API] URL Resolution:",{provided:q,resolved:z,strategy:"auto"});return z}async getTemplate(q){let z=await this.makeRequest(`/templates/${q}`,{method:"GET"});if(!z.data?.template)throw new O("TEMPLATE_NOT_FOUND",`Template ${q} not found`);return z.data.template}async submitForm(q){let z={...q,api_key:this.config.apiKey,metadata:{...q.metadata,userAgent:typeof navigator!=="undefined"?navigator.userAgent:void 0,timestamp:new Date().toISOString()}},B=await this.makeRequest("/submit",{method:"POST",body:JSON.stringify(z),headers:{"Content-Type":"application/json"}});if(!B.success||!B.data)throw new O(B.error?.code||"SUBMISSION_FAILED",B.error?.message||"Form submission failed");return B.data}async validateField(q,z,B){let G=this.validateFieldClientSide(q,z,B);if(G)return G;if(B.pattern||q==="email")try{let S=await this.makeRequest("/validate",{method:"POST",body:JSON.stringify({field:q,value:z,rules:B}),headers:{"Content-Type":"application/json"}});if(S.data&&!S.data.valid)return S.data.error||"Validation failed"}catch(S){if(this.config.debug)console.warn("[VulForm API] Server validation failed, using client-side validation:",S)}return null}async trackEvent(q){try{await this.makeRequest("/analytics/events",{method:"POST",body:JSON.stringify({events:[q]}),headers:{"Content-Type":"application/json"}})}catch(z){if(this.config.debug)console.warn("[VulForm API] Failed to track analytics event:",z)}}async exportTemplate(q){let z=await this.makeRequest(`/templates/${q.templateId}/export`,{method:"POST",body:JSON.stringify({options:q.options}),headers:{"Content-Type":"application/json"}});if(!z.data)throw new O("EXPORT_FAILED","Failed to export template");return z.data}validateFieldClientSide(q,z,B){if(B.required&&(!z||typeof z==="string"&&z.trim().length===0))return B.customMessage||`${q} is required`;if(!z||z.length===0)return null;if(typeof z==="string"){if(B.minLength&&z.length<B.minLength)return B.customMessage||`Must be at least ${B.minLength} characters`;if(B.maxLength&&z.length>B.maxLength)return B.customMessage||`Must be no more than ${B.maxLength} characters`;if(q==="email"&&!this.isValidEmail(z))return B.customMessage||"Please enter a valid email address";if(B.pattern&&!new RegExp(B.pattern).test(z))return B.customMessage||"Invalid format"}if(typeof z==="number"||typeof z==="string"&&!isNaN(Number(z))){let G=Number(z);if(B.min!==void 0&&G<B.min)return B.customMessage||`Must be at least ${B.min}`;if(B.max!==void 0&&G>B.max)return B.customMessage||`Must be no more than ${B.max}`}return null}isValidEmail(q){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(q)}async makeRequest(q,z){let B=`${this.config.baseUrl}${q}`,G=null;for(let S=1;S<=this.config.retries;S++)try{let U=new AbortController,k=setTimeout(()=>U.abort(),this.config.timeout),h=await fetch(B,{...z,headers:{Authorization:`Bearer ${this.config.apiKey}`,"X-VulForm-SDK":"core/1.0.0",...z.headers},signal:U.signal});clearTimeout(k);let H,$=h.headers.get("content-type");if($&&$.includes("application/json"))H=await h.json();else H={message:await h.text()};if(!h.ok){if(h.status===429){let C=parseInt(h.headers.get("Retry-After")||"60");throw new O("RATE_LIMIT_EXCEEDED",`Rate limit exceeded. Try again in ${C} seconds`,h.status,{retryAfter:C})}throw new O(H.code||"API_ERROR",H.message||`HTTP ${h.status}: ${h.statusText}`,h.status,H)}return{success:!0,data:H}}catch(U){if(G=U instanceof Error?U:new Error(String(U)),U instanceof O){if(["INVALID_API_KEY","TEMPLATE_NOT_FOUND","VALIDATION_ERROR"].includes(U.code))throw U}if(S<this.config.retries){let k=Math.min(1000*Math.pow(2,S-1),1e4);await this.sleep(k);continue}if(U instanceof O)throw U;throw new O("NETWORK_ERROR",G.message||"Network request failed",0,G)}throw new O("UNKNOWN_ERROR","An unknown error occurred")}sleep(q){return new Promise((z)=>setTimeout(z,q))}}class O extends Error{code;statusCode;details;constructor(q,z,B,G){super(z);this.name="VulFormError",this.code=q,this.statusCode=B,this.details=G}}class F{static validateForm(q,z){let B={};for(let G of z.fields){if(["divider","heading","paragraph","hidden"].includes(G.type))continue;let S=q[G.name],U=this.validateField(G,S);if(U)B[G.name]=U}return B}static validateField(q,z){if(!q.validation)return null;return this.validateValue(q.name,z,q.validation,q.type)}static validateValue(q,z,B,G){if(B.required&&this.isEmpty(z))return B.customMessage||`${q} is required`;if(this.isEmpty(z))return null;if(typeof z==="string"){if(B.minLength&&z.length<B.minLength)return B.customMessage||`Must be at least ${B.minLength} characters`;if(B.maxLength&&z.length>B.maxLength)return B.customMessage||`Must be no more than ${B.maxLength} characters`;if(B.pattern&&!new RegExp(B.pattern).test(z))return B.customMessage||"Invalid format";if(G==="email"&&!this.isValidEmail(z))return B.customMessage||"Please enter a valid email address";if(G==="url"&&!this.isValidUrl(z))return B.customMessage||"Please enter a valid URL";if(G==="tel"&&!this.isValidPhone(z))return B.customMessage||"Please enter a valid phone number"}if(G==="number"&&z!==""){let S=parseFloat(z);if(isNaN(S))return B.customMessage||"Please enter a valid number";if(B.min!==void 0&&S<B.min)return B.customMessage||`Must be at least ${B.min}`;if(B.max!==void 0&&S>B.max)return B.customMessage||`Must be no more than ${B.max}`}if(["date","datetime-local","time"].includes(G||"")){if(!this.isValidDate(z))return B.customMessage||"Please enter a valid date"}if(G==="file"&&z instanceof FileList){let S=this.validateFiles(z,B);if(S)return B.customMessage||S}return null}static isEmpty(q){if(q===null||q===void 0)return!0;if(typeof q==="string")return q.trim().length===0;if(Array.isArray(q))return q.length===0;if(q instanceof FileList)return q.length===0;return!1}static isValidEmail(q){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(q)}static isValidUrl(q){try{return new URL(q),!0}catch{return!1}}static isValidPhone(q){let z=/^[\+]?[1-9][\d]{0,15}$/,B=q.replace(/[\s\-\(\)\.]/g,"");return z.test(B)}static isValidDate(q){if(!q)return!1;let z=new Date(q);return!isNaN(z.getTime())}static validateFiles(q,z){if(q.length===0)return null;if(z.max&&q.length>z.max)return`Too many files. Maximum ${z.max} files allowed`;for(let B=0;B<q.length;B++){let G=q[B];if(z.min&&G.size<z.min)return`File "${G.name}" is too small. Minimum size: ${this.formatFileSize(z.min)}`;if(z.max&&G.size>z.max)return`File "${G.name}" is too large. Maximum size: ${this.formatFileSize(z.max)}`}return null}static formatFileSize(q){let z=["Bytes","KB","MB","GB"];if(q===0)return"0 Bytes";let B=Math.floor(Math.log(q)/Math.log(1024));return Math.round(q/Math.pow(1024,B)*100)/100+" "+z[B]}}class Q{validators=new Map;debounceTimers=new Map;registerField(q,z){this.validators.set(q,z)}async validateField(q,z,B,G=300){let S=this.debounceTimers.get(q);if(S)clearTimeout(S);let U=setTimeout(async()=>{let k=this.validators.get(q);if(k)try{let h=await k(z);B(h)}catch(h){console.warn(`Validation error for field ${q}:`,h),B(null)}this.debounceTimers.delete(q)},G);this.debounceTimers.set(q,U)}destroy(){this.debounceTimers.forEach((q)=>clearTimeout(q)),this.debounceTimers.clear(),this.validators.clear()}}});var e={};d(e,{default:()=>q0,VulFormFormHandler:()=>Y,VulFormError:()=>p.VulFormError,VulForm:()=>Z});module.exports=g(e);var _=W(M()),p=W(M());class Z{config;submitCount=0;lastSubmitTime=0;apiClient;constructor(q){if(!q.apiKey)throw new _.VulFormError("API key is required","MISSING_API_KEY");if(!q.apiKey.startsWith("vf_"))throw new _.VulFormError("Invalid API key format","INVALID_API_KEY");let z=_.getDefaultConfig();if(this.config={baseUrl:z.baseUrl,timeout:z.timeout,retries:z.retries,debug:!1,onSuccess:()=>{},onError:()=>{},onRateLimit:()=>{},...q},this.apiClient=new _.VulFormApiClient({apiKey:this.config.apiKey,baseUrl:this.config.baseUrl,timeout:this.config.timeout}),this.config.debug)console.log("[VulForm] SDK initialized with config:",{apiKey:q.apiKey.substring(0,8)+"...",baseUrl:this.config.baseUrl})}async submit(q){this.validateFormData(q);let z=Date.now();if(this.submitCount>0&&z-this.lastSubmitTime<1000)throw new _.VulFormError("Rate limit exceeded","RATE_LIMIT_EXCEEDED");this.trackEvent({type:"form_submit",timestamp:z,metadata:{fieldCount:Object.keys(q).length}});let B=null;for(let G=1;G<=this.config.retries;G++)try{let S=await this.makeRequest("/api/v1/submit",{method:"POST",body:JSON.stringify(q),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`,"X-VulForm-SDK":`js/${_.VULFORM_CONFIG.SDK_VERSION}`}});if(!S.ok){let k=await S.json().catch(()=>({}));if(S.status===429){let h=parseInt(S.headers.get("Retry-After")||"60");throw this.config.onRateLimit(h),new _.VulFormError("Rate limit exceeded","RATE_LIMIT_EXCEEDED",429,{retryAfter:h})}throw new _.VulFormError(k.message||"Submission failed",k.code||"SUBMISSION_FAILED",S.status,k)}let U=await S.json();if(this.submitCount++,this.lastSubmitTime=z,this.config.onSuccess(U),this.config.debug)console.log("[VulForm] Submission successful:",U);return U}catch(S){if(B=S instanceof _.VulFormError?S:new _.VulFormError(S instanceof Error?S.message:"Unknown error","NETWORK_ERROR"),G===this.config.retries)throw this.config.onError(B),B;let U=Math.min(1000*Math.pow(2,G-1),1e4);await this.sleep(U)}throw B}validate(q){let z=[],B=[];if(!q.email)z.push({field:"email",message:"Email is required",code:"REQUIRED_FIELD"});else if(!this.isValidEmail(q.email.toString()))z.push({field:"email",message:"Please enter a valid email address",code:"INVALID_EMAIL"});if(!q.message)z.push({field:"message",message:"Message is required",code:"REQUIRED_FIELD"});if(!q.name)B.push({field:"name",message:"Name is recommended for better user experience",suggestion:"Consider making the name field required"});if(q.message&&q.message.toString().length<10)B.push({field:"message",message:"Message seems quite short",suggestion:"Consider providing more details"});return{valid:z.length===0,errors:z,warnings:B}}async validateField(q,z){switch(q){case"email":if(!z)return{field:"email",message:"Email is required",code:"REQUIRED_FIELD"};if(!this.isValidEmail(z))return{field:"email",message:"Invalid email format",code:"INVALID_EMAIL"};break;case"message":if(!z)return{field:"message",message:"Message is required",code:"REQUIRED_FIELD"};if(z.length<5)return{field:"message",message:"Message too short",code:"MIN_LENGTH"};break}return null}trackEvent(q){if(this.config.debug)console.log("[VulForm] Analytics event:",q);let z=this.getStoredEvents();if(z.push(q),localStorage.setItem("vulform_events",JSON.stringify(z)),z.length>=10)this.flushEvents()}setupForm(q){return new Y(this,q)}validateFormData(q){if(!q||typeof q!=="object")throw new _.VulFormError("Form data must be an object","INVALID_DATA");let z=String(q.message||"");if(z.length>1e4)throw new _.VulFormError("Message too long","MESSAGE_TOO_LONG");if((z.match(/https?:\/\//g)||[]).length>3)throw new _.VulFormError("Too many URLs in message","SUSPICIOUS_CONTENT")}isValidEmail(q){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(q)}async makeRequest(q,z){let B=`${this.config.baseUrl}${q}`,G=new AbortController,S=setTimeout(()=>G.abort(),this.config.timeout);try{let U=await fetch(B,{...z,signal:G.signal});return clearTimeout(S),U}catch(U){if(clearTimeout(S),U instanceof Error&&U.name==="AbortError")throw new _.VulFormError("Request timeout","TIMEOUT");throw U}}sleep(q){return new Promise((z)=>setTimeout(z,q))}getStoredEvents(){try{let q=localStorage.getItem("vulform_events");return q?JSON.parse(q):[]}catch{return[]}}async flushEvents(){let q=this.getStoredEvents();if(q.length===0)return;try{await this.makeRequest("/api/v1/analytics/events",{method:"POST",body:JSON.stringify({events:q}),headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`}}),localStorage.removeItem("vulform_events")}catch(z){if(this.config.debug)console.warn("[VulForm] Failed to send analytics events:",z)}}}class Y{vulform;form;submitButton;originalSubmitText;constructor(q,z){this.vulform=q,this.form=z,this.setupFormHandling()}setupFormHandling(){if(this.submitButton=this.form.querySelector('button[type="submit"]')||this.form.querySelector('input[type="submit"]'),this.submitButton)this.originalSubmitText=this.submitButton.textContent||this.submitButton.value||"Submit";this.vulform.trackEvent({type:"form_view",timestamp:Date.now()}),this.form.addEventListener("focusin",()=>{this.vulform.trackEvent({type:"form_start",timestamp:Date.now()})},{once:!0}),this.form.addEventListener("submit",this.handleSubmit.bind(this)),this.setupRealTimeValidation()}async handleSubmit(q){q.preventDefault();let z=new FormData(this.form),B={};z.forEach((S,U)=>{B[U]=S});let G=this.vulform.validate(B);if(!G.valid){this.showValidationErrors(G.errors);return}this.setLoading(!0);try{let S=await this.vulform.submit(B);this.showSuccess(S),this.form.reset()}catch(S){this.showError(S)}finally{this.setLoading(!1)}}setupRealTimeValidation(){let q=this.form.querySelector('input[type="email"], input[name="email"]'),z=this.form.querySelector('textarea[name="message"]');if(q)q.addEventListener("blur",async()=>{let B=await this.vulform.validateField("email",q.value);this.showFieldError(q,B)});if(z)z.addEventListener("blur",async()=>{let B=await this.vulform.validateField("message",z.value);this.showFieldError(z,B)})}setLoading(q){if(this.submitButton)this.submitButton.disabled=q,this.submitButton.textContent=q?"Sending...":this.originalSubmitText}showSuccess(q){this.showMessage("Thank you! Your message has been sent successfully.","success")}showError(q){let z="Sorry, there was an error sending your message. Please try again.";if(q.code==="RATE_LIMIT_EXCEEDED")z="Please wait a moment before submitting again.";else if(q.code==="VALIDATION_ERROR")z="Please check your form and try again.";this.showMessage(z,"error")}showValidationErrors(q){q.forEach((z)=>{let B=this.form.querySelector(`[name="${z.field}"]`);this.showFieldError(B,z)})}showFieldError(q,z){let B=q.parentElement?.querySelector(".vulform-error");if(B)B.remove();if(z){let G=document.createElement("div");G.className="vulform-error",G.style.color="red",G.style.fontSize="14px",G.style.marginTop="4px",G.textContent=z.message,q.parentElement?.appendChild(G),q.style.borderColor="red"}else q.style.borderColor=""}showMessage(q,z){let B=this.form.querySelector(".vulform-message");if(B)B.remove();let G=document.createElement("div");G.className="vulform-message",G.style.padding="12px",G.style.borderRadius="4px",G.style.marginTop="16px",G.style.backgroundColor=z==="success"?"#d4edda":"#f8d7da",G.style.color=z==="success"?"#155724":"#721c24",G.style.border=`1px solid ${z==="success"?"#c3e6cb":"#f5c6cb"}`,G.textContent=q,this.form.appendChild(G),setTimeout(()=>{G.remove()},5000)}}var q0=Z; //# debugId=E95D436A624E99EC64756E2164756E21 //# sourceMappingURL=index.cjs.js.map