osmos-web-sdk
Version:
OnlineSales.ai Web SDK for Vue and React projects
785 lines (777 loc) • 26.7 kB
JavaScript
'use strict';
class DeviceDetector {
constructor(mobileMediaQuery = "(max-width: 767px)", tabletMediaQuery = "(min-width: 768px) and (max-width: 1023px)", detectDeviceViaUA = false) {
this.mobileMediaQuery = mobileMediaQuery;
this.tabletMediaQuery = tabletMediaQuery;
this.detectDeviceViaUA = detectDeviceViaUA;
}
/**
* 检测当前设备类型
*/
detectDevice() {
if (this.detectDeviceViaUA) {
return this.detectDeviceViaUserAgent();
}
return this.detectDeviceViaMediaQuery();
}
/**
* 通过媒体查询检测设备类型
*/
detectDeviceViaMediaQuery() {
if (typeof window === 'undefined') {
return 'DESKTOP';
}
if (window.matchMedia(this.mobileMediaQuery).matches) {
return 'MOBILE';
}
if (window.matchMedia(this.tabletMediaQuery).matches) {
return 'TABLET';
}
return 'DESKTOP';
}
/**
* 通过用户代理检测设备类型
*/
detectDeviceViaUserAgent() {
if (typeof navigator === 'undefined') {
return 'DESKTOP';
}
const userAgent = navigator.userAgent.toLowerCase();
// 检测移动设备
if (/android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i.test(userAgent)) {
// 进一步区分平板和手机
if (/ipad|android(?=.*\b(?:tablet|playbook)\b)/i.test(userAgent)) {
return 'TABLET';
}
return 'MOBILE';
}
return 'DESKTOP';
}
/**
* 检查是否为移动设备
*/
isMobile() {
return this.detectDevice() === 'MOBILE';
}
/**
* 检查是否为平板设备
*/
isTablet() {
return this.detectDevice() === 'TABLET';
}
/**
* 检查是否为桌面设备
*/
isDesktop() {
return this.detectDevice() === 'DESKTOP';
}
}
class HttpClient {
constructor(retryCount = 0, debugMode = false) {
this.retryCount = retryCount;
this.debugMode = debugMode;
}
/**
* 发送HTTP请求
*/
async request(url, options = {}, retryAttempt = 0) {
try {
if (this.debugMode) {
console.log(`[OsmosSDK] Making request to: ${url}`, options);
}
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options.headers,
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (this.debugMode) {
console.log(`[OsmosSDK] Response from ${url}:`, data);
}
return data;
}
catch (error) {
if (retryAttempt < this.retryCount) {
if (this.debugMode) {
console.log(`[OsmosSDK] Retry attempt ${retryAttempt + 1} for ${url}`);
}
// 指数退避重试
const delay = Math.pow(2, retryAttempt) * 1000;
await this.sleep(delay);
return this.request(url, options, retryAttempt + 1);
}
if (this.debugMode) {
console.error(`[OsmosSDK] Request failed after ${retryAttempt + 1} attempts:`, error);
}
throw error;
}
}
/**
* GET请求
*/
async get(url, params) {
const queryString = params ? this.buildQueryString(params) : '';
const fullUrl = queryString ? `${url}?${queryString}` : url;
return this.request(fullUrl, {
method: 'GET',
});
}
/**
* POST请求
*/
async post(url, data) {
return this.request(url, {
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
});
}
/**
* 构建查询字符串
*/
buildQueryString(params) {
return Object.entries(params)
.filter(([_, value]) => value !== undefined && value !== null)
.map(([key, value]) => {
if (Array.isArray(value)) {
return value.map(v => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`).join('&');
}
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
})
.join('&');
}
/**
* 延迟函数
*/
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}
class AdFetcher {
constructor(productAdsHost, displayAdsHost, retryCount = 0, debugMode = false) {
this.productAdsHost = productAdsHost;
this.displayAdsHost = displayAdsHost;
this.httpClient = new HttpClient(retryCount, debugMode);
}
/**
* 获取展示广告
*/
async fetchDisplayAds(query) {
try {
const url = `https://${this.displayAdsHost}/api/display-ads`;
// 处理adUnit参数
const params = {
...query,
adUnit: Array.isArray(query.adUnit) ? query.adUnit.join(',') : query.adUnit
};
const response = await this.httpClient.get(url, params);
return { success: true, data: response };
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* 获取产品页面广告
*/
async fetchPLAProductPageAds(query) {
try {
const url = `https://${this.productAdsHost}/api/pla/product-page`;
const params = {
...query,
productCount: query.productCount || 2,
skuIds: query.skuIds.join(',')
};
const response = await this.httpClient.get(url, params);
return { success: true, data: response };
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* 获取搜索页面广告
*/
async fetchPLASearchPageAds(query) {
try {
const url = `https://${this.productAdsHost}/api/pla/search-page`;
const params = {
...query,
productCount: query.productCount || 2
};
const response = await this.httpClient.get(url, params);
return { success: true, data: response };
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* 获取分类页面广告
*/
async fetchPLACategoryPageAd(query) {
try {
const url = `https://${this.productAdsHost}/api/pla/category-page`;
const params = {
...query,
productCount: query.productCount || 2,
categoryId: query.categoryId,
categories: query.categories ? query.categories.join(',') : undefined
};
const response = await this.httpClient.get(url, params);
return { success: true, data: response };
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* 获取TPA页面广告
*/
async fetchPLATPAPageAd(query) {
try {
const url = `https://${this.productAdsHost}/api/pla/tpa-page`;
const params = {
...query,
productCount: query.productCount || 2,
skuIds: query.skuIds ? query.skuIds.join(',') : undefined
};
const response = await this.httpClient.get(url, params);
return { success: true, data: response };
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* 获取购买页面广告
*/
async fetchPLAPurchasePageAd(query) {
try {
const url = `https://${this.productAdsHost}/api/pla/purchase-page`;
const params = {
...query,
productCount: query.productCount || 2,
skuIds: query.skuIds.join(',')
};
const response = await this.httpClient.get(url, params);
return { success: true, data: response };
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* 获取首页广告
*/
async fetchPLAHomePageAd(query) {
try {
const url = `https://${this.productAdsHost}/api/pla/home-page`;
const params = {
...query,
productCount: query.productCount || 2
};
const response = await this.httpClient.get(url, params);
return { success: true, data: response };
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
/**
* 获取自定义页面广告
*/
async fetchPLACustomPageAd(query) {
try {
const url = `https://${this.productAdsHost}/api/pla/custom-page`;
const params = {
...query,
productCount: query.productCount || 2,
mcategoriesIds: query.mcategoriesIds ? query.mcategoriesIds.join(',') : undefined,
brands: query.brands ? query.brands.join(',') : undefined,
categories: query.categories ? query.categories.join(',') : undefined
};
const response = await this.httpClient.get(url, params);
return { success: true, data: response };
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
}
}
}
class TrackingService {
constructor(eventTrackingHost, clientId, options = {}) {
var _a, _b, _c, _d, _e, _f, _g, _h;
this.eventTrackingHost = eventTrackingHost;
this.clientId = clientId;
this.cliUbid = options.cliUbid;
this.enableTracking = (_a = options.enableTracking) !== null && _a !== void 0 ? _a : true;
this.fireDefaultPixel = (_b = options.fireDefaultPixel) !== null && _b !== void 0 ? _b : true;
this.trackingParams = options.trackingParams;
this.searchKeywordKey = (_c = options.searchKeywordKey) !== null && _c !== void 0 ? _c : 'keyword';
this.customQueryParams = options.customQueryParams;
this.queryToParamMapping = (_d = options.queryToParamMapping) !== null && _d !== void 0 ? _d : {};
this.urlToParamMapping = (_e = options.urlToParamMapping) !== null && _e !== void 0 ? _e : {};
this.videoView = (_f = options.videoView) !== null && _f !== void 0 ? _f : 5;
this.defaultTrackSeconds = (_g = options.defaultTrackSeconds) !== null && _g !== void 0 ? _g : [1, 3];
this.debugMode = (_h = options.debugMode) !== null && _h !== void 0 ? _h : false;
this.httpClient = new HttpClient(0, this.debugMode);
}
/**
* 发送跟踪事件
*/
async trackEvent(event) {
if (!this.enableTracking) {
if (this.debugMode) {
console.log('[OsmosSDK] Tracking disabled, skipping event:', event);
}
return;
}
try {
const trackingData = {
clientId: this.clientId,
cliUbid: this.cliUbid,
timestamp: event.timestamp || Date.now(),
...event,
...this.getTrackingParams(),
...this.getCustomQueryParams()
};
const url = `https://${this.eventTrackingHost}/api/track`;
if (this.debugMode) {
console.log('[OsmosSDK] Sending tracking event:', trackingData);
}
await this.httpClient.post(url, trackingData);
}
catch (error) {
if (this.debugMode) {
console.error('[OsmosSDK] Failed to send tracking event:', error);
}
}
}
/**
* 跟踪产品查看事件
*/
async trackProductView(skuId, additionalData) {
await this.trackEvent({
eventType: 'PRODUCT_VIEW',
skuId,
...additionalData
});
}
/**
* 跟踪添加到购物车事件
*/
async trackAddToCart(skuId, quantity = 1, additionalData) {
await this.trackEvent({
eventType: 'ADD_TO_CART',
skuId,
quantity,
...additionalData
});
}
/**
* 跟踪购买事件
*/
async trackPurchase(skuIds, total, additionalData) {
await this.trackEvent({
eventType: 'PURCHASE',
skuIds,
total,
...additionalData
});
}
/**
* 跟踪广告点击事件
*/
async trackAdClick(adId, adUnit, additionalData) {
await this.trackEvent({
eventType: 'AD_CLICK',
adId,
adUnit,
...additionalData
});
}
/**
* 跟踪视频观看事件
*/
async trackVideoView(videoId, duration, additionalData) {
await this.trackEvent({
eventType: 'VIDEO_VIEW',
videoId,
duration,
viewed: duration >= this.videoView,
...additionalData
});
}
/**
* 跟踪搜索事件
*/
async trackSearch(keyword, resultsCount, additionalData) {
await this.trackEvent({
eventType: 'SEARCH',
keyword,
resultsCount,
...additionalData
});
}
/**
* 跟踪页面浏览事件
*/
async trackPageView(pageName, additionalData) {
await this.trackEvent({
eventType: 'PAGE_VIEW',
pageName,
url: typeof window !== 'undefined' ? window.location.href : undefined,
...additionalData
});
}
/**
* 获取跟踪参数
*/
getTrackingParams() {
if (typeof this.trackingParams === 'function') {
return this.trackingParams();
}
return this.trackingParams || {};
}
/**
* 获取自定义查询参数
*/
getCustomQueryParams() {
if (typeof this.customQueryParams === 'function') {
return this.customQueryParams();
}
return this.customQueryParams || {};
}
/**
* 从URL获取搜索关键词
*/
getSearchKeyword() {
if (typeof window === 'undefined') {
return null;
}
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(this.searchKeywordKey);
}
/**
* 从URL映射参数
*/
getMappedParams() {
if (typeof window === 'undefined') {
return {};
}
const params = {};
const urlParams = new URLSearchParams(window.location.search);
const pathname = window.location.pathname;
// 从查询参数映射
Object.entries(this.queryToParamMapping).forEach(([queryKey, paramKey]) => {
const value = urlParams.get(queryKey);
if (value) {
params[paramKey] = value;
}
});
// 从URL路径映射
Object.entries(this.urlToParamMapping).forEach(([pattern, paramKey]) => {
const regex = new RegExp(pattern);
const match = pathname.match(regex);
if (match && match[1]) {
params[paramKey] = match[1];
}
});
return params;
}
/**
* 发送默认像素
*/
async fireDefaultTrackingPixel() {
if (!this.fireDefaultPixel || !this.enableTracking) {
return;
}
try {
const pixelUrl = `https://${this.eventTrackingHost}/pixel.gif`;
const params = {
clientId: this.clientId,
cliUbid: this.cliUbid,
timestamp: Date.now(),
...this.getTrackingParams()
};
const queryString = new URLSearchParams(params).toString();
const fullUrl = `${pixelUrl}?${queryString}`;
if (this.debugMode) {
console.log('[OsmosSDK] Firing default pixel:', fullUrl);
}
// 创建图片元素来发送像素
const img = new Image();
img.src = fullUrl;
}
catch (error) {
if (this.debugMode) {
console.error('[OsmosSDK] Failed to fire default pixel:', error);
}
}
}
}
class OsmosSDK {
constructor() {
this.isInitialized = false;
// 初始化时不做任何操作,等待initialize调用
}
/**
* 初始化SDK
*/
initialize(options) {
if (this.isInitialized) {
console.warn('[OsmosSDK] SDK already initialized');
return;
}
// 验证必需参数
if (!options.clientId) {
throw new Error('clientId is required');
}
if (!options.productAdsHost) {
throw new Error('productAdsHost is required');
}
if (!options.displayAdsHost) {
throw new Error('displayAdsHost is required');
}
this.options = {
// 默认值
device: 'DESKTOP',
mobileMediaQuery: "(max-width: 767px)",
tabletMediaQuery: "(min-width: 768px) and (max-width: 1023px)",
detectDeviceViaUA: false,
enableTracking: true,
fireDefaultPixel: true,
searchKeywordKey: "keyword",
queryToParamMapping: {},
urlToParamMapping: {},
videoView: 5,
defaultTrackSeconds: [1, 3],
enableOMSdk: false,
omSdkOptions: {},
selectorPrefix: "os",
retryCount: 0,
debugMode: false,
...options
};
// 初始化设备检测器
this.deviceDetector = new DeviceDetector(this.options.mobileMediaQuery, this.options.tabletMediaQuery, this.options.detectDeviceViaUA);
// 初始化广告获取器
this._adFetcher = new AdFetcher(this.options.productAdsHost, this.options.displayAdsHost, this.options.retryCount, this.options.debugMode);
// 初始化跟踪服务
const eventTrackingHost = this.options.eventTrackingHost || 'tracking.onlinesales.ai';
this.trackingService = new TrackingService(eventTrackingHost, this.options.clientId, {
cliUbid: this.options.cliUbid,
enableTracking: this.options.enableTracking,
fireDefaultPixel: this.options.fireDefaultPixel,
trackingParams: this.options.trackingParams,
searchKeywordKey: this.options.searchKeywordKey,
customQueryParams: this.options.customQueryParams,
queryToParamMapping: this.options.queryToParamMapping,
urlToParamMapping: this.options.urlToParamMapping,
videoView: this.options.videoView,
defaultTrackSeconds: this.options.defaultTrackSeconds,
debugMode: this.options.debugMode
});
this.isInitialized = true;
if (this.options.debugMode) {
console.log('[OsmosSDK] SDK initialized successfully', this.options);
}
// 发送默认跟踪像素
this.trackingService.fireDefaultTrackingPixel();
}
/**
* 更新SDK选项
*/
updateOptions(options) {
if (!this.isInitialized) {
throw new Error('SDK must be initialized before updating options');
}
this.options = { ...this.options, ...options };
// 重新初始化相关服务
if (options.mobileMediaQuery || options.tabletMediaQuery || options.detectDeviceViaUA) {
this.deviceDetector = new DeviceDetector(this.options.mobileMediaQuery, this.options.tabletMediaQuery, this.options.detectDeviceViaUA);
}
if (options.productAdsHost || options.displayAdsHost || options.retryCount || options.debugMode) {
this._adFetcher = new AdFetcher(this.options.productAdsHost, this.options.displayAdsHost, this.options.retryCount, this.options.debugMode);
}
if (this.options.debugMode) {
console.log('[OsmosSDK] Options updated', this.options);
}
}
/**
* 获取当前设备类型
*/
getDevice() {
if (!this.isInitialized) {
throw new Error('SDK must be initialized before getting device type');
}
return this.options.device || this.deviceDetector.detectDevice();
}
/**
* 获取广告获取器
*/
get adFetcher() {
if (!this.isInitialized) {
throw new Error('SDK must be initialized before accessing adFetcher');
}
return this._adFetcher;
}
/**
* 获取跟踪服务
*/
get tracking() {
if (!this.isInitialized) {
throw new Error('SDK must be initialized before accessing tracking service');
}
return this.trackingService;
}
/**
* 检查SDK是否已初始化
*/
isReady() {
return this.isInitialized;
}
/**
* 获取当前配置
*/
getConfig() {
if (!this.isInitialized) {
throw new Error('SDK must be initialized before getting config');
}
return { ...this.options };
}
}
// 创建全局实例(用于浏览器环境)
let globalOsmos = null;
/**
* 获取全局Osmos实例
*/
function getOsmos() {
if (!globalOsmos) {
globalOsmos = new OsmosSDK();
}
return globalOsmos;
}
/**
* 初始化全局Osmos实例
*/
function initializeOsmos(options) {
const osmos = getOsmos();
osmos.initialize(options);
return osmos;
}
// 浏览器环境下的全局对象
if (typeof window !== 'undefined') {
window.osmos = {
initialize: (options) => {
const osmos = getOsmos();
osmos.initialize(options);
return osmos;
},
updateOptions: (options) => {
const osmos = getOsmos();
osmos.updateOptions(options);
},
getDevice: () => {
const osmos = getOsmos();
return osmos.getDevice();
},
isReady: () => {
const osmos = getOsmos();
return osmos.isReady();
},
getConfig: () => {
const osmos = getOsmos();
return osmos.getConfig();
},
adFetcher: {
fetchDisplayAds: (query) => {
const osmos = getOsmos();
return osmos.adFetcher.fetchDisplayAds(query);
},
fetchPLAProductPageAds: (query) => {
const osmos = getOsmos();
return osmos.adFetcher.fetchPLAProductPageAds(query);
},
fetchPLASearchPageAds: (query) => {
const osmos = getOsmos();
return osmos.adFetcher.fetchPLASearchPageAds(query);
},
fetchPLACategoryPageAd: (query) => {
const osmos = getOsmos();
return osmos.adFetcher.fetchPLACategoryPageAd(query);
},
fetchPLATPAPageAd: (query) => {
const osmos = getOsmos();
return osmos.adFetcher.fetchPLATPAPageAd(query);
},
fetchPLAPurchasePageAd: (query) => {
const osmos = getOsmos();
return osmos.adFetcher.fetchPLAPurchasePageAd(query);
},
fetchPLAHomePageAd: (query) => {
const osmos = getOsmos();
return osmos.adFetcher.fetchPLAHomePageAd(query);
},
fetchPLACustomPageAd: (query) => {
const osmos = getOsmos();
return osmos.adFetcher.fetchPLACustomPageAd(query);
}
},
tracking: {
trackEvent: (event) => {
const osmos = getOsmos();
return osmos.tracking.trackEvent(event);
},
trackProductView: (skuId, additionalData) => {
const osmos = getOsmos();
return osmos.tracking.trackProductView(skuId, additionalData);
},
trackAddToCart: (skuId, quantity, additionalData) => {
const osmos = getOsmos();
return osmos.tracking.trackAddToCart(skuId, quantity, additionalData);
},
trackPurchase: (skuIds, total, additionalData) => {
const osmos = getOsmos();
return osmos.tracking.trackPurchase(skuIds, total, additionalData);
},
trackAdClick: (adId, adUnit, additionalData) => {
const osmos = getOsmos();
return osmos.tracking.trackAdClick(adId, adUnit, additionalData);
},
trackVideoView: (videoId, duration, additionalData) => {
const osmos = getOsmos();
return osmos.tracking.trackVideoView(videoId, duration, additionalData);
},
trackSearch: (keyword, resultsCount, additionalData) => {
const osmos = getOsmos();
return osmos.tracking.trackSearch(keyword, resultsCount, additionalData);
},
trackPageView: (pageName, additionalData) => {
const osmos = getOsmos();
return osmos.tracking.trackPageView(pageName, additionalData);
},
getSearchKeyword: () => {
const osmos = getOsmos();
return osmos.tracking.getSearchKeyword();
},
getMappedParams: () => {
const osmos = getOsmos();
return osmos.tracking.getMappedParams();
}
}
};
}
exports.AdFetcher = AdFetcher;
exports.DeviceDetector = DeviceDetector;
exports.HttpClient = HttpClient;
exports.OsmosSDK = OsmosSDK;
exports.TrackingService = TrackingService;
exports.getOsmos = getOsmos;
exports.initializeOsmos = initializeOsmos;
//# sourceMappingURL=index.js.map