PageSourceSearch

https://grok4.app/js/global-trial-manager.js

js grok4.app collected 2026-10-05 21:42:28 UTC 6,618 bytes, 193 lines download raw bytes

1/**
2 * 全局试用次数管理器 - 跨语言版本同步
3 * Global Trial Manager - Cross-language synchronization
4 */
5
6class GlobalTrialManager {
7    constructor() {
8        // 全局键名,所有语言版本共享
9        this.GLOBAL_TRIAL_KEY = 'grok4_global_trial_used';
10        this.GLOBAL_TRIAL_COUNT_KEY = 'grok4_global_trial_count';
11        this.GLOBAL_TRIAL_TIMESTAMP_KEY = 'grok4_global_trial_timestamp';
12        
13        // 浏览器指纹生成
14        this.fingerprint = this.generateBrowserFingerprint();
15        
16        this.init();
17    }
18    
19    init() {
20        // 初始化试用状态
21        if (!localStorage.getItem(this.GLOBAL_TRIAL_KEY)) {
22            this.resetTrialStatus();
23        }
24    }
25    
26    generateBrowserFingerprint() {
27        // 生成基于浏览器环境的指纹
28        const canvas = document.createElement('canvas');
29        const ctx = canvas.getContext('2d');
30        ctx.textBaseline = 'top';
31        ctx.font = '14px Arial';
32        ctx.fillText('Browser fingerprint', 2, 2);
33        
34        const fingerprint = [
35            navigator.userAgent,
36            navigator.language,
37            screen.width + 'x' + screen.height,
38            new Date().getTimezoneOffset(),
39            canvas.toDataURL()
40        ].join('|');
41        
42        // 简单哈希
43        let hash = 0;
44        for (let i = 0; i < fingerprint.length; i++) {
45            const char = fingerprint.charCodeAt(i);
46            hash = ((hash << 5) - hash) + char;
47            hash = hash & hash; // 转换为32位整数
48        }
49        
50        return Math.abs(hash).toString(36);
51    }
52    
53    hasTriedBefore() {
54        const globalUsed = localStorage.getItem(this.GLOBAL_TRIAL_KEY) === 'true';
55        const fingerprintUsed = localStorage.getItem(`trial_fp_${this.fingerprint}`) === 'true';
56        
57        return globalUsed || fingerprintUsed;
58    }
59    
60    getRemainingTrials() {
61        return this.hasTriedBefore() ? 0 : 1;
62    }
63    
64    consumeTrial() {
65        if (this.hasTriedBefore()) {
66            return false; // 已经使用过了
67        }
68        
69        // 标记为已使用
70        localStorage.setItem(this.GLOBAL_TRIAL_KEY, 'true');
71        localStorage.setItem(this.GLOBAL_TRIAL_COUNT_KEY, '0');
72        localStorage.setItem(this.GLOBAL_TRIAL_TIMESTAMP_KEY, new Date().toISOString());
73        localStorage.setItem(`trial_fp_${this.fingerprint}`, 'true');
74        
75        // 触发跨标签页同步事件
76        this.broadcastTrialUpdate();
77        
78        return true;
79    }
80    
81    resetTrialStatus() {
82        localStorage.setItem(this.GLOBAL_TRIAL_KEY, 'false');
83        localStorage.setItem(this.GLOBAL_TRIAL_COUNT_KEY, '1');
84        localStorage.removeItem(this.GLOBAL_TRIAL_TIMESTAMP_KEY);
85        
86        // 清理指纹记录(开发测试用)
87        Object.keys(localStorage).forEach(key => {
88            if (key.startsWith('trial_fp_')) {
89                localStorage.removeItem(key);
90            }
91        });
92    }
93    
94    broadcastTrialUpdate() {
95        // 发送自定义事件,通知其他标签页
96        const event = new CustomEvent('grok4TrialUpdate', {
97            detail: {
98                hasTriedBefore: this.hasTriedBefore(),
99                remainingTrials: this.getRemainingTrials(),
100                timestamp: new Date().toISOString()
101            }
102        });
103        
104        window.dispatchEvent(event);
105        
106        // 使用 localStorage 事件进行跨标签页通信
107        localStorage.setItem('grok4_trial_sync', JSON.stringify({
108            action: 'trial_consumed',
109            timestamp: Date.now(),
110            fingerprint: this.fingerprint
111        }));
112    }
113    
114    // 监听其他标签页的试用状态变化
115    setupCrossTabSync() {
116        window.addEventListener('storage', (event) => {
117            if (event.key === 'grok4_trial_sync') {
118                try {
119                    const data = JSON.parse(event.newValue);
120                    if (data.action === 'trial_consumed') {
121                        // 其他标签页消费了试用次数,刷新当前页面状态
122                        this.refreshTrialStatus();
123                    }
124                } catch (error) {
125                    console.warn('Failed to parse trial sync data:', error);
126                }
127            }
128        });
129        
130        // 监听自定义事件
131        window.addEventListener('grok4TrialUpdate', (event) => {
132            this.refreshTrialStatus();
133        });
134    }
135    
136    refreshTrialStatus() {
137        // 刷新试用状态显示
138        const event = new CustomEvent('refreshTrialStatus', {
139            detail: {
140                hasTriedBefore: this.hasTriedBefore(),
141                remainingTrials: this.getRemainingTrials()
142            }
143        });
144        
145        window.dispatchEvent(event);
146    }
147    
148    // 获取试用使用的时间戳
149    getTrialTimestamp() {
150        return localStorage.getItem(this.GLOBAL_TRIAL_TIMESTAMP_KEY);
151    }
152    
153    // 开发者工具:重置试用状态(仅用于测试)
154    devResetTrial() {
155        if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
156            this.resetTrialStatus();
157            console.log('Trial status reset for development');
158            this.refreshTrialStatus();
159        }
160    }
161}
162
163// 全局实例
164window.globalTrialManager = new GlobalTrialManager();
165window.globalTrialManager.setupCrossTabSync();
166
167// 开发者控制台快捷方式
168if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
169    window.resetTrial = () => window.globalTrialManager.devResetTrial();
170    console.log('Development mode: Use resetTrial() in console to reset trial status');
171}
172
173// Nav dropdown click toggle (mobile & touch support)
174document.addEventListener('DOMContentLoaded', function() {
175    document.querySelectorAll('.nav__dropdown-toggle').forEach(function(toggle) {
176        toggle.addEventListener('click', function(e) {
177            e.preventDefault();
178            var item = this.closest('.nav__item--dropdown');
179            var isActive = item.classList.contains('active');
180            document.querySelectorAll('.nav__item--dropdown').forEach(function(el) {
181                el.classList.remove('active');
182            });
183            if (!isActive) item.classList.add('active');
184        });
185    });
186    document.addEventListener('click', function(e) {
187        if (!e.target.closest('.nav__item--dropdown')) {
188            document.querySelectorAll('.nav__item--dropdown').forEach(function(el) {
189                el.classList.remove('active');
190            });
191        }
192    });
193});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.