@rashidazarang/airtable-mcp 1.5.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug-report.yml +173 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +209 -0
- package/.github/ISSUE_TEMPLATE/security-report.yml +216 -0
- package/.github/pull_request_template.md +245 -0
- package/.github/workflows/ci-cd.yml +408 -0
- package/.github/workflows/security-audit.yml +316 -0
- package/API_DOCUMENTATION.md +897 -0
- package/CODE_OF_CONDUCT.md +181 -0
- package/Dockerfile.production +127 -0
- package/README.md +55 -10
- package/RELEASE_NOTES_v1.6.0.md +248 -0
- package/airtable-clipper/CHANGELOG.md +198 -0
- package/airtable-clipper/CHROME_STORE_SUBMISSION.md +343 -0
- package/airtable-clipper/LAUNCH_STRATEGY.md +495 -0
- package/airtable-clipper/LICENSE +21 -0
- package/airtable-clipper/OAUTH_SETUP.md +51 -0
- package/airtable-clipper/PRIVACY_POLICY.md +187 -0
- package/airtable-clipper/README.md +575 -0
- package/airtable-clipper/SUBMIT_TO_CHROME_STORE.md +273 -0
- package/airtable-clipper/build.sh +85 -0
- package/airtable-clipper/docs/QUICK_START.md +99 -0
- package/airtable-clipper/docs/SETUP.md +291 -0
- package/airtable-clipper/extension/background.js +337 -0
- package/airtable-clipper/extension/base-setup.html +324 -0
- package/airtable-clipper/extension/base-setup.js +471 -0
- package/airtable-clipper/extension/content.js +771 -0
- package/airtable-clipper/extension/icons/README.md +69 -0
- package/airtable-clipper/extension/icons/icon-16.png +3 -0
- package/airtable-clipper/extension/manifest.json +73 -0
- package/airtable-clipper/extension/popup.html +144 -0
- package/airtable-clipper/extension/popup.js +475 -0
- package/airtable-clipper/extension/styles/content.css +229 -0
- package/airtable-clipper/extension/styles/popup.css +477 -0
- package/airtable-clipper/privacy-policy.md +63 -0
- package/airtable-clipper/releases/v1.0.0/background.js +337 -0
- package/airtable-clipper/releases/v1.0.0/base-setup.html +324 -0
- package/airtable-clipper/releases/v1.0.0/base-setup.js +471 -0
- package/airtable-clipper/releases/v1.0.0/content.js +771 -0
- package/airtable-clipper/releases/v1.0.0/icons/README.md +69 -0
- package/airtable-clipper/releases/v1.0.0/icons/icon-128.png +2 -0
- package/airtable-clipper/releases/v1.0.0/icons/icon-16.png +3 -0
- package/airtable-clipper/releases/v1.0.0/icons/icon-32.png +2 -0
- package/airtable-clipper/releases/v1.0.0/icons/icon-48.png +2 -0
- package/airtable-clipper/releases/v1.0.0/manifest.json +73 -0
- package/airtable-clipper/releases/v1.0.0/popup.html +144 -0
- package/airtable-clipper/releases/v1.0.0/popup.js +475 -0
- package/airtable-clipper/releases/v1.0.0/sidepanel.html +25 -0
- package/airtable-clipper/releases/v1.0.0/styles/content.css +229 -0
- package/airtable-clipper/releases/v1.0.0/styles/popup.css +477 -0
- package/airtable-clipper/releases/v1.0.1/background.js +337 -0
- package/airtable-clipper/releases/v1.0.1/base-setup.html +324 -0
- package/airtable-clipper/releases/v1.0.1/base-setup.js +471 -0
- package/airtable-clipper/releases/v1.0.1/content.js +771 -0
- package/airtable-clipper/releases/v1.0.1/icons/README.md +69 -0
- package/airtable-clipper/releases/v1.0.1/icons/icon-128.png +2 -0
- package/airtable-clipper/releases/v1.0.1/icons/icon-16.png +3 -0
- package/airtable-clipper/releases/v1.0.1/icons/icon-32.png +2 -0
- package/airtable-clipper/releases/v1.0.1/icons/icon-48.png +2 -0
- package/airtable-clipper/releases/v1.0.1/manifest.json +70 -0
- package/airtable-clipper/releases/v1.0.1/popup.html +157 -0
- package/airtable-clipper/releases/v1.0.1/popup.js +562 -0
- package/airtable-clipper/releases/v1.0.1/sidepanel.html +25 -0
- package/airtable-clipper/releases/v1.0.1/styles/content.css +229 -0
- package/airtable-clipper/releases/v1.0.1/styles/popup.css +647 -0
- package/airtable-clipper/releases/v1.0.2/background.js +337 -0
- package/airtable-clipper/releases/v1.0.2/base-setup.html +324 -0
- package/airtable-clipper/releases/v1.0.2/base-setup.js +471 -0
- package/airtable-clipper/releases/v1.0.2/content.js +771 -0
- package/airtable-clipper/releases/v1.0.2/icons/README.md +69 -0
- package/airtable-clipper/releases/v1.0.2/icons/icon-128.png +2 -0
- package/airtable-clipper/releases/v1.0.2/icons/icon-16.png +3 -0
- package/airtable-clipper/releases/v1.0.2/icons/icon-32.png +2 -0
- package/airtable-clipper/releases/v1.0.2/icons/icon-48.png +2 -0
- package/airtable-clipper/releases/v1.0.2/manifest.json +62 -0
- package/airtable-clipper/releases/v1.0.2/popup.html +157 -0
- package/airtable-clipper/releases/v1.0.2/popup.js +567 -0
- package/airtable-clipper/releases/v1.0.2/sidepanel.html +25 -0
- package/airtable-clipper/releases/v1.0.2/styles/content.css +229 -0
- package/airtable-clipper/releases/v1.0.2/styles/popup.css +647 -0
- package/airtable-clipper/terms-of-service.md +124 -0
- package/airtable-clipper/test-credentials.md +61 -0
- package/airtable-clipper/test-extension/background.js +337 -0
- package/airtable-clipper/test-extension/base-setup.html +324 -0
- package/airtable-clipper/test-extension/base-setup.js +471 -0
- package/airtable-clipper/test-extension/content.js +873 -0
- package/airtable-clipper/test-extension/icons/README.md +69 -0
- package/airtable-clipper/test-extension/icons/icon-128.png +2 -0
- package/airtable-clipper/test-extension/icons/icon-16.png +3 -0
- package/airtable-clipper/test-extension/icons/icon-32.png +2 -0
- package/airtable-clipper/test-extension/icons/icon-48.png +2 -0
- package/airtable-clipper/test-extension/manifest.json +72 -0
- package/airtable-clipper/test-extension/popup.html +274 -0
- package/airtable-clipper/test-extension/popup.js +729 -0
- package/airtable-clipper/test-extension/sidepanel.html +25 -0
- package/airtable-clipper/test-extension/styles/content.css +229 -0
- package/airtable-clipper/test-extension/styles/popup.css +794 -0
- package/airtable_mcp_v2.js +1505 -0
- package/airtable_mcp_v2_oauth.js +1048 -0
- package/airtable_mcp_v3_advanced.js +1161 -0
- package/airtable_simple.js +447 -1
- package/airtable_simple_production.js +532 -0
- package/docker-compose.production.yml +366 -0
- package/helm/airtable-mcp/Chart.yaml +122 -0
- package/helm/airtable-mcp/values.yaml +538 -0
- package/k8s/deployment.yaml +402 -0
- package/k8s/namespace.yaml +108 -0
- package/k8s/service.yaml +194 -0
- package/monitoring/alerts.yml +289 -0
- package/monitoring/prometheus.yml +224 -0
- package/package.json +6 -6
- package/test_v1.6.0_comprehensive.sh +187 -0
- package/.claude/settings.local.json +0 -12
- package/airtable-mcp-1.1.0.tgz +0 -0
- package/airtable_enhanced.js +0 -499
- package/airtable_simple_v1.2.4_backup.js +0 -277
- package/airtable_v1.4.0.js +0 -654
- package/rashidazarang-airtable-mcp-1.1.0.tgz +0 -0
- package/rashidazarang-airtable-mcp-1.2.0.tgz +0 -0
- package/rashidazarang-airtable-mcp-1.2.1.tgz +0 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Terms of Service for Airtable Clipper
|
|
2
|
+
|
|
3
|
+
**Last updated: August 15, 2025**
|
|
4
|
+
|
|
5
|
+
## 1. Acceptance of Terms
|
|
6
|
+
|
|
7
|
+
By installing, accessing, or using Airtable Clipper ("the Extension"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, do not use the Extension.
|
|
8
|
+
|
|
9
|
+
## 2. Description of Service
|
|
10
|
+
|
|
11
|
+
Airtable Clipper is a Chrome browser extension that enables users to save web content directly to their personal Airtable bases. The Extension provides functionality to:
|
|
12
|
+
|
|
13
|
+
- Save webpage titles, URLs, and content to Airtable
|
|
14
|
+
- Extract LinkedIn profile information (public data only)
|
|
15
|
+
- Organize web research and data collection
|
|
16
|
+
- Bulk selection and saving of multiple items
|
|
17
|
+
|
|
18
|
+
## 3. User Responsibilities
|
|
19
|
+
|
|
20
|
+
### 3.1 Account Security
|
|
21
|
+
- You are responsible for maintaining the security of your Airtable credentials
|
|
22
|
+
- You must not share your Personal Access Token with others
|
|
23
|
+
- You agree to notify us immediately of any unauthorized use of your account
|
|
24
|
+
|
|
25
|
+
### 3.2 Lawful Use
|
|
26
|
+
- You agree to use the Extension only for lawful purposes
|
|
27
|
+
- You will not use the Extension to collect or process data in violation of applicable laws
|
|
28
|
+
- You respect the terms of service of websites you visit while using the Extension
|
|
29
|
+
|
|
30
|
+
### 3.3 Data Accuracy
|
|
31
|
+
- You are responsible for the accuracy of data you save to your Airtable bases
|
|
32
|
+
- You understand that the Extension processes publicly available web content only
|
|
33
|
+
|
|
34
|
+
## 4. Privacy and Data Protection
|
|
35
|
+
|
|
36
|
+
- Your data privacy is governed by our Privacy Policy
|
|
37
|
+
- We do not store or access your Airtable data
|
|
38
|
+
- All data processing occurs locally in your browser
|
|
39
|
+
- We do not sell or share your personal information
|
|
40
|
+
|
|
41
|
+
## 5. Intellectual Property
|
|
42
|
+
|
|
43
|
+
### 5.1 Extension Rights
|
|
44
|
+
- The Extension and its original content are owned by Rashid Azarang
|
|
45
|
+
- You are granted a limited, non-exclusive license to use the Extension
|
|
46
|
+
|
|
47
|
+
### 5.2 User Content
|
|
48
|
+
- You retain ownership of all data you save using the Extension
|
|
49
|
+
- You grant us no rights to your data or content
|
|
50
|
+
|
|
51
|
+
## 6. Third-Party Services
|
|
52
|
+
|
|
53
|
+
### 6.1 Airtable Integration
|
|
54
|
+
- The Extension integrates with Airtable's services
|
|
55
|
+
- Your use of Airtable is subject to Airtable's Terms of Service
|
|
56
|
+
- We are not responsible for Airtable's availability or performance
|
|
57
|
+
|
|
58
|
+
### 6.2 Website Content
|
|
59
|
+
- You are responsible for respecting the terms of service of websites you visit
|
|
60
|
+
- We do not control or endorse third-party website content
|
|
61
|
+
|
|
62
|
+
## 7. Disclaimers
|
|
63
|
+
|
|
64
|
+
### 7.1 Service Availability
|
|
65
|
+
- The Extension is provided "as is" without warranties of any kind
|
|
66
|
+
- We do not guarantee uninterrupted or error-free service
|
|
67
|
+
- We reserve the right to modify or discontinue the Extension at any time
|
|
68
|
+
|
|
69
|
+
### 7.2 Data Loss
|
|
70
|
+
- While we strive for reliability, we cannot guarantee against data loss
|
|
71
|
+
- You are responsible for backing up important data
|
|
72
|
+
- We recommend regular exports of your Airtable data
|
|
73
|
+
|
|
74
|
+
## 8. Limitation of Liability
|
|
75
|
+
|
|
76
|
+
To the maximum extent permitted by law:
|
|
77
|
+
- We shall not be liable for any indirect, incidental, or consequential damages
|
|
78
|
+
- Our total liability shall not exceed $10 USD
|
|
79
|
+
- We are not liable for data loss, business interruption, or lost profits
|
|
80
|
+
|
|
81
|
+
## 9. Support and Updates
|
|
82
|
+
|
|
83
|
+
### 9.1 Support
|
|
84
|
+
- Support is provided on a best-effort basis through GitHub issues
|
|
85
|
+
- We do not guarantee response times or issue resolution
|
|
86
|
+
|
|
87
|
+
### 9.2 Updates
|
|
88
|
+
- We may release updates to improve functionality and security
|
|
89
|
+
- Updates are provided at our discretion
|
|
90
|
+
- Continued use after updates constitutes acceptance of changes
|
|
91
|
+
|
|
92
|
+
## 10. Termination
|
|
93
|
+
|
|
94
|
+
### 10.1 User Termination
|
|
95
|
+
- You may stop using the Extension at any time by uninstalling it
|
|
96
|
+
- You may disconnect your Airtable account through the Extension settings
|
|
97
|
+
|
|
98
|
+
### 10.2 Service Termination
|
|
99
|
+
- We may terminate or suspend the Extension for violations of these Terms
|
|
100
|
+
- We may discontinue the Extension with reasonable notice
|
|
101
|
+
|
|
102
|
+
## 11. Changes to Terms
|
|
103
|
+
|
|
104
|
+
- We may update these Terms from time to time
|
|
105
|
+
- Changes will be posted on this page with an updated date
|
|
106
|
+
- Continued use after changes constitutes acceptance of new Terms
|
|
107
|
+
|
|
108
|
+
## 12. Governing Law
|
|
109
|
+
|
|
110
|
+
These Terms are governed by the laws of the United States and the State of California, without regard to conflict of law principles.
|
|
111
|
+
|
|
112
|
+
## 13. Contact Information
|
|
113
|
+
|
|
114
|
+
For questions about these Terms, please contact:
|
|
115
|
+
- Email: rashid.azarang.e@gmail.com
|
|
116
|
+
- GitHub: https://github.com/rashidazarang/airtable-clipper/issues
|
|
117
|
+
|
|
118
|
+
## 14. Severability
|
|
119
|
+
|
|
120
|
+
If any provision of these Terms is found to be unenforceable, the remaining provisions will remain in full force and effect.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
By using Airtable Clipper, you acknowledge that you have read, understood, and agree to be bound by these Terms of Service.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Test Credentials for Airtable Clipper
|
|
2
|
+
|
|
3
|
+
## For Manual Connection Testing
|
|
4
|
+
|
|
5
|
+
### Personal Access Token:
|
|
6
|
+
```
|
|
7
|
+
pat_yourTokenHere_generateFromAirtable
|
|
8
|
+
```
|
|
9
|
+
*Note: You need to create this from https://airtable.com/create/tokens*
|
|
10
|
+
|
|
11
|
+
### Base ID:
|
|
12
|
+
```
|
|
13
|
+
appYourBaseId
|
|
14
|
+
```
|
|
15
|
+
*Note: This is found in your Airtable URL like https://airtable.com/appYourBaseId/...*
|
|
16
|
+
|
|
17
|
+
## Setting Up Test Base
|
|
18
|
+
|
|
19
|
+
1. Go to https://airtable.com
|
|
20
|
+
2. Create a new base called "Clipper Test"
|
|
21
|
+
3. Create these tables:
|
|
22
|
+
|
|
23
|
+
### Table 1: "Contacts"
|
|
24
|
+
Fields:
|
|
25
|
+
- Name (Single line text)
|
|
26
|
+
- Title (Single line text)
|
|
27
|
+
- Company (Single line text)
|
|
28
|
+
- LinkedIn URL (URL)
|
|
29
|
+
- Location (Single line text)
|
|
30
|
+
- Date Added (Date)
|
|
31
|
+
- Source (Single select: LinkedIn, Manual, Import)
|
|
32
|
+
- Notes (Long text)
|
|
33
|
+
|
|
34
|
+
### Table 2: "Web Clips"
|
|
35
|
+
Fields:
|
|
36
|
+
- Title (Single line text)
|
|
37
|
+
- URL (URL)
|
|
38
|
+
- Content (Long text)
|
|
39
|
+
- Domain (Single line text)
|
|
40
|
+
- Saved At (Date)
|
|
41
|
+
- Type (Single select: Web Page, Text Selection, Image)
|
|
42
|
+
|
|
43
|
+
## OAuth Client ID (Already Configured)
|
|
44
|
+
```
|
|
45
|
+
db9710e0-7d4b-4be6-bb15-2621448b0425
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Testing Steps
|
|
49
|
+
|
|
50
|
+
1. **Install Extension**: Load the `test-extension` folder as an unpacked extension in Chrome
|
|
51
|
+
2. **Manual Connection**: Use the Personal Access Token and Base ID from above
|
|
52
|
+
3. **Test Quick Save**: Visit any webpage and click "Save Page"
|
|
53
|
+
4. **Test LinkedIn**: Visit a LinkedIn profile and click "Save Profile"
|
|
54
|
+
5. **Test Bulk Mode**: Try bulk selection on a webpage
|
|
55
|
+
|
|
56
|
+
## Troubleshooting
|
|
57
|
+
|
|
58
|
+
- Check Chrome DevTools Console for errors
|
|
59
|
+
- Verify token has correct permissions (data.records:read, data.records:write, schema.bases:read)
|
|
60
|
+
- Ensure base ID is correct from Airtable URL
|
|
61
|
+
- Check that table names match exactly (case-sensitive)
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
// Airtable Clipper - Background Service Worker
|
|
2
|
+
// Handles context menus, notifications, and cross-tab communication
|
|
3
|
+
|
|
4
|
+
class BackgroundService {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.init();
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
init() {
|
|
10
|
+
// Create context menu on installation
|
|
11
|
+
chrome.runtime.onInstalled.addListener(() => {
|
|
12
|
+
this.createContextMenus();
|
|
13
|
+
this.setupNotifications();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Handle context menu clicks
|
|
17
|
+
chrome.contextMenus.onClicked.addListener((info, tab) => {
|
|
18
|
+
this.handleContextMenuClick(info, tab);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Handle messages from content scripts and popup
|
|
22
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
23
|
+
this.handleMessage(message, sender, sendResponse);
|
|
24
|
+
return true; // Keep message channel open for async responses
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Handle extension icon clicks
|
|
28
|
+
chrome.action.onClicked.addListener((tab) => {
|
|
29
|
+
this.handleActionClick(tab);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Periodic cleanup
|
|
33
|
+
chrome.alarms.onAlarm.addListener((alarm) => {
|
|
34
|
+
if (alarm.name === 'cleanup') {
|
|
35
|
+
this.cleanupStorage();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Set up cleanup alarm
|
|
40
|
+
chrome.alarms.create('cleanup', { delayInMinutes: 60, periodInMinutes: 60 });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
createContextMenus() {
|
|
44
|
+
// Remove existing menus
|
|
45
|
+
chrome.contextMenus.removeAll();
|
|
46
|
+
|
|
47
|
+
// Main menu
|
|
48
|
+
chrome.contextMenus.create({
|
|
49
|
+
id: 'save-to-airtable',
|
|
50
|
+
title: 'Save to Airtable',
|
|
51
|
+
contexts: ['page', 'selection', 'link', 'image']
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// LinkedIn specific
|
|
55
|
+
chrome.contextMenus.create({
|
|
56
|
+
id: 'save-linkedin-profile',
|
|
57
|
+
title: 'Save LinkedIn Profile',
|
|
58
|
+
contexts: ['page'],
|
|
59
|
+
documentUrlPatterns: ['*://www.linkedin.com/in/*', '*://linkedin.com/in/*']
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Quick save selection
|
|
63
|
+
chrome.contextMenus.create({
|
|
64
|
+
id: 'quick-save-selection',
|
|
65
|
+
title: 'Quick Save Selected Text',
|
|
66
|
+
contexts: ['selection']
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async handleContextMenuClick(info, tab) {
|
|
71
|
+
try {
|
|
72
|
+
switch (info.menuItemId) {
|
|
73
|
+
case 'save-to-airtable':
|
|
74
|
+
await this.triggerSave(tab, { type: 'general', info });
|
|
75
|
+
break;
|
|
76
|
+
case 'save-linkedin-profile':
|
|
77
|
+
await this.triggerSave(tab, { type: 'linkedin', info });
|
|
78
|
+
break;
|
|
79
|
+
case 'quick-save-selection':
|
|
80
|
+
await this.triggerSave(tab, { type: 'selection', text: info.selectionText });
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
console.error('Context menu error:', error);
|
|
85
|
+
this.showErrorNotification(error.message);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async triggerSave(tab, data) {
|
|
90
|
+
// Send message to content script to initiate save
|
|
91
|
+
try {
|
|
92
|
+
const response = await chrome.tabs.sendMessage(tab.id, {
|
|
93
|
+
action: 'triggerSave',
|
|
94
|
+
data: data
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
if (response && response.success) {
|
|
98
|
+
this.showSuccessNotification('Content saved to Airtable!');
|
|
99
|
+
} else {
|
|
100
|
+
throw new Error(response?.error || 'Failed to save content');
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('Save trigger error:', error);
|
|
104
|
+
// Fallback: open popup if content script isn't available
|
|
105
|
+
chrome.action.openPopup();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async handleMessage(message, sender, sendResponse) {
|
|
110
|
+
try {
|
|
111
|
+
switch (message.action) {
|
|
112
|
+
case 'saveToAirtable':
|
|
113
|
+
const result = await this.saveToAirtable(message.data);
|
|
114
|
+
sendResponse({ success: true, result });
|
|
115
|
+
break;
|
|
116
|
+
|
|
117
|
+
case 'getSettings':
|
|
118
|
+
const settings = await this.getSettings();
|
|
119
|
+
sendResponse({ success: true, settings });
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case 'saveSettings':
|
|
123
|
+
await this.saveSettings(message.settings);
|
|
124
|
+
sendResponse({ success: true });
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case 'showNotification':
|
|
128
|
+
this.showNotification(message.title, message.message, message.type);
|
|
129
|
+
sendResponse({ success: true });
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 'openSidePanel':
|
|
133
|
+
if (sender.tab) {
|
|
134
|
+
chrome.sidePanel.open({ tabId: sender.tab.id });
|
|
135
|
+
}
|
|
136
|
+
sendResponse({ success: true });
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
default:
|
|
140
|
+
sendResponse({ success: false, error: 'Unknown action' });
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('Message handler error:', error);
|
|
144
|
+
sendResponse({ success: false, error: error.message });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async saveToAirtable(data) {
|
|
149
|
+
// Get user settings
|
|
150
|
+
const settings = await this.getSettings();
|
|
151
|
+
|
|
152
|
+
if (!settings.airtableToken || !settings.baseId) {
|
|
153
|
+
throw new Error('Airtable credentials not configured');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Import Airtable client
|
|
157
|
+
const { AirtableClient } = await import('./lib/airtable-client.js');
|
|
158
|
+
const client = new AirtableClient(settings.airtableToken, settings.baseId);
|
|
159
|
+
|
|
160
|
+
// Process and save data
|
|
161
|
+
const processedData = await this.processData(data, settings);
|
|
162
|
+
const result = await client.createRecord(processedData.table, processedData.fields);
|
|
163
|
+
|
|
164
|
+
// Update usage stats
|
|
165
|
+
await this.updateUsageStats();
|
|
166
|
+
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async processData(data, settings) {
|
|
171
|
+
// Smart data processing based on content type
|
|
172
|
+
if (data.type === 'linkedin') {
|
|
173
|
+
return {
|
|
174
|
+
table: settings.linkedinTable || 'Contacts',
|
|
175
|
+
fields: this.processLinkedInData(data)
|
|
176
|
+
};
|
|
177
|
+
} else if (data.type === 'selection') {
|
|
178
|
+
return {
|
|
179
|
+
table: settings.defaultTable || 'Clips',
|
|
180
|
+
fields: {
|
|
181
|
+
'Content': data.text,
|
|
182
|
+
'Source URL': data.url,
|
|
183
|
+
'Saved At': new Date().toISOString().split('T')[0],
|
|
184
|
+
'Type': 'Text Selection'
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
} else {
|
|
188
|
+
return {
|
|
189
|
+
table: settings.defaultTable || 'Clips',
|
|
190
|
+
fields: {
|
|
191
|
+
'Title': data.title || 'Untitled',
|
|
192
|
+
'URL': data.url,
|
|
193
|
+
'Content': data.content,
|
|
194
|
+
'Saved At': new Date().toISOString().split('T')[0],
|
|
195
|
+
'Type': 'Web Page'
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
processLinkedInData(data) {
|
|
202
|
+
return {
|
|
203
|
+
'Name': data.name || '',
|
|
204
|
+
'Title': data.title || '',
|
|
205
|
+
'Company': data.company || '',
|
|
206
|
+
'Location': data.location || '',
|
|
207
|
+
'LinkedIn URL': data.url || '',
|
|
208
|
+
'Date Added': new Date().toISOString().split('T')[0],
|
|
209
|
+
'Source': 'LinkedIn',
|
|
210
|
+
'Notes': data.about ? data.about.substring(0, 500) + '...' : ''
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async getSettings() {
|
|
215
|
+
const result = await chrome.storage.sync.get([
|
|
216
|
+
'airtableToken',
|
|
217
|
+
'baseId',
|
|
218
|
+
'linkedinTable',
|
|
219
|
+
'defaultTable',
|
|
220
|
+
'autoSave',
|
|
221
|
+
'notifications'
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
airtableToken: result.airtableToken || '',
|
|
226
|
+
baseId: result.baseId || '',
|
|
227
|
+
linkedinTable: result.linkedinTable || 'Contacts',
|
|
228
|
+
defaultTable: result.defaultTable || 'Clips',
|
|
229
|
+
autoSave: result.autoSave || false,
|
|
230
|
+
notifications: result.notifications !== false // Default to true
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async saveSettings(settings) {
|
|
235
|
+
await chrome.storage.sync.set(settings);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async updateUsageStats() {
|
|
239
|
+
const stats = await chrome.storage.local.get(['usageStats']);
|
|
240
|
+
const currentStats = stats.usageStats || {
|
|
241
|
+
totalClips: 0,
|
|
242
|
+
weeklyClips: 0,
|
|
243
|
+
lastResetDate: new Date().toDateString()
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// Reset weekly counter if it's a new week
|
|
247
|
+
const today = new Date().toDateString();
|
|
248
|
+
if (currentStats.lastResetDate !== today) {
|
|
249
|
+
const lastReset = new Date(currentStats.lastResetDate);
|
|
250
|
+
const daysSinceReset = Math.floor((Date.now() - lastReset.getTime()) / (1000 * 60 * 60 * 24));
|
|
251
|
+
|
|
252
|
+
if (daysSinceReset >= 7) {
|
|
253
|
+
currentStats.weeklyClips = 0;
|
|
254
|
+
currentStats.lastResetDate = today;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
currentStats.totalClips++;
|
|
259
|
+
currentStats.weeklyClips++;
|
|
260
|
+
|
|
261
|
+
await chrome.storage.local.set({ usageStats: currentStats });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
setupNotifications() {
|
|
265
|
+
// Check if notifications are supported
|
|
266
|
+
if ('notifications' in chrome) {
|
|
267
|
+
// Request notification permission if needed
|
|
268
|
+
chrome.notifications.getPermissionLevel((level) => {
|
|
269
|
+
if (level !== 'granted') {
|
|
270
|
+
console.log('Notification permission not granted');
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
showSuccessNotification(message) {
|
|
277
|
+
this.showNotification('Success!', message, 'success');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
showErrorNotification(message) {
|
|
281
|
+
this.showNotification('Error', message, 'error');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async showNotification(title, message, type = 'basic') {
|
|
285
|
+
const settings = await this.getSettings();
|
|
286
|
+
if (!settings.notifications) return;
|
|
287
|
+
|
|
288
|
+
const iconUrl = type === 'error' ? 'icons/icon-error-48.png' : 'icons/icon-48.png';
|
|
289
|
+
|
|
290
|
+
chrome.notifications.create({
|
|
291
|
+
type: 'basic',
|
|
292
|
+
iconUrl: iconUrl,
|
|
293
|
+
title: title,
|
|
294
|
+
message: message
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async handleActionClick(tab) {
|
|
299
|
+
// Check if this is a LinkedIn profile page
|
|
300
|
+
if (tab.url.includes('linkedin.com/in/')) {
|
|
301
|
+
// Try to extract LinkedIn data automatically
|
|
302
|
+
try {
|
|
303
|
+
await this.triggerSave(tab, { type: 'linkedin' });
|
|
304
|
+
} catch (error) {
|
|
305
|
+
// Fallback to opening popup
|
|
306
|
+
chrome.action.openPopup();
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
// Open popup for general pages
|
|
310
|
+
chrome.action.openPopup();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async cleanupStorage() {
|
|
315
|
+
// Clean up old temporary data
|
|
316
|
+
const result = await chrome.storage.local.get(null);
|
|
317
|
+
const keysToRemove = [];
|
|
318
|
+
|
|
319
|
+
for (const [key, value] of Object.entries(result)) {
|
|
320
|
+
// Remove temporary data older than 24 hours
|
|
321
|
+
if (key.startsWith('temp_') && value.timestamp) {
|
|
322
|
+
const age = Date.now() - value.timestamp;
|
|
323
|
+
if (age > 24 * 60 * 60 * 1000) { // 24 hours
|
|
324
|
+
keysToRemove.push(key);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (keysToRemove.length > 0) {
|
|
330
|
+
await chrome.storage.local.remove(keysToRemove);
|
|
331
|
+
console.log(`Cleaned up ${keysToRemove.length} temporary items`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Initialize the background service
|
|
337
|
+
new BackgroundService();
|