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 +169 -0
- package/dist/mfm-widget.iife.js +2 -0
- package/package.json +28 -0
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
|
+
}
|