mfm-widget 1.0.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/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # MockForMe Widget - TypeScript Integration
2
+
3
+ βœ… **TypeScript support has been successfully integrated!**
4
+
5
+ ## πŸ“¦ What's Included
6
+
7
+ ### Type Definitions (`mfm/types.ts`)
8
+ - **Message Types**: All postMessage contracts with full type safety
9
+ - **HTTP Types**: `HttpMethod`, `HttpRequest`, `HttpResponse`
10
+ - **Request Tracking**: `RequestRecord`, `InterceptionType`
11
+ - **Mappings**: `ApiMapping`, `Rule`, `MappingsPayload`
12
+ - **Type Guards**: `isMFMMessage()`, `isMessageType()`
13
+ - **Helper Functions**: `createMessage()`, `sendMessage()`
14
+
15
+ ### Type-Safe Events (`mfm/events.ts`)
16
+ - **Sending Messages**: Type-safe functions for all widget→consumer messages
17
+ - **Receiving Messages**: Strongly-typed event listeners
18
+ - **Convenience Functions**: `onMappingsResponse()`, `onRequest()`, `onResponse()`
19
+ - **Unsubscribe Support**: All listeners return cleanup functions
20
+
21
+ ## πŸš€ Quick Start
22
+
23
+ ### 1. Build the Widget
24
+
25
+ ```bash
26
+ npm install
27
+ npm run build
28
+ ```
29
+
30
+ This generates:
31
+ - `dist/mfm-widget.iife.js` - Widget bundle for `<script>` tags
32
+ - `dist/mfm-widget.js` - ES module bundle
33
+ - Type definitions are embedded in the source files
34
+
35
+ ### 2. Include in Your Project
36
+
37
+ #### Option A: Script Tag (Pure JS)
38
+ ```html
39
+ <script src="./dist/mfm-widget.iife.js"></script>
40
+ <script>
41
+ MFM.initMFM();
42
+ </script>
43
+ ```
44
+
45
+ #### Option B: ES Module (TypeScript/Modern JS)
46
+ ```typescript
47
+ import MFM from './dist/mfm-widget.js';
48
+ MFM.initMFM();
49
+ ```
50
+
51
+ ### 3. Use Type-Safe Messaging
52
+
53
+ ```typescript
54
+ import {
55
+ onMessage,
56
+ MessageType,
57
+ type MappingsPayload
58
+ } from './mfm/events';
59
+
60
+ // Listen for mapping requests
61
+ onMessage(MessageType.MFM_REQ_MAPPINGS, () => {
62
+ const mappings: MappingsPayload = {
63
+ apis: [{ method: 'GET', url: '/api/users' }],
64
+ rules: []
65
+ };
66
+
67
+ window.postMessage({
68
+ type: MessageType.MFM_RES_MAPPINGS,
69
+ payload: mappings
70
+ }, '*');
71
+ });
72
+ ```
73
+
74
+ ## πŸ“š Documentation
75
+
76
+ - **[TYPESCRIPT.md](./TYPESCRIPT.md)** - Complete TypeScript integration guide
77
+ - **[examples/consumer-integration.ts](./examples/consumer-integration.ts)** - Working examples
78
+
79
+ ## 🎯 Key Features
80
+
81
+ ### βœ… Full Type Safety
82
+ ```typescript
83
+ // TypeScript knows the exact payload type!
84
+ onMessage(MessageType.MFM_SUBMIT_TOKEN, (payload) => {
85
+ console.log(payload?.token); // βœ… Type-safe
86
+ });
87
+ ```
88
+
89
+ ### βœ… Auto-Complete
90
+ Your IDE will provide auto-complete for:
91
+ - All message types
92
+ - Payload structures
93
+ - Helper functions
94
+
95
+ ### βœ… Compile-Time Validation
96
+ ```typescript
97
+ // ❌ This will fail at compile time
98
+ window.postMessage({
99
+ type: MessageType.MFM_RES_MAPPINGS,
100
+ payload: { wrong: 'structure' } // Error: Type mismatch!
101
+ }, '*');
102
+ ```
103
+
104
+ ### βœ… Type Guards
105
+ ```typescript
106
+ if (isMFMMessage(event.data)) {
107
+ // TypeScript knows this is an MFMMessage
108
+ console.log(event.data.type);
109
+ }
110
+ ```
111
+
112
+ ## πŸ“‹ All Message Types
113
+
114
+ | Message Type | Direction | Payload Type | Description |
115
+ |--------------|-----------|--------------|-------------|
116
+ | `MFM_REQ_MAPPINGS` | Widget β†’ Consumer | - | Request mappings |
117
+ | `MFM_RES_MAPPINGS` | Consumer β†’ Widget | `MappingsPayload` | Send mappings |
118
+ | `MFM_REQ` | Consumer β†’ Widget | `RequestRecord` | Track pending request |
119
+ | `MFM_RES` | Consumer β†’ Widget | `RequestRecord` | Track completed request |
120
+ | `MFM_SUBMIT_TOKEN` | Widget β†’ Consumer | `{ token: string }` | Submit new token |
121
+ | `MFM_DEL_CLIENT_TOKEN` | Widget β†’ Consumer | - | Delete token |
122
+ | `MFM_TOGGLE_INTERCEPTION` | Widget β†’ Consumer | `{ enabled: boolean }` | Toggle interception |
123
+ | `MFM_LOG_INFO` | Widget β†’ Consumer | `any[]` | Info log |
124
+ | `MFM_LOG_WARN` | Widget β†’ Consumer | `any[]` | Warning log |
125
+ | `MFM_LOG_ERROR` | Widget β†’ Consumer | `any[]` | Error log |
126
+
127
+ ## πŸ”§ Development
128
+
129
+ ```bash
130
+ # Install dependencies
131
+ npm install
132
+
133
+ # Development mode (with hot reload)
134
+ npm run dev
135
+
136
+ # Build types only
137
+ npm run build:types
138
+
139
+ # Build everything (types + widget)
140
+ npm run build
141
+ ```
142
+
143
+ ## πŸ“– Example Usage
144
+
145
+ See the complete examples in:
146
+ - `examples/consumer-integration.ts` - Full integration examples
147
+ - `TYPESCRIPT.md` - Detailed documentation
148
+ - `test-head-init.html` - Working demo
149
+
150
+ ## 🎨 Benefits
151
+
152
+ 1. **Type Safety**: Catch errors at compile time, not runtime
153
+ 2. **Better IDE Support**: Auto-complete, inline documentation, refactoring
154
+ 3. **Self-Documenting**: Types serve as documentation
155
+ 4. **Easier Refactoring**: TypeScript helps you find all usages
156
+ 5. **Fewer Bugs**: Prevents common mistakes like typos in message types
157
+
158
+ ## 🀝 Contributing
159
+
160
+ When adding new message types:
161
+ 1. Add the type definition to `mfm/types.ts`
162
+ 2. Add the message interface
163
+ 3. Update the union types
164
+ 4. Add convenience functions to `mfm/events.ts`
165
+ 5. Update documentation
166
+
167
+ ## πŸ“ License
168
+
169
+ [Your License Here]
@@ -0,0 +1,2 @@
1
+ !function(){"use strict";try{if("undefined"!=typeof document){var r=document.createElement("style");r.appendChild(document.createTextNode(':root{--mfm-primary: #2563eb;--mfm-primary-hover: #1d4ed8;--mfm-bg: #ffffff;--mfm-surface: #f8fafc;--mfm-surface-bright: #f1f5f9;--mfm-text: #0f172a;--mfm-text-dim: #64748b;--mfm-border: #e2e8f0;--mfm-success: #10b981;--mfm-error: #ef4444;--mfm-warning: #f59e0b;--mfm-radius: 8px;--mfm-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--mfm-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--mfm-transition: all .15s ease-in-out}#mfm-root{position:fixed;bottom:24px;right:24px;z-index:999999;font-family:Inter,-apple-system,system-ui,sans-serif;color:var(--mfm-text);-webkit-user-select:none;user-select:none;box-sizing:border-box}#mfm-root *{box-sizing:border-box}.mfm-widget-toggle{display:flex;align-items:center;background:#fff;padding:6px;border-radius:9999px;box-shadow:var(--mfm-shadow-lg);border:1px solid var(--mfm-border);gap:2px;cursor:pointer;transition:var(--mfm-transition)}.mfm-widget-toggle.hidden{display:none!important}.mfm-toggle-section{padding:6px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:var(--mfm-transition)}.mfm-toggle-section:hover{background:var(--mfm-surface-bright)}.mfm-toggle-logo img{width:32px;height:32px}.mfm-toggle-divider{width:1px;height:24px;background:var(--mfm-border);margin:0 4px}.mfm-main-container{position:fixed;bottom:24px;right:24px;width:450px;height:650px;background:var(--mfm-bg);border:1px solid var(--mfm-border);border-radius:var(--mfm-radius);box-shadow:var(--mfm-shadow-lg);display:flex;flex-direction:column;overflow:hidden;transition:var(--mfm-transition);transform-origin:bottom right}.mfm-main-container.hidden{opacity:0;transform:scale(.95) translateY(20px);pointer-events:none}.mfm-header{padding:12px 16px;margin-bottom:5px;background:#fff;display:flex;align-items:center;justify-content:space-between}.mfm-logo img{height:25px;width:auto}.mfm-tabs-container{flex:1;display:flex;flex-direction:column;overflow:hidden}.mfm-tabs-nav{display:flex;padding:0 16px;background:#fff;border-bottom:1px solid var(--mfm-border);gap:4px}.mfm-tab-btn{padding:10px 16px;background:transparent;border:1px solid transparent;border-bottom:none;color:var(--mfm-text-dim);cursor:pointer;font-size:.9rem;font-weight:500;margin-bottom:-1px;border-top-left-radius:4px;border-top-right-radius:4px;transition:var(--mfm-transition)}.mfm-tab-btn:hover{color:var(--mfm-text);background:var(--mfm-surface)}.mfm-tab-btn.active{color:var(--mfm-primary);background:#fff;border-color:var(--mfm-border);border-bottom:1px solid white;font-weight:700}.mfm-tab-toolbar{padding:12px 16px;border-bottom:1px solid var(--mfm-border);display:flex;flex-direction:column;gap:8px}.mfm-toolbar-row{display:flex;gap:8px;align-items:center}.mfm-input{flex:1;height:36px;width:100%;padding:0 12px;border:1px solid var(--mfm-border);border-radius:6px;font-size:.875rem;outline:none}.mfm-input:focus{border-color:var(--mfm-primary);box-shadow:0 0 0 2px #2563eb1a}.mfm-btn-outline,.mfm-btn-icon{height:36px;padding:0 12px;background:transparent!important;border:none!important;border-radius:6px;font-size:.875rem;font-weight:500;cursor:pointer;color:var(--mfm-text-dim);transition:var(--mfm-transition);display:flex;align-items:center;justify-content:center}.mfm-btn-outline:hover,.mfm-btn-icon:hover{background:var(--mfm-surface-bright)!important;color:var(--mfm-text)}.mfm-preserve-logs{display:flex;align-items:center;gap:1px;font-size:.75rem;color:var(--mfm-text-dim);cursor:pointer}.mfm-tab-content{flex:1;overflow-y:auto;background:#fff}.mfm-list{display:flex;flex-direction:column;max-height:460px;overflow-y:scroll}.mfm-empty{padding:60px 20px;text-align:center;color:var(--mfm-text-dim);font-size:.875rem}.mfm-request-item{padding:12px 16px;border-bottom:1px solid var(--mfm-border);cursor:pointer;transition:var(--mfm-transition)}.mfm-request-item.active{background:#eff6ff;border-left:3px solid var(--mfm-primary)}.mfm-request-item:hover{background:var(--mfm-surface)}.mfm-request-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.mfm-method{font-size:.7rem;font-weight:700;padding:2px 6px;border-radius:4px;text-transform:uppercase}.mfm-method.get{color:#2563eb;background:#eff6ff}.mfm-method.post{color:#16a34a;background:#f0fdf4}.mfm-method.put{color:#d97706;background:#fffbeb}.mfm-method.delete{color:#dc2626;background:#fef2f2}.mfm-method.rule{color:#7c3aed;background:#f5f3ff}.mfm-status{font-size:.75rem;font-weight:600}.mfm-status.success{color:#16a34a}.mfm-status.error{color:#dc2626}.mfm-status.pending{color:#64748b}.mfm-url{font-size:.8125rem;color:var(--mfm-text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mfm-bottomsheet{position:absolute;top:60px;left:0;right:0;bottom:0;background:#fff;z-index:1100;display:flex;flex-direction:column;transform:translate(100%);transition:transform .25s ease-in-out}.mfm-bottomsheet.active{transform:translate(0)}.mfm-bottomsheet-header{padding:12px 16px;border-bottom:1px solid var(--mfm-border);display:flex;align-items:center;justify-content:space-between}.mfm-bottomsheet-content{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column}.mfm-detail-tabs{display:flex;border-bottom:1px solid var(--mfm-border);margin:16px -16px;padding:0 16px;gap:8px}.mfm-detail-tab-btn{padding:8px 12px;background:transparent;border:none;border-bottom:2px solid transparent;color:var(--mfm-text-dim);font-size:.8rem;font-weight:600;cursor:pointer;transition:var(--mfm-transition)}.mfm-detail-tab-btn:hover{color:var(--mfm-text)}.mfm-detail-tab-btn.active{color:var(--mfm-primary);border-bottom-color:var(--mfm-primary)}.mfm-detail-body{flex:1}.mfm-tab-content::-webkit-scrollbar,.mfm-bottomsheet-content::-webkit-scrollbar{width:8px}.mfm-tab-content::-webkit-scrollbar-track,.mfm-bottomsheet-content::-webkit-scrollbar-track{background:transparent}.mfm-tab-content::-webkit-scrollbar-thumb,.mfm-bottomsheet-content::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:4px;border:2px solid white}.mfm-tab-content::-webkit-scrollbar-thumb:hover,.mfm-bottomsheet-content::-webkit-scrollbar-thumb:hover{background:#94a3b8}.mfm-detail-section{margin-bottom:24px}.mfm-detail-title{font-size:.75rem;font-weight:600;text-transform:uppercase;color:var(--mfm-text-dim);margin-bottom:8px;display:flex;justify-content:space-between}.mfm-detail-content{background:var(--mfm-surface);padding:12px;border-radius:6px;font-family:JetBrains Mono,Menlo,monospace;font-size:.8rem;border:1px solid var(--mfm-border);word-break:break-all;max-height:300px;overflow-y:auto}.mfm-copy-btn{background:var(--mfm-primary);border:none;color:#fff;padding:4px 8px;border-radius:4px;font-size:.7rem;cursor:pointer;font-weight:600}.mfm-dropdown{position:relative}.mfm-dropdown-content{display:none;position:absolute;top:100%;right:0;background:#fff;min-width:140px;box-shadow:var(--mfm-shadow-lg);border:1px solid var(--mfm-border);border-radius:8px;z-index:1000;overflow:hidden}.mfm-dropdown-content.show{display:block}.mfm-dropdown-content button{width:100%;padding:10px 16px;text-align:left;background:transparent;border:none;font-size:.8125rem;color:var(--mfm-text);cursor:pointer}.mfm-dropdown-content button:hover{background:var(--mfm-surface)}.mfm-btn-primary{background:var(--mfm-primary);color:#fff;border:none;border-radius:8px;padding:12px 24px;font-weight:600;cursor:pointer;transition:var(--mfm-transition)}.mfm-btn-primary:hover{background:var(--mfm-primary-hover)}.mfm-header-actions{display:flex;align-items:center}.mfm-switch{position:relative;display:inline-block;width:32px;height:20px;margin-right:12px}.mfm-switch input{opacity:0;width:0;height:0}.mfm-slider{position:absolute;cursor:pointer;top:0;left:0;right:0;bottom:0;background-color:#cbd5e1;transition:.4s;border-radius:34px}.mfm-slider:before{position:absolute;content:"";height:16px;width:16px;left:2px;bottom:2px;background-color:#fff;transition:.4s;border-radius:50%}input:checked+.mfm-slider{background-color:var(--mfm-primary)}input:checked+.mfm-slider:before{transform:translate(12px)}.mfm-spinner{width:14px;height:14px;border:2px solid #cbd5e1;border-bottom-color:var(--mfm-primary);border-radius:50%;display:inline-block;box-sizing:border-box;animation:mfm-rotation 1s linear infinite}@keyframes mfm-rotation{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.mfm-request-item.pending-item{cursor:default;opacity:.8}.mfm-snackbar{visibility:hidden;min-width:250px;background-color:#333;color:#fff;text-align:center;border-radius:8px;padding:12px;position:fixed;z-index:2000;left:50%;bottom:30px;transform:translate(-50%);font-size:.9rem;box-shadow:0 4px 6px #0000001a;opacity:0;transition:opacity .3s,bottom .3s}.mfm-snackbar.show{visibility:visible;opacity:1;bottom:50px}.clear-btn svg{color:var(--mfm-text-dim)}')),document.head.appendChild(r)}}catch(t){console.error("vite-plugin-css-injected-by-js",t)}}();
2
+ var MFM=function(){"use strict";const e=JSON.parse(sessionStorage.getItem("MFM_SAVED_REQUESTS")||"[]"),t=JSON.parse(localStorage.getItem("MFM_CONFIG")||"{}"),s={state:{enabled:!0,isInterceptEnabled:"false"!==sessionStorage.getItem("MFM_IS_INTERCEPT_ENABLED"),isOpen:t.isOpen||!1,token:localStorage.getItem("MFM_TOKEN")||null,mappings:{apis:[],rules:[]},isMappingsLoading:!1,requests:e,ui:{selectedTab:"MOCKED",isDetailOpen:t.isDetailOpen||!1,selectedRequestId:t.selectedRequestId||null,detailTab:"HEADERS",tabConfigs:{MAPPINGS:{searchText:""},MOCKED:{searchText:"",isPreserveLogs:"true"===localStorage.getItem("MFM_PRESERVE_LOGS_MOCKED")},OTHER:{searchText:"",isPreserveLogs:"true"===localStorage.getItem("MFM_PRESERVE_LOGS_OTHER")}}}},listeners:[],subscribe(e){return this.listeners.push(e),()=>{this.listeners=this.listeners.filter(t=>t!==e)}},notify(){this.listeners.forEach(e=>e(this.state)),document.dispatchEvent(new CustomEvent("MFM_STATE_CHANGE",{detail:this.state})),localStorage.setItem("MFM_CONFIG",JSON.stringify({isOpen:this.state.isOpen,selectedRequestId:this.state.ui.selectedRequestId,isDetailOpen:this.state.ui.isDetailOpen}))},update(e,t){const s=e.split(".");let n=this.state;for(let i=0;i<s.length-1;i++)n=n[s[i]];n[s[s.length-1]]=t,this.notify()},set(e,t){this.state[e]=t,this.notify()}},n=new class{constructor(){this.level=3}setLevel(e){this.level=e}postLog(e,t){if(window&&window.postMessage){const s=t.map(e=>{try{return"object"==typeof e?e:String(e)}catch(t){return"[Unserializable]"}});window.postMessage({source:"mfm-widget-logger",type:e,payload:s},"*")}}info(...e){this.level>=1&&this.postLog("MFM_LOG_INFO",e)}warn(...e){this.level>=2&&this.postLog("MFM_LOG_WARN",e)}error(...e){this.level>=3&&this.postLog("MFM_LOG_ERROR",e)}},i={toggleWidget(){s.update("isOpen",!s.state.isOpen)},setTab(e){s.update("ui.selectedTab",e)},deleteToken(){n.warn("Deleting Client Token"),s.update("token",null),localStorage.removeItem("MFM_TOKEN"),window.postMessage({type:"MFM_DEL_CLIENT_TOKEN"},"*")},reloadMappings(){n.info("Reloading Mappings"),s.update("isMappingsLoading",!0),window.postMessage({type:"MFM_REQ_MAPPINGS"},"*")},toggleInterception(){const e=!s.state.isInterceptEnabled;n.info("Toggling Interception:",e),s.update("isInterceptEnabled",e),sessionStorage.setItem("MFM_IS_INTERCEPT_ENABLED",e),window.postMessage({type:"MFM_TOGGLE_INTERCEPTION",payload:{enabled:e}},"*")},addRequest(e){var t,n;let i=[...s.state.requests];const o=i.findIndex(t=>t.requestId===e.requestId);o>-1?i[o]={...i[o],...e}:i.push(e),i.length>100&&(i=i.slice(i.length-100)),s.set("requests",i);const a=null==(t=s.state.ui.tabConfigs.MOCKED)?void 0:t.isPreserveLogs,l=null==(n=s.state.ui.tabConfigs.OTHER)?void 0:n.isPreserveLogs,r=i.filter(e=>{const t="MOCKED"===e.interceptionType;return t&&a||!t&&l});r.length>0?sessionStorage.setItem("MFM_SAVED_REQUESTS",JSON.stringify(r)):sessionStorage.removeItem("MFM_SAVED_REQUESTS")},clearRequests(){const e=s.state.ui.selectedTab,t=s.state.requests.filter(t=>("MOCKED"===t.interceptionType?"MOCKED":"OTHER")!==e);s.set("requests",t),sessionStorage.setItem("MFM_SAVED_REQUESTS",JSON.stringify(t))},setPreserveLogs(e){var t,n;const i=s.state.ui.selectedTab;if("MAPPINGS"===i)return;s.update(`ui.tabConfigs.${i}.isPreserveLogs`,e),localStorage.setItem(`MFM_PRESERVE_LOGS_${i}`,e);const o=null==(t=s.state.ui.tabConfigs.MOCKED)?void 0:t.isPreserveLogs,a=null==(n=s.state.ui.tabConfigs.OTHER)?void 0:n.isPreserveLogs,l=s.state.requests.filter(e=>{const t="MOCKED"===e.interceptionType;return t&&o||!t&&a});l.length>0?sessionStorage.setItem("MFM_SAVED_REQUESTS",JSON.stringify(l)):sessionStorage.removeItem("MFM_SAVED_REQUESTS")},setSearchText(e){const t=s.state.ui.selectedTab;s.update(`ui.tabConfigs.${t}.searchText`,e)},selectRequest(e){s.state.ui.selectedRequestId=e,s.state.ui.isDetailOpen=!0,s.state.ui.detailTab="HEADERS",s.notify()},closeDetails(){s.state.ui.isDetailOpen=!1,s.state.ui.selectedRequestId=null,s.notify()},setDetailTab(e){s.update("ui.detailTab",e)}},o="https://www.mockforme.com/assets/images/logo.png",a='<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path><polyline points="21 3 21 8 16 8"></polyline></svg>';function l(e){n.info("Initializing MockForMe Widget");let t=null;const l=e=>{if(!e)return;t=document.createElement("div"),t.id="mfm-root";const n=document.createElement("div");n.className="mfm-widget-toggle",n.innerHTML=`\n <div class="mfm-toggle-section mfm-toggle-reload" title="Reload Mappings">\n ${a}\n </div>\n <div class="mfm-toggle-divider"></div>\n <div class="mfm-toggle-section mfm-toggle-logo" title="MockForMe">\n <img src="https://ik.imagekit.io/mfm/static-collection/mfm-48x48.png" alt="Logo">\n </div>\n `;const l=document.createElement("div");l.className="mfm-main-container hidden",t.appendChild(n),t.appendChild(l),e.appendChild(t),function(e){const t=document.createElement("div");t.className="mfm-header",t.innerHTML=`\n <div class="mfm-logo">\n <img src="${o}" height="32" alt="MockForMe Logo">\n </div>\n <div class="mfm-header-actions">\n <label class="mfm-switch" title="Toggle Mock Interception">\n <input type="checkbox" id="mfm-intercept-toggle" ${s.state.isInterceptEnabled?"checked":""}>\n <span class="mfm-slider"></span>\n </label>\n <button class="mfm-btn-icon" id="mfm-reload-header" title="Reload Mappings">\n \n<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-cw"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"></path><polyline points="21 3 21 8 16 8"></polyline></svg>\n\n </button>\n <button class="mfm-btn-icon" id="mfm-delete-token" title="Delete Token">\n \n<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-trash-2"><path d="M3 6h18"></path><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>\n\n </button>\n </div>\n `,e.appendChild(t);const n=t.querySelector("#mfm-intercept-toggle");n.onchange=()=>{i.toggleInterception()};const a=t.querySelector("#mfm-reload-header");a.onclick=()=>{a.innerHTML='<div class="mfm-spinner"></div>',a.style.pointerEvents="none",i.reloadMappings()},t.querySelector("#mfm-delete-token").onclick=i.deleteToken,s.subscribe(e=>{n.checked=e.isInterceptEnabled})}(l),function(e){const t=document.createElement("div");t.className="mfm-token-form",t.innerHTML=`\n <div style="padding: 40px 24px; text-align: center;">\n <img src="${o}" height="25" style="margin-bottom: 16px;">\n <h3 style="margin: 0 0 8px 0; font-size: 1.25rem;">Welcome to MockForMe</h3>\n <p style="color: var(--mfm-text-dim); font-size: 0.9rem; margin-bottom: 24px; line-height: 1.5;">\n Paste your token below to start mocking and intercepting API requests.\n </p>\n <input type="text" class="mfm-input" id="mfm-token-input" placeholder="Enter Client Token..." style="text-align: center; height: 44px; font-size: 1rem;">\n <button class="mfm-btn-primary" id="mfm-submit-token" style="margin-top: 16px; width: 100%; height: 44px;">\n Continue\n </button>\n </div>\n `,e.appendChild(t);const n=t.querySelector("#mfm-token-input");t.querySelector("#mfm-submit-token").onclick=()=>{const e=n.value.trim();e&&(s.update("token",e),localStorage.setItem("MFM_TOKEN",e),window.postMessage({type:"MFM_SUBMIT_TOKEN",payload:{token:e}},"*"))}}(l),function(e){const t=document.createElement("div");t.className="mfm-tabs-container";const n=document.createElement("div");n.className="mfm-tabs-nav",[{id:"MAPPINGS",label:"Mappings"},{id:"MOCKED",label:"Mocked"},{id:"OTHER",label:"Others"}].forEach(e=>{const t=document.createElement("button");t.className="mfm-tab-btn "+(s.state.ui.selectedTab===e.id?"active":""),t.innerText=e.label,t.onclick=()=>{i.setTab(e.id),d()},t.dataset.tabId=e.id,n.appendChild(t)});const o=document.createElement("div");o.className="mfm-tab-toolbar",o.innerHTML=`\n <div class="mfm-toolbar-row">\n <input type="text" class="mfm-input" id="mfm-search" placeholder="Search requests..." value="${s.state.ui.searchText}">\n <label class="mfm-preserve-logs">\n <input type="checkbox" id="mfm-preserve-check" ${s.state.ui.isPreserveLogs?"checked":""}>\n Preserve\n </label>\n <button class="mfm-btn-outline clear-btn" id="mfm-clear-btn" title="Clear requests">\n <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:block;"><circle cx="12" cy="12" r="10"></circle><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line></svg>\n </button>\n </div>\n `;const a=document.createElement("div");a.className="mfm-tab-content",t.appendChild(n),t.appendChild(o),t.appendChild(a),e.appendChild(t);const l=o.querySelector("#mfm-search"),r=o.querySelector("#mfm-clear-btn"),c=o.querySelector("#mfm-preserve-check");function d(){const e=s.state.ui.selectedTab,t=s.state.ui.tabConfigs[e];n.querySelectorAll(".mfm-tab-btn").forEach(t=>{t.classList.toggle("active",t.dataset.tabId===e)});const i=(null==t?void 0:t.searchText)||"";l.value!==i&&(l.value=i);const a="MAPPINGS"===e;r.style.display=a?"none":"block",o.querySelector(".mfm-preserve-logs").style.display=a?"none":"flex",a||(c.checked=!!(null==t?void 0:t.isPreserveLogs)),m()}function m(){const e=s.state.ui.selectedTab;if("MAPPINGS"===e&&s.state.isMappingsLoading)return void(a.innerHTML='<div class="mfm-empty"><div class="mfm-spinner"></div></div>');let t=[];"MAPPINGS"===e?t=function(){const{apis:e,rules:t}=s.state.mappings,n=(s.state.ui.tabConfigs.MAPPINGS.searchText||"").toLowerCase();return{apis:e.filter(e=>e.url.toLowerCase().includes(n)),rules:t.filter(e=>e.ruleName.toLowerCase().includes(n))}}():"MOCKED"===e?t=function(){const e=(s.state.ui.tabConfigs.MOCKED.searchText||"").toLowerCase();return s.state.requests.filter(t=>{var s,n;return"MOCKED"===t.interceptionType&&(t.request.url.toLowerCase().includes(e)||(null==(n=null==(s=t.rule)?void 0:s.ruleName)?void 0:n.toLowerCase().includes(e)))})}():"OTHER"===e&&(t=function(){const e=(s.state.ui.tabConfigs.OTHER.searchText||"").toLowerCase();return s.state.requests.filter(t=>"MOCKED"!==t.interceptionType&&t.request.url.toLowerCase().includes(e))}()),a.innerHTML=`<div class="mfm-list">${function(e,t){if("MAPPINGS"===e){const{apis:e,rules:s}=t;return 0===e.length&&0===s.length?'<div class="mfm-empty">No mappings found</div>':`\n ${s.map(e=>u({method:"RULE",url:e.ruleName,status:e.type,type:"rule"})).join("")}\n ${e.map(e=>u({method:e.method,url:e.url,status:"Active",type:"api"})).join("")}\n `}return 0===t.length?'<div class="mfm-empty">No requests found</div>':t.map(e=>{var t;return u({method:e.request.method,url:e.request.url,status:(null==(t=e.response)?void 0:t.statusCode)||"Pending",type:"request",id:e.requestId})}).join("")}(e,t)}</div>`}function u(e){const t="Pending"===e.status,n=t?"pending":e.status>=200&&e.status<300?"success":e.status>=400?"error":"",i=e.id&&String(e.id)===String(s.state.ui.selectedRequestId),o="request"===e.type;return`\n <div class="mfm-request-item ${i?"active":""} ${t?"pending-item":""}" data-id="${e.id}" data-type="${e.type}">\n <div class="mfm-request-header">\n <div class="mfm-flex-row">\n <span class="mfm-method ${e.method.toLowerCase()}">${e.method}</span>\n <span class="mfm-status ${n}">\n ${t?'<span class="mfm-spinner"></span>':e.status}\n </span>\n </div>\n ${o?'\n <div class="mfm-dropdown">\n <button class="mfm-btn-icon mfm-dots-btn">\n <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"></circle><circle cx="12" cy="5" r="1"></circle><circle cx="12" cy="19" r="1"></circle></svg>\n </button>\n <div class="mfm-dropdown-content">\n <button data-action="copy-curl">Copy as cURL</button>\n <button data-action="copy-fetch">Copy as Fetch</button>\n <button data-action="copy-xhr">Copy as XHR</button>\n </div>\n </div>':""}\n </div>\n <div class="mfm-url" title="${e.url}">${e.url}</div>\n </div>\n `}l.oninput=e=>{i.setSearchText(e.target.value),m()},r.onclick=()=>{i.clearRequests()},c.onchange=e=>{i.setPreserveLogs(e.target.checked)},a.onclick=e=>{const t=e.target.closest(".mfm-dots-btn");if(t){if(t.closest(".pending-item"))return;return e.stopPropagation(),void t.nextElementSibling.classList.toggle("show")}const n=e.target.closest("[data-action]");if(n)return e.stopPropagation(),function(e,t){const n=s.state.requests.find(e=>e.requestId===t);if(!n)return;let i="";"copy-curl"===e?i=`curl '${n.request.url}' -X ${n.request.method}`:"copy-fetch"===e?i=`fetch('${n.request.url}', { method: '${n.request.method}' })`:"copy-xhr"===e&&(i=`const xhr = new XMLHttpRequest(); xhr.open('${n.request.method}', '${n.request.url}'); xhr.send();`),navigator.clipboard.writeText(i)}(n.dataset.action,n.closest(".mfm-request-item").dataset.id),void n.parentElement.classList.remove("show");const o=e.target.closest(".mfm-request-item");if(!o)return;if(o.classList.contains("pending-item"))return;e.stopPropagation();const a=o.dataset.id;"request"===o.dataset.type&&i.selectRequest(a)},d(),s.subscribe(e=>{d()})}(l),function(e){const t=document.createElement("div");function n(){var e,n,o,a;const{isDetailOpen:l,selectedRequestId:r,detailTab:c}=s.state.ui;if(!l||!r)return void t.classList.remove("active");const d=s.state.requests.find(e=>String(e.requestId)===String(r));if(!d)return void i.closeDetails();t.querySelector(".mfm-bottomsheet-content").innerHTML=`\n <div class="mfm-detail-header">\n <div class="mfm-flex-row">\n <span class="mfm-method ${d.request.method.toLowerCase()}">${d.request.method}</span>\n <span style="font-weight:600">${(null==(e=d.response)?void 0:e.statusCode)||"Pending"}</span>\n </div>\n <div style="margin-top:8px; color:var(--mfm-text-dim); font-family:monospace; font-size:0.8rem; word-break:break-all;">${d.request.url}</div>\n </div>\n\n <div class="mfm-detail-tabs">\n <button class="mfm-detail-tab-btn ${"HEADERS"===c?"active":""}" data-tab="HEADERS">Headers</button>\n <button class="mfm-detail-tab-btn ${"REQ_BODY"===c?"active":""}" data-tab="REQ_BODY">Request</button>\n <button class="mfm-detail-tab-btn ${"RES_BODY"===c?"active":""}" data-tab="RES_BODY">Response</button>\n </div>\n\n <div class="mfm-detail-body">\n ${"HEADERS"===c?`\n <div class="mfm-detail-section">\n <div class="mfm-detail-title">Request Headers</div>\n <div class="mfm-detail-content">\n <pre style="margin:0">${JSON.stringify(d.request.headers||{},null,2)}</pre>\n </div>\n </div>\n <div class="mfm-detail-section" style="margin-top:16px">\n <div class="mfm-detail-title">Response Headers</div>\n <div class="mfm-detail-content">\n <pre style="margin:0">${JSON.stringify((null==(n=d.response)?void 0:n.headers)||{},null,2)}</pre>\n </div>\n </div>\n `:""}\n\n ${"REQ_BODY"===c?`\n <div class="mfm-detail-section">\n <div class="mfm-detail-title">Request Body</div>\n <div class="mfm-detail-content">\n <pre style="margin:0">${"string"==typeof d.request.body?d.request.body:JSON.stringify(d.request.body,null,2)}</pre>\n </div>\n </div>\n `:""}\n\n ${"RES_BODY"===c?`\n <div class="mfm-detail-section">\n <div class="mfm-detail-title">\n Response Body\n <button class="mfm-copy-btn" id="mfm-copy-res">Copy</button>\n </div>\n <div class="mfm-detail-content">\n <pre style="margin:0">${"string"==typeof(null==(o=d.response)?void 0:o.body)?d.response.body:JSON.stringify(null==(a=d.response)?void 0:a.body,null,2)}</pre>\n </div>\n </div>\n `:""}\n </div>\n `,t.querySelectorAll(".mfm-detail-tab-btn").forEach(e=>{e.onclick=()=>i.setDetailTab(e.dataset.tab)});const m=t.querySelector("#mfm-copy-res");m&&(m.onclick=()=>{var e;navigator.clipboard.writeText(JSON.stringify(null==(e=d.response)?void 0:e.body,null,2))}),t.classList.add("active")}t.className="mfm-bottomsheet",t.id="mfm-details-sheet",t.innerHTML='\n <div class="mfm-bottomsheet-header">\n <h3 style="margin:0; font-size: 1.1rem;">Request Details</h3>\n <button class="mfm-btn-icon" id="mfm-close-sheet">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>\n </button>\n </div>\n <div class="mfm-bottomsheet-content"></div>\n ',e.appendChild(t),t.querySelector("#mfm-close-sheet").onclick=()=>{i.closeDetails()},s.subscribe(n),n()}(l);const r=e=>{const t=!!e.token,s=l.querySelector(".mfm-header"),i=l.querySelector(".mfm-tabs-container"),o=l.querySelector(".mfm-token-form");s&&(s.style.display=t?"flex":"none"),i&&(i.style.display=t?"block":"none"),o&&(o.style.display=t?"none":"block"),l.classList.toggle("hidden",!e.isOpen),n.classList.toggle("hidden",e.isOpen)};n.querySelector(".mfm-toggle-reload").onclick=e=>{e.stopPropagation(),s.state.token?i.reloadMappings():s.update("isOpen",!0)},n.querySelector(".mfm-toggle-logo").onclick=e=>{e.stopPropagation(),s.state.token?i.toggleWidget():s.update("isOpen",!0)},window.addEventListener("click",e=>{const n=document.contains(e.target);s.state.isOpen&&n&&!t.contains(e.target)&&i.toggleWidget()}),window.addEventListener("click",e=>{e.target.closest(".mfm-dropdown")||document.querySelectorAll(".mfm-dropdown-content.show").forEach(e=>e.classList.remove("show"))}),s.subscribe(r),r(s.state)},r=()=>e||document.body;return r()?l(r()):"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>l(r())):document.body&&l(document.body),window.addEventListener("message",e=>{const{type:t,payload:o}=e.data;if(t&&t.startsWith("MFM_"))switch(t.startsWith("MFM_LOG_")||n.info(`Incoming PostMessage: ${t}`,o),t){case"MFM_RES_MAPPINGS":s.set("mappings",o),s.update("isMappingsLoading",!1);const e=document.querySelector("#mfm-reload-header");e&&(e.innerHTML=a,e.style.pointerEvents="auto"),function(e,t=3e3){if(!document.body)return;let s=document.getElementById("mfm-snackbar");s||(s=document.createElement("div"),s.id="mfm-snackbar",s.className="mfm-snackbar",document.body.appendChild(s)),s.textContent=e,s.className="mfm-snackbar show",setTimeout(()=>{s.className=s.className.replace("show","")},t)}("Mappings reloaded successfully");break;case"MFM_REQ":n.info("Captured Request",o),i.addRequest(o);break;case"MFM_RES":n.info("Captured Response",o),i.addRequest(o)}}),{destroy(){t&&t.remove()},actions:i}}const r={run:l,initMFM:l,actions:i};return window.MFM=r,r}();
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "mfm-widget",
3
+ "version": "1.0.0",
4
+ "description": "MFM Widget",
5
+ "type": "module",
6
+ "main": "./dist/mfm-widget.iife.js",
7
+ "module": "./dist/mfm-widget.iife.js",
8
+ "files": [
9
+ "dist/mfm-widget.iife.js"
10
+ ],
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/mfm-widget.iife.js",
14
+ "require": "./dist/mfm-widget.iife.js"
15
+ }
16
+ },
17
+ "scripts": {},
18
+ "keywords": [
19
+ "widget"
20
+ ],
21
+ "author": "",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": ""
26
+ },
27
+ "devDependencies": {}
28
+ }