@pigmilcom/a11y 1.0.9 → 1.1.1
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 +118 -18
- package/dist/a11y.cdn.js +29 -10
- package/dist/index.css +23 -4
- package/dist/index.js +115 -9
- package/dist/index.min.css +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.mjs +113 -8
- package/package.json +3 -2
- package/src/cdn.jsx +2 -2
package/README.md
CHANGED
|
@@ -61,6 +61,54 @@ PigmilA11y.unmount(); // remove the widget and clean up
|
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
64
|
+
## Plans & domain validation
|
|
65
|
+
|
|
66
|
+
Starting from v1.1.0, the widget validates the hosting domain against the **PIGMIL API** before rendering. This enables usage-based quotas and per-domain access control.
|
|
67
|
+
|
|
68
|
+
### Tiers
|
|
69
|
+
|
|
70
|
+
| Plan | Requests / day | Requests / month | PIGMIL branding |
|
|
71
|
+
| ------ | -------------- | ---------------- | --------------- |
|
|
72
|
+
| Free | 1,000 | 30,000 | Shown in footer |
|
|
73
|
+
| Pro | Unlimited | Unlimited | Hidden |
|
|
74
|
+
|
|
75
|
+
A "request" is one widget initialisation (page load / route change).
|
|
76
|
+
|
|
77
|
+
### How validation works
|
|
78
|
+
|
|
79
|
+
1. On widget mount the browser makes a single `GET` request to the PIGMIL API.
|
|
80
|
+
2. The API checks whether the domain is registered and within quota.
|
|
81
|
+
3. The response controls whether the widget renders:
|
|
82
|
+
- **`valid`** — widget renders normally.
|
|
83
|
+
- **`blocked`** — quota exceeded; widget does not render until the day / month resets or the plan is upgraded.
|
|
84
|
+
- **`error`** / API unreachable — widget does not render (fail-closed on non-local domains).
|
|
85
|
+
4. The result is cached in `sessionStorage` for 30 minutes to avoid redundant calls.
|
|
86
|
+
5. Cached and returned result objects are `Object.freeze()`d — their `status` and `plan` properties cannot be mutated after resolution.
|
|
87
|
+
|
|
88
|
+
### Local / development environments
|
|
89
|
+
|
|
90
|
+
Validation is **automatically skipped** when the page runs on:
|
|
91
|
+
- `localhost`, `127.x.x.x`, `::1`, `0.0.0.0`
|
|
92
|
+
- Any non-standard port (e.g. `:3000`, `:5173`, `:8080`)
|
|
93
|
+
|
|
94
|
+
The widget always renders in these environments with a free-plan appearance. No API key or sign-up is required during development.
|
|
95
|
+
|
|
96
|
+
### Security layers
|
|
97
|
+
|
|
98
|
+
| Layer | What it does |
|
|
99
|
+
|---|---|
|
|
100
|
+
| **Server-side enforcement** (primary) | The PIGMIL API only validates registered domains, increments per-domain counters, and returns `blocked:true` when quotas are exceeded. This is the real gate — no client-side bypass can defeat it. |
|
|
101
|
+
| **Frozen result objects** | `Object.freeze()` on the validation result prevents callers from mutating `{ status, plan }` to escalate their own access level. |
|
|
102
|
+
| **CDN bundle obfuscation** | `dist/a11y.cdn.js` is post-processed by `javascript-obfuscator` with Base64 string-array encoding and self-defending code. The self-defending wrapper causes the bundle to break at runtime if it has been reformatted / prettified before execution, deterring the "prettify → grep → patch → re-minify" bypass workflow. The npm package source remains intentionally readable (open source). |
|
|
103
|
+
| **XOR-encoded endpoint** | The API URL is stored as a numeric XOR array in source and decoded at runtime, making it non-trivial to locate in the minified + obfuscated CDN bundle. |
|
|
104
|
+
| **Daily-rotating request signature** | Each API call includes an FNV-1a hash of `domain + YYYY-MM-DD` — a per-domain-per-day token the server can optionally verify to detect replayed or forged requests. |
|
|
105
|
+
|
|
106
|
+
### Current status (mock mode)
|
|
107
|
+
|
|
108
|
+
The API endpoint is being implemented in the PIGMIL backend. Until it is live, `src/license.js` uses `MOCK_MODE = true`, which always returns `{ ok: true, plan: 'free', blocked: false }`. To switch to the real API, set `MOCK_MODE = false` in that file — the fetch code is already written and ready.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
64
112
|
## Features
|
|
65
113
|
|
|
66
114
|
- Text size — Normal / Large / X-Large
|
|
@@ -120,13 +168,71 @@ class rules applied to `<html>`.
|
|
|
120
168
|
### 2 — Drop in the widget
|
|
121
169
|
|
|
122
170
|
```jsx
|
|
123
|
-
import
|
|
171
|
+
import a11y from '@pigmilcom/a11y';
|
|
172
|
+
|
|
173
|
+
// Assign to a capitalized variable to use in JSX
|
|
174
|
+
const A11y = a11y;
|
|
175
|
+
|
|
176
|
+
<A11y className="fixed bottom-4 right-4 rounded" />
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The `className` prop is merged onto the trigger button — use Tailwind classes,
|
|
180
|
+
custom classes, or anything your bundler supports to position the widget.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Customising the widget with CSS
|
|
185
|
+
|
|
186
|
+
The widget exposes stable **`a11y-widget-*`** class names on its key elements
|
|
187
|
+
as a public API. Target them from a `<style>` tag (CDN / self-hosted) or your
|
|
188
|
+
own stylesheet (React):
|
|
189
|
+
|
|
190
|
+
```html
|
|
191
|
+
<style>
|
|
192
|
+
/* Trigger button */
|
|
193
|
+
.a11y-widget-btn {
|
|
194
|
+
border-radius: 8px !important;
|
|
195
|
+
background: rgba(15, 15, 40, 0.9) !important;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/* Dialog panel */
|
|
199
|
+
.a11y-widget-dialog {
|
|
200
|
+
border-color: rgba(99, 102, 241, 0.4) !important;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Toggle option rows */
|
|
204
|
+
.a11y-widget-toggle-btn:hover {
|
|
205
|
+
background: rgba(99, 102, 241, 0.06) !important;
|
|
206
|
+
}
|
|
207
|
+
</style>
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
| Public class | Element |
|
|
211
|
+
| --------------------------- | -------------------- |
|
|
212
|
+
| `a11y-widget-btn` | Trigger button |
|
|
213
|
+
| `a11y-widget-dialog` | Dialog panel |
|
|
214
|
+
| `a11y-widget-toggle-btn` | Each toggle option |
|
|
215
|
+
|
|
216
|
+
> Place your `<style>` tag **after** the widget `<script>` tag so it takes
|
|
217
|
+
> precedence in the cascade. Use `!important` where the widget's base styles
|
|
218
|
+
> have higher specificity.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Tailwind CSS support
|
|
223
|
+
|
|
224
|
+
Tailwind utility classes work transparently via the `className` prop:
|
|
225
|
+
|
|
226
|
+
```jsx
|
|
227
|
+
import a11y from '@pigmilcom/a11y';
|
|
228
|
+
const A11y = a11y;
|
|
124
229
|
|
|
125
|
-
|
|
230
|
+
// Tailwind classes are passed straight through to the trigger button
|
|
231
|
+
<A11y className="fixed bottom-6 right-6 rounded-full shadow-xl" />
|
|
126
232
|
```
|
|
127
233
|
|
|
128
|
-
|
|
129
|
-
|
|
234
|
+
Because the classes come from *your* source files, Tailwind's JIT scanner
|
|
235
|
+
picks them up automatically. No purge exceptions or safelist entries needed.
|
|
130
236
|
|
|
131
237
|
---
|
|
132
238
|
|
|
@@ -137,14 +243,16 @@ the widget wherever suits your layout.
|
|
|
137
243
|
```jsx
|
|
138
244
|
// app/layout.jsx
|
|
139
245
|
import '@pigmilcom/a11y/styles';
|
|
140
|
-
import
|
|
246
|
+
import a11y from '@pigmilcom/a11y';
|
|
247
|
+
|
|
248
|
+
const A11y = a11y;
|
|
141
249
|
|
|
142
250
|
export default function RootLayout({ children }) {
|
|
143
251
|
return (
|
|
144
252
|
<html lang="en">
|
|
145
253
|
<body>
|
|
146
254
|
{children}
|
|
147
|
-
<
|
|
255
|
+
<A11y className="fixed bottom-4 right-4 rounded" />
|
|
148
256
|
</body>
|
|
149
257
|
</html>
|
|
150
258
|
);
|
|
@@ -155,25 +263,17 @@ export default function RootLayout({ children }) {
|
|
|
155
263
|
|
|
156
264
|
### Vite + React
|
|
157
265
|
|
|
158
|
-
```jsx
|
|
159
|
-
// src/main.jsx
|
|
160
|
-
import '@pigmilcom/a11y/styles';
|
|
161
|
-
import React from 'react';
|
|
162
|
-
import ReactDOM from 'react-dom/client';
|
|
163
|
-
import App from './App';
|
|
164
|
-
|
|
165
|
-
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
|
166
|
-
```
|
|
167
|
-
|
|
168
266
|
```jsx
|
|
169
267
|
// src/App.jsx
|
|
170
|
-
import
|
|
268
|
+
import a11y from '@pigmilcom/a11y';
|
|
269
|
+
|
|
270
|
+
const A11y = a11y;
|
|
171
271
|
|
|
172
272
|
export default function App() {
|
|
173
273
|
return (
|
|
174
274
|
<>
|
|
175
275
|
{/* …your content… */}
|
|
176
|
-
<
|
|
276
|
+
<A11y className="fixed bottom-4 right-4 rounded" />
|
|
177
277
|
</>
|
|
178
278
|
);
|
|
179
279
|
}
|