UNPKG

betaflow-sdk

Version:

BetaFlow SDK for web applications - A powerful tool for managing beta testing campaigns and user feedback

2 lines (1 loc) 29.6 kB
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).BetaFlow={})}(this,function(e){"use strict";class t{constructor(e){this.config={campaignId:e.campaignId,apiKey:e.apiKey,apiEndpoint:e.apiEndpoint||("undefined"!=typeof window?window.location.origin+"/api":"https://betaflow.fulitimes.com/api"),language:e.language||"zh-CN",theme:e.theme||"light",position:e.position||"bottom-right",debug:e.debug||!1,...e},this.validateConfig(),"undefined"!=typeof window&&this.init()}init(){this.createStyles(),!1!==this.config.autoShow&&this.createFloatingButton(),this.bindEvents(),this.log("BetaFlow SDK 初始化完成")}log(e,t=null){this.config.debug&&console.log("[BetaFlow SDK]",e,t)}logError(e,t=null){this.config.debug&&console.error("[BetaFlow SDK Error]",e,t)}logWarning(e,t=null){this.config.debug&&console.warn("[BetaFlow SDK Warning]",e,t)}validateConfig(){const e=[];if(["campaignId","apiKey"].forEach(t=>{this.config[t]||e.push(t)}),e.length>0){const t=`缺少必要的配置参数: ${e.join(", ")}`;throw this.logError("配置验证失败",t),new Error(`[BetaFlow SDK] ${t}`)}this.config.apiEndpoint&&!this.isValidUrl(this.config.apiEndpoint)&&this.logWarning("API端点格式可能不正确",this.config.apiEndpoint),this.log("配置验证通过",{apiEndpoint:this.config.apiEndpoint,campaignId:this.config.campaignId,hasApiKey:!!this.config.apiKey})}isValidUrl(e){try{return new URL(e),!0}catch(e){return!1}}async diagnose(){const e={timestamp:(new Date).toISOString(),config:{apiEndpoint:this.config.apiEndpoint,campaignId:this.config.campaignId,hasApiKey:!!this.config.apiKey,debug:this.config.debug},tests:[],summary:{passed:0,failed:0,warnings:0}};try{this.validateConfig(),e.tests.push({name:"配置验证",status:"passed",message:"所有必要配置参数都已提供"}),e.summary.passed++}catch(t){e.tests.push({name:"配置验证",status:"failed",message:t.message,suggestion:"请检查 campaignId 等必要参数是否已正确配置"}),e.summary.failed++}this.isValidUrl(this.config.apiEndpoint)?(e.tests.push({name:"API端点格式",status:"passed",message:"API端点格式正确"}),e.summary.passed++):(e.tests.push({name:"API端点格式",status:"failed",message:"API端点格式不正确",suggestion:"请确保API端点是完整的URL格式,如: http://localhost:3000/api"}),e.summary.failed++),"undefined"==typeof navigator||navigator.onLine?(e.tests.push({name:"网络连接",status:"passed",message:"网络连接正常"}),e.summary.passed++):(e.tests.push({name:"网络连接",status:"failed",message:"网络连接不可用",suggestion:"请检查网络连接"}),e.summary.failed++);try{const t=await this.getCampaignInfo();t.success?(e.tests.push({name:"API连接测试",status:"passed",message:"成功连接到API并获取项目信息",data:{campaignName:t.data?.name,campaignStatus:t.data?.status}}),e.summary.passed++):(e.tests.push({name:"API连接测试",status:"failed",message:t.message,errorCode:t.errorCode,suggestion:this.getSuggestionForError(t.errorCode)}),e.summary.failed++)}catch(t){e.tests.push({name:"API连接测试",status:"failed",message:t.message,suggestion:"请检查API端点和网络连接"}),e.summary.failed++}return 0===e.summary.failed?(e.overall="healthy",e.message="SDK配置正常,可以正常使用"):e.summary.failed>e.summary.passed?(e.overall="critical",e.message="存在严重配置问题,需要修复后才能正常使用"):(e.overall="warning",e.message="存在一些问题,但基本功能可能仍然可用"),console.group("[BetaFlow SDK] 诊断结果"),console.log("整体状态:",e.overall),console.log("总结:",e.message),console.log("通过测试:",e.summary.passed),console.log("失败测试:",e.summary.failed),console.log("详细结果:",e.tests),console.groupEnd(),e}getSuggestionForError(e){return{CAMPAIGN_NOT_FOUND:"请检查 campaignId 是否正确,确保项目存在",UNAUTHORIZED:"请检查 apiKey 是否正确配置",FORBIDDEN:"请检查API权限配置",FETCH_FAILED:"请检查API端点是否正确,服务器是否运行,是否存在CORS问题",TIMEOUT:"请检查网络连接速度和服务器响应时间",SERVER_ERROR:"服务器内部错误,请联系管理员或稍后重试",NETWORK_OFFLINE:"请检查网络连接"}[e]||"请检查配置和网络连接"}createStyles(){if("undefined"==typeof document)return;const e=document.createElement("style");e.textContent="\n .betaflow-container {\n position: fixed;\n z-index: 10000;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;\n }\n .betaflow-button {\n background: linear-gradient(135deg, #007bff 0%, #0056b3 100%);\n color: white;\n border: none;\n border-radius: 50px;\n padding: 12px 20px;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(0,123,255,0.3);\n transition: all 0.3s ease;\n font-size: 14px;\n font-weight: 500;\n white-space: nowrap;\n }\n .betaflow-button:hover {\n transform: translateY(-2px);\n box-shadow: 0 6px 20px rgba(0,123,255,0.4);\n background: linear-gradient(135deg, #0056b3 0%, #004085 100%);\n }\n .betaflow-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background: rgba(0,0,0,0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 10001;\n backdrop-filter: blur(4px);\n }\n .betaflow-form {\n background: white;\n padding: 30px;\n border-radius: 12px;\n max-width: 500px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 20px 40px rgba(0,0,0,0.1);\n animation: betaflow-fadeIn 0.3s ease;\n }\n @keyframes betaflow-fadeIn {\n from { opacity: 0; transform: scale(0.9); }\n to { opacity: 1; transform: scale(1); }\n }\n .betaflow-form h2 {\n margin: 0 0 20px 0;\n color: #333;\n font-size: 24px;\n font-weight: 600;\n }\n .betaflow-form-header {\n text-align: center;\n margin-bottom: 20px;\n padding: 20px;\n border-radius: 8px;\n }\n .betaflow-form-icon {\n margin-bottom: 15px;\n }\n .betaflow-form-icon img {\n max-width: 64px;\n max-height: 64px;\n border-radius: 8px;\n }\n .betaflow-form-title {\n margin: 0 0 10px 0;\n color: inherit;\n font-size: 24px;\n font-weight: 600;\n }\n .betaflow-form-subtitle {\n margin: 0 0 10px 0;\n color: inherit;\n font-size: 18px;\n font-weight: 400;\n opacity: 0.8;\n }\n .betaflow-form-description {\n margin: 0;\n color: inherit;\n font-size: 14px;\n line-height: 1.5;\n opacity: 0.7;\n }\n .betaflow-form label {\n display: block;\n margin-bottom: 5px;\n color: #555;\n font-weight: 500;\n }\n .betaflow-form .form-field {\n margin-bottom: 15px;\n }\n .betaflow-form .field-description {\n display: block;\n margin-top: 5px;\n font-size: 12px;\n color: #666;\n line-height: 1.4;\n }\n .betaflow-form input,\n .betaflow-form textarea,\n .betaflow-form select {\n width: 100%;\n padding: 12px;\n margin-bottom: 15px;\n border: 1px solid #ddd;\n border-radius: 6px;\n font-size: 14px;\n transition: border-color 0.3s ease;\n box-sizing: border-box;\n }\n .betaflow-form input:focus,\n .betaflow-form textarea:focus,\n .betaflow-form select:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: 0 0 0 3px rgba(0,123,255,0.1);\n }\n .betaflow-form .radio-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 15px;\n }\n .betaflow-form .radio-option,\n .betaflow-form .checkbox-option {\n display: flex;\n align-items: center;\n gap: 8px;\n cursor: pointer;\n font-size: 14px;\n }\n .betaflow-form .radio-option input[type=\"radio\"],\n .betaflow-form .checkbox-option input[type=\"checkbox\"] {\n width: auto;\n margin: 0;\n margin-bottom: 0;\n }\n .betaflow-form .button-group {\n display: flex;\n gap: 10px;\n margin-top: 20px;\n }\n .betaflow-form button {\n flex: 1;\n padding: 12px;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n font-size: 14px;\n font-weight: 500;\n transition: all 0.3s ease;\n }\n .betaflow-form .submit-btn {\n background: #007bff;\n color: white;\n }\n .betaflow-form .submit-btn:hover {\n background: #0056b3;\n }\n .betaflow-form .submit-btn:disabled {\n background: #ccc;\n cursor: not-allowed;\n }\n .betaflow-form .cancel-btn {\n background: #6c757d;\n color: white;\n }\n .betaflow-form .cancel-btn:hover {\n background: #545b62;\n }\n .betaflow-loading {\n display: inline-block;\n width: 16px;\n height: 16px;\n border: 2px solid #ffffff;\n border-radius: 50%;\n border-top-color: transparent;\n animation: betaflow-spin 1s ease-in-out infinite;\n }\n @keyframes betaflow-spin {\n to { transform: rotate(360deg); }\n }\n .betaflow-success {\n text-align: center;\n padding: 40px 20px;\n }\n .betaflow-success .icon {\n font-size: 48px;\n color: #28a745;\n margin-bottom: 20px;\n }\n .betaflow-error {\n color: #dc3545;\n font-size: 14px;\n margin-top: 10px;\n }\n ",document.head.appendChild(e)}createFloatingButton(){if("undefined"==typeof document)return;const e=document.createElement("div");e.className="betaflow-container",e.style.cssText=this.getPositionStyles();const t=document.createElement("button");t.className="betaflow-button",t.textContent=this.config.buttonText||"申请 Beta 测试",t.onclick=()=>{this.showApplicationForm().catch(e=>{this.log("显示申请表单失败",e),this.showFallbackForm()})},e.appendChild(t),document.body.appendChild(e),this.floatingButton=e}getPositionStyles(){const e={"bottom-right":"bottom: 20px; right: 20px;","bottom-left":"bottom: 20px; left: 20px;","top-right":"top: 20px; right: 20px;","top-left":"top: 20px; left: 20px;","bottom-center":"bottom: 20px; left: 50%; transform: translateX(-50%);"};return e[this.config.position]||e["bottom-right"]}async showApplicationForm(){if("undefined"==typeof document)return;if(document.querySelector(".betaflow-modal"))return;this.log("显示申请表单");let e=this.config.formTitle,t=null,n=null,o=null,a=null;if(this.config.campaignId)try{a=await this.getCampaignInfo(),this.log("getCampaignInfo返回",a),a.success&&a.data?(o=a.data.name,o&&(e=o),a.data.config&&a.data.config.formFields?t=a.data.config.formFields:a.data.formFields&&(t=a.data.formFields),a.data.config&&a.data.config.formHeader?n=a.data.config.formHeader:a.data.formHeader&&(n=a.data.formHeader),this.log("获取到的表单配置",{formFields:t,formHeader:n,campaignName:o})):(this.logError("获取项目信息失败",{success:a.success,message:a.message,errorCode:a.errorCode,httpStatus:a.httpStatus}),"CAMPAIGN_NOT_FOUND"!==a.errorCode&&"UNAUTHORIZED"!==a.errorCode&&"FETCH_FAILED"!==a.errorCode||this.logWarning("项目配置可能有问题,将使用默认表单",{errorCode:a.errorCode,message:a.message}))}catch(e){this.logError("获取项目信息异常",e)}e||(e="申请 Beta 测试");const i=this.generateFormHeader(n,e);let s="";t&&Array.isArray(t)&&t.length>0?s=this.generateFormFields(t):(this.logWarning("未获取到后端表单字段,使用默认字段"),s=this.getDefaultFormFields());const r=document.createElement("div");r.className="betaflow-modal",r.innerHTML=`\n <div class="betaflow-form">\n ${i}\n <form id="betaflow-application-form">\n ${s}\n <div class="button-group">\n <button type="submit" class="submit-btn">\n <span class="btn-text">提交申请</span>\n <span class="betaflow-loading" style="display: none;"></span>\n </button>\n <button type="button" class="cancel-btn" onclick="this.closest('.betaflow-modal').remove()">取消</button>\n </div>\n <div class="betaflow-error" style="display: none;"></div>\n </form>\n </div>\n `,document.body.appendChild(r);r.querySelector("#betaflow-application-form").onsubmit=e=>this.handleFormSubmit(e,r),this.config.onFormShow&&this.config.onFormShow()}hideApplicationForm(){if("undefined"==typeof document)return;const e=document.querySelector(".betaflow-modal");e&&(e.remove(),this.log("隐藏申请表单"))}generateFormHeader(e,t){if(!e)return`<h2>${t}</h2>`;const{title:n,subtitle:o,description:a,iconUrl:i,backgroundColor:s,textColor:r,className:l}=e,c=[];s&&c.push(`background-color: ${s}`),r&&c.push(`color: ${r}`);let d="";i&&(d+=`<div class="betaflow-form-icon"><img src="${i}" alt="Icon" /></div>`);const p=n||t;return p&&(d+=`<h2 class="betaflow-form-title">${p}</h2>`),o&&(d+=`<h3 class="betaflow-form-subtitle">${o}</h3>`),a&&(d+=`<p class="betaflow-form-description">${a}</p>`),`<div${l?` class="betaflow-form-header ${l}"`:' class="betaflow-form-header"'}${c.length>0?` style="${c.join("; ")}"`:""}>${d}</div>`}generateFormFields(e){return e&&Array.isArray(e)&&0!==e.length?e.map(e=>{const t=e.required?" *":"",n=e.required?" required":"",o=e.placeholder||`请输入${e.label}`,a=e.defaultValue?` value="${e.defaultValue}"`:"",i=e.name||e.label;let s="";e.validation&&(e.validation.minLength&&(s+=` minlength="${e.validation.minLength}"`),e.validation.maxLength&&(s+=` maxlength="${e.validation.maxLength}"`),e.validation.pattern&&(s+=` pattern="${e.validation.pattern}"`));const r=e.description?`<small class="field-description">${e.description}</small>`:"";switch(e.type){case"text":case"email":case"tel":case"url":return`\n <div class="form-field">\n <label>${e.label}${t}</label>\n <input type="${e.type}" name="${i}"${n}${s}${a} placeholder="${o}">\n ${r}\n </div>`;case"number":return`\n <div class="form-field">\n <label>${e.label}${t}</label>\n <input type="number" name="${i}"${n}${s}${a} placeholder="${o}">\n ${r}\n </div>`;case"date":return`\n <div class="form-field">\n <label>${e.label}${t}</label>\n <input type="date" name="${i}"${n}${a}>\n ${r}\n </div>`;case"textarea":const l=e.rows||3;return`\n <div class="form-field">\n <label>${e.label}${t}</label>\n <textarea name="${i}" rows="${l}"${n}${s} placeholder="${o}">${e.defaultValue||""}</textarea>\n ${r}\n </div>`;case"select":const c=(e.options||[]).map(t=>{const n=t.value||t,o=t.label||t;return`<option value="${n}"${e.defaultValue===n?" selected":""}>${o}</option>`}).join("");return`\n <div class="form-field">\n <label>${e.label}${t}</label>\n <select name="${i}"${n}>\n <option value="">请选择${e.label}</option>\n ${c}\n </select>\n ${r}\n </div>`;case"radio":const d=(e.options||[]).map((t,o)=>{const a=t.value||t,s=t.label||t,r=e.defaultValue===a?" checked":"";return`<label class="radio-option">\n <input type="radio" name="${i}" value="${a}"${n}${r}>\n ${s}\n </label>`}).join("");return`\n <div class="form-field">\n <label>${e.label}${t}</label>\n <div class="radio-group">\n ${d}\n </div>\n ${r}\n </div>`;case"checkbox":const p=e.defaultValue?" checked":"";return`\n <div class="form-field">\n <label class="checkbox-option">\n <input type="checkbox" name="${i}" value="1"${n}${p}>\n ${e.label}${t}\n </label>\n ${r}\n </div>`;default:return`\n <div class="form-field">\n <label>${e.label}${t}</label>\n <input type="text" name="${i}"${n}${s}${a} placeholder="${o}">\n ${r}\n </div>`}}).join(""):this.getDefaultFormFields()}getDefaultFormFields(){return'\n <div>\n <label>姓名 *</label>\n <input type="text" name="name" required placeholder="请输入您的姓名">\n </div>\n <div>\n <label>邮箱 *</label>\n <input type="email" name="email" required placeholder="请输入您的邮箱地址">\n </div>\n <div>\n <label>公司/组织</label>\n <input type="text" name="company" placeholder="请输入您的公司或组织名称">\n </div>\n <div>\n <label>职位</label>\n <input type="text" name="position" placeholder="请输入您的职位">\n </div>\n <div>\n <label>申请理由</label>\n <textarea name="reason" rows="3" placeholder="请简要说明您申请 Beta 测试的理由"></textarea>\n </div>'}showFallbackForm(){if("undefined"==typeof document)return;if(document.querySelector(".betaflow-modal"))return;this.log("显示备用申请表单");const e=this.getDefaultFormFields(),t=this.config.formTitle||"申请 Beta 测试",n=document.createElement("div");n.className="betaflow-modal",n.innerHTML=`\n <div class="betaflow-form">\n <h2>${t}</h2>\n <form id="betaflow-application-form">\n ${e}\n <div class="button-group">\n <button type="submit" class="submit-btn">\n <span class="btn-text">提交申请</span>\n <span class="betaflow-loading" style="display: none;"></span>\n </button>\n <button type="button" class="cancel-btn" onclick="this.closest('.betaflow-modal').remove()">取消</button>\n </div>\n <div class="betaflow-error" style="display: none;"></div>\n </form>\n </div>\n `,document.body.appendChild(n);n.querySelector("#betaflow-application-form").onsubmit=e=>this.handleFormSubmit(e,n),this.config.onFormShow&&this.config.onFormShow()}showFloatingButton(){this.floatingButton?this.floatingButton.style.display="block":this.createFloatingButton()}hideFloatingButton(){this.floatingButton&&(this.floatingButton.style.display="none")}async handleFormSubmit(e,t){e.preventDefault();const n=e.target,o=n.querySelector(".submit-btn"),a=o.querySelector(".btn-text"),i=o.querySelector(".betaflow-loading"),s=n.querySelector(".betaflow-error");o.disabled=!0,a.style.display="none",i.style.display="inline-block",s.style.display="none";const r=new FormData(n),l=Object.fromEntries(r.entries());this.log("提交表单数据",l),this.config.onFormSubmit&&this.config.onFormSubmit(l);try{const e=await this.submitApplication(l);if(!e.success)throw new Error(e.message||"提交失败");this.log("申请提交成功",e),this.showSuccessMessage(t),this.config.onFormSuccess&&this.config.onFormSuccess(e)}catch(e){this.logError("申请提交失败",e);let t=e.message||"提交失败,请稍后重试",n=!1;if(e.code)switch(e.code){case"FETCH_FAILED":t="网络连接失败,请检查网络设置后重试",n=!0;break;case"TIMEOUT":t="请求超时,请检查网络连接后重试";break;case"UNAUTHORIZED":t="API认证失败,请联系管理员检查配置",n=!0;break;case"FORBIDDEN":t="访问被拒绝,请联系管理员";break;case"INVALID_REQUEST":t="请求参数错误,请检查填写的信息";break;case"CONFLICT":t="申请已存在,请勿重复提交";break;case"SERVER_ERROR":t="服务器错误,请稍后重试";break;case"CAMPAIGN_NOT_FOUND":t="项目不存在,请联系管理员检查配置",n=!0}n&&this.config.debug?s.innerHTML=`\n <div style="margin-bottom: 10px;">${t}</div>\n <details style="font-size: 12px; color: #666;">\n <summary style="cursor: pointer;">查看详细信息</summary>\n <div style="margin-top: 5px; padding: 5px; background: #f5f5f5; border-radius: 3px;">\n <div><strong>错误代码:</strong> ${e.code||"UNKNOWN"}</div>\n <div><strong>API端点:</strong> ${this.config.apiEndpoint}</div>\n <div><strong>项目ID:</strong> ${this.config.campaignId}</div>\n ${e.httpStatus?`<div><strong>HTTP状态:</strong> ${e.httpStatus}</div>`:""}\n ${e.originalError?`<div><strong>原始错误:</strong> ${e.originalError}</div>`:""}\n </div>\n </details>\n `:s.textContent=t,s.style.display="block",o.disabled=!1,a.style.display="inline",i.style.display="none",this.config.onFormError&&this.config.onFormError(e)}}async submitApplication(e){const t=`${this.config.apiEndpoint}/applications`,n=e.email||e["applicant-email"]||e.applicantEmail||"",o=e.name||e["applicant-name"]||e.applicantName||"Beta测试用户",a={...e};delete a.name,delete a["applicant-name"],delete a.applicantName,delete a.email,delete a["applicant-email"],delete a.applicantEmail;const i={campaignId:this.config.campaignId,applicantName:o,applicantEmail:n,formData:a,source:"sdk",userAgent:"undefined"!=typeof navigator?navigator.userAgent:"Node.js",timestamp:(new Date).toISOString()};this.log("提交申请",{url:t,dataKeys:Object.keys(i),formDataKeys:Object.keys(a),applicantName:o,applicantEmail:n});try{if("undefined"!=typeof navigator&&!navigator.onLine){const e="网络连接不可用,请检查网络设置";throw this.logError("网络检查失败",e),new Error(e)}const e={"Content-Type":"application/json",...this.config.apiKey&&{Authorization:`Bearer ${this.config.apiKey}`}};this.log("发送申请请求",{url:t,headers:Object.keys(e)});const n=await fetch(t,{method:"POST",headers:e,body:JSON.stringify(i),timeout:15e3});this.log("收到申请响应",{status:n.status,statusText:n.statusText,ok:n.ok});const o=await n.json();if(n.ok&&o.success)return this.log("申请提交成功",o),o;{let e=o.error||o.message||"提交失败",t="SUBMIT_FAILED";400===n.status?(e=`请求参数错误: ${e}`,t="INVALID_REQUEST"):401===n.status?(e="API认证失败,请检查 apiKey 配置",t="UNAUTHORIZED"):403===n.status?(e="访问被拒绝,请检查权限配置",t="FORBIDDEN"):409===n.status?(e="申请已存在或冲突",t="CONFLICT"):n.status>=500&&(e="服务器内部错误,请稍后重试",t="SERVER_ERROR"),this.logError("申请提交失败",{status:n.status,message:e,response:o});const a=new Error(e);throw a.code=t,a.httpStatus=n.status,a.response=o,a}}catch(e){if(e.code)throw e;let t=e.message,n="NETWORK_ERROR";"TypeError"===e.name&&e.message.includes("Failed to fetch")?(t=`网络请求失败,请检查:\n1. API端点是否正确: ${this.config.apiEndpoint}\n2. 服务器是否运行\n3. 是否存在CORS问题\n4. 网络连接是否正常`,n="FETCH_FAILED"):"AbortError"===e.name?(t="请求超时,请检查网络连接或服务器响应速度",n="TIMEOUT"):e.message.includes("JSON")&&(t="服务器响应格式错误,请检查API端点配置",n="INVALID_RESPONSE"),this.logError("提交申请失败",{error:e.message,name:e.name,stack:e.stack,config:{apiEndpoint:this.config.apiEndpoint,campaignId:this.config.campaignId,hasApiKey:!!this.config.apiKey}});const o=new Error(t);throw o.code=n,o.originalError=e.message,o}}async getCampaignInfo(){const e=`${this.config.apiEndpoint}/campaigns/${this.config.campaignId}`;this.log("获取项目信息",{url:e,campaignId:this.config.campaignId});try{if("undefined"!=typeof navigator&&!navigator.onLine){const e="网络连接不可用,请检查网络设置";return this.logError("网络检查失败",e),{success:!1,message:e,errorCode:"NETWORK_OFFLINE"}}const t={"Content-Type":"application/json",...this.config.apiKey&&{Authorization:`Bearer ${this.config.apiKey}`}};this.log("发送请求",{url:e,headers:Object.keys(t)});const n=await fetch(e,{method:"GET",headers:t,timeout:1e4});if(this.log("收到响应",{status:n.status,statusText:n.statusText,ok:n.ok,url:n.url}),!n.ok){let e=`HTTP ${n.status}: ${n.statusText}`,t="HTTP_ERROR";return 404===n.status?(e=`项目不存在 (ID: ${this.config.campaignId}),请检查 campaignId 配置`,t="CAMPAIGN_NOT_FOUND"):401===n.status?(e="API认证失败,请检查 apiKey 配置",t="UNAUTHORIZED"):403===n.status?(e="访问被拒绝,请检查权限配置",t="FORBIDDEN"):n.status>=500&&(e="服务器内部错误,请稍后重试",t="SERVER_ERROR"),this.logError("API请求失败",{status:n.status,message:e}),{success:!1,message:e,errorCode:t,httpStatus:n.status}}const o=await n.json();return this.log("解析响应成功",o),o}catch(e){let t=e.message,n="NETWORK_ERROR";return"TypeError"===e.name&&e.message.includes("Failed to fetch")?(t=`网络请求失败,请检查:\n1. API端点是否正确: ${this.config.apiEndpoint}\n2. 服务器是否运行\n3. 是否存在CORS问题\n4. 网络连接是否正常`,n="FETCH_FAILED"):"AbortError"===e.name?(t="请求超时,请检查网络连接或服务器响应速度",n="TIMEOUT"):e.message.includes("JSON")&&(t="服务器响应格式错误,请检查API端点配置",n="INVALID_RESPONSE"),this.logError("获取项目信息失败",{error:e.message,name:e.name,stack:e.stack,config:{apiEndpoint:this.config.apiEndpoint,campaignId:this.config.campaignId,hasApiKey:!!this.config.apiKey}}),{success:!1,message:t,errorCode:n,originalError:e.message}}}async verifyUserApplication(e){if(this.log("验证用户申请状态",e),!e||!e.email&&!e.phone)return{success:!1,message:"请提供邮箱或手机号",errorCode:"INVALID_PARAMS"};if(!this.config.apiKey)return{success:!1,message:"API Key 未配置,请在初始化时提供 apiKey",errorCode:"MISSING_API_KEY"};try{const t=new URLSearchParams({campaignId:this.config.campaignId});e.email&&t.append("email",e.email),e.phone&&t.append("phone",e.phone);const n=await fetch(`${this.config.apiEndpoint}/applications/verify?${t}`,{method:"GET",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.config.apiKey}`}}),o=await n.json();if(this.log("用户申请状态验证结果",o),n.ok&&o.success){if(!o.data||!o.data.application)return{success:!1,message:"该用户未申请公测",status:"NOT_APPLIED",errorCode:"USER_NOT_APPLIED"};const e=o.data.application,t=e.status;return"APPROVED"===t?{success:!0,message:"用户已通过审核",status:"APPROVED",applicationData:e}:"PENDING"===t?{success:!1,message:"该用户申请待审核",status:"PENDING",errorCode:"USER_NOT_APPROVED",applicationData:e}:"REJECTED"===t?{success:!1,message:"该用户申请已被拒绝",status:"REJECTED",errorCode:"USER_NOT_APPROVED",applicationData:e}:{success:!1,message:"用户申请状态未知",status:t,errorCode:"USER_NOT_APPROVED",applicationData:e}}return{success:!1,message:o.message||"验证失败",errorCode:o.errorCode||"NETWORK_ERROR"}}catch(e){return this.log("用户申请状态验证失败",e),{success:!1,message:"网络错误,请稍后重试",errorCode:"NETWORK_ERROR"}}}async getCampaignInfo_old(){try{const e=await fetch(`${this.config.apiEndpoint}/campaigns/${this.config.campaignId}`,{headers:{...this.config.apiKey&&{Authorization:`Bearer ${this.config.apiKey}`}}}),t=await e.json();return{success:e.ok,data:t,message:e.ok?"Success":t.message||"Failed to get campaign info"}}catch(e){return{success:!1,message:e.message,errorCode:"NETWORK_ERROR"}}}showSuccessMessage(e){if("undefined"==typeof document)return;e.querySelector(".betaflow-form").innerHTML='\n <div class="betaflow-success">\n <div class="icon">✓</div>\n <h2>申请提交成功!</h2>\n <p>感谢您的申请,我们会尽快审核并与您联系。</p>\n <div class="button-group">\n <button type="button" class="submit-btn" onclick="this.closest(\'.betaflow-modal\').remove()">确定</button>\n </div>\n </div>\n ',setTimeout(()=>{e.parentNode&&e.remove()},3e3)}updateConfig(e){this.config={...this.config,...e},this.log("配置已更新",this.config)}setLanguage(e){this.config.language=e,this.log("语言已设置为",e)}setTheme(e){this.config.theme=e,this.log("主题已设置为",e)}bindEvents(){"undefined"!=typeof document&&document.addEventListener("keydown",e=>{"Escape"===e.key&&this.hideApplicationForm()})}destroy(){this.floatingButton&&this.floatingButton.parentNode&&this.floatingButton.parentNode.removeChild(this.floatingButton),this.hideApplicationForm(),this.log("BetaFlow SDK 已销毁")}}function n(){if("undefined"==typeof window)return;const e=document.querySelector('script[src*="betaflow"]');if(e&&"true"===e.dataset.autoInit){const n={campaignId:e.dataset.campaignId,apiKey:e.dataset.apiKey,debug:"true"===e.dataset.debug};n.campaignId&&n.apiKey&&(window.betaflowInstance=new t(n))}}"undefined"!=typeof window&&(window.BetaFlow=t,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",n):n()),e.BetaFlow=t,e.default=t,Object.defineProperty(e,"__esModule",{value:!0})});