@inputdev/sdk 1.0.0 → 1.0.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.
@@ -0,0 +1,495 @@
1
+ /**
2
+ * InputDev SDK - Version Production Simplifiée
3
+ * Backend de formulaires sans code
4
+ *
5
+ * @version 1.0.0
6
+ * @author InputDev
7
+ */
8
+
9
+ (function(global) {
10
+ 'use strict';
11
+
12
+ // Configuration de production (non modifiable)
13
+ const CONFIG = {
14
+ apiEndpoint: 'https://server-g4xx.onrender.com/inputdev/submit',
15
+ timeout: 10000
16
+ };
17
+
18
+ // Codes d'erreur
19
+ const ERROR_CODES = {
20
+ API_001: 'Clé API invalide',
21
+ API_003: 'Clé API désactivée',
22
+ DOM_001: 'Domaine non autorisé',
23
+ QUO_001: 'Quota de soumissions épuisé',
24
+ STO_001: 'Quota de stockage épuisé',
25
+ SRV_001: 'Erreur serveur',
26
+ SRV_004: 'Trop de requêtes'
27
+ };
28
+
29
+ /**
30
+ * SDK InputDev - Version simplifiée
31
+ */
32
+ class InputDevSDK {
33
+ constructor() {
34
+ this.messagesEnabled = true;
35
+ this.logsEnabled = true;
36
+ this.init();
37
+ }
38
+
39
+ init() {
40
+ // Log de démarrage pour vérifier que le SDK est bien chargé
41
+ if (this.logsEnabled) {
42
+ console.log('%c🚀 INPUTDEV SDK CHARGÉ', 'color: #8b5cf6; font-weight: bold; font-size: 12px;');
43
+ console.log('⏱️ Timeout:', CONFIG.timeout + 'ms');
44
+ }
45
+
46
+ // Masquer les logs XHR du navigateur pour réduire le bruit
47
+ this.hideXHRLogs();
48
+
49
+ this.setupAutoFormHandling();
50
+ }
51
+
52
+ hideXHRLogs() {
53
+ // Masquer les logs XHR/Fetch du navigateur dans la console
54
+ const originalFetch = window.fetch;
55
+ window.fetch = function(...args) {
56
+ const result = originalFetch.apply(this, args);
57
+
58
+ // Masquer les logs de fetch dans la console et les erreurs non critiques
59
+ result.catch(() => {});
60
+
61
+ return result;
62
+ };
63
+
64
+ // Masquer les erreurs de favicon et autres erreurs non critiques
65
+ const originalError = console.error;
66
+ console.error = function(...args) {
67
+ // Filtrer les erreurs de favicon et autres erreurs non critiques
68
+ const message = args[0];
69
+ if (typeof message === 'string' && (
70
+ message.includes('favicon.ico') ||
71
+ message.includes('404') && message.includes('File not found')
72
+ )) {
73
+ return; // Ignorer ces erreurs
74
+ }
75
+ return originalError.apply(console, args);
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Désactive/active les messages
81
+ */
82
+ disableMessages() {
83
+ this.messagesEnabled = false;
84
+ }
85
+
86
+ enableMessages() {
87
+ this.messagesEnabled = true;
88
+ }
89
+
90
+ /**
91
+ * Désactive/active les logs
92
+ */
93
+ disableLogs() {
94
+ this.logsEnabled = false;
95
+ }
96
+
97
+ enableLogs() {
98
+ this.logsEnabled = true;
99
+ }
100
+
101
+ setupAutoFormHandling() {
102
+ document.addEventListener('DOMContentLoaded', () => {
103
+ const forms = document.querySelectorAll('form[data-inputdev], form[data-inputdev-silence]');
104
+
105
+ forms.forEach(form => {
106
+ // Ajouter la validation automatique des emails
107
+ this.setupEmailValidation(form);
108
+
109
+ form.addEventListener('submit', (e) => {
110
+ e.preventDefault();
111
+
112
+ // Vérifier si c'est un formulaire silencieux
113
+ const isSilent = form.hasAttribute('data-inputdev-silence');
114
+ const apiKey = isSilent ? form.getAttribute('data-inputdev-silence') : form.getAttribute('data-inputdev');
115
+
116
+ // Convertir FormData en objet
117
+ const data = {};
118
+ for (let [key, value] of new FormData(form).entries()) {
119
+ data[key] = value;
120
+ }
121
+
122
+ const submitButton = form.querySelector('button[type="submit"], input[type="submit"]');
123
+ if (submitButton) {
124
+ submitButton.disabled = true;
125
+ const originalText = submitButton.textContent || submitButton.value;
126
+ submitButton.textContent = 'Envoi en cours...';
127
+
128
+ setTimeout(() => {
129
+ submitButton.disabled = false;
130
+ submitButton.textContent = originalText;
131
+ }, 3000);
132
+ }
133
+
134
+ this.submit(apiKey, isSilent ? 'silence' : undefined, data)
135
+ .then(response => {
136
+ this.handleSuccess(form, response);
137
+ })
138
+ .catch(error => {
139
+ this.handleError(form, error);
140
+ });
141
+ });
142
+ });
143
+ });
144
+ }
145
+
146
+ setupEmailValidation(form) {
147
+ const emailInputs = form.querySelectorAll('input[type="email"]');
148
+
149
+ emailInputs.forEach(input => {
150
+ // Ajouter un écouteur pour la validation en temps réel
151
+ input.addEventListener('blur', () => {
152
+ this.validateEmail(input);
153
+ });
154
+
155
+ input.addEventListener('input', () => {
156
+ // Retirer le style d'erreur quand l'utilisateur tape
157
+ if (input.classList.contains('inputdev-email-error')) {
158
+ input.classList.remove('inputdev-email-error');
159
+ this.removeEmailError(input);
160
+ }
161
+ });
162
+ });
163
+ }
164
+
165
+ validateEmail(emailInput) {
166
+ const email = emailInput.value.trim();
167
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
168
+
169
+ if (!email) {
170
+ // Champ vide - pas d'erreur
171
+ this.removeEmailError(emailInput);
172
+ return true;
173
+ }
174
+
175
+ if (!emailRegex.test(email)) {
176
+ // Email invalide
177
+ this.showEmailError(emailInput);
178
+ return false;
179
+ }
180
+
181
+ // Email valide
182
+ this.removeEmailError(emailInput);
183
+ return true;
184
+ }
185
+
186
+ showEmailError(emailInput) {
187
+ emailInput.classList.add('inputdev-email-error');
188
+
189
+ // Ajouter un style CSS si pas déjà présent
190
+ if (!document.getElementById('inputdev-email-styles')) {
191
+ const style = document.createElement('style');
192
+ style.id = 'inputdev-email-styles';
193
+ style.textContent = `
194
+ .inputdev-email-error {
195
+ border: 2px solid #ef4444 !important;
196
+ box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1) !important;
197
+ outline: none !important;
198
+ }
199
+ .inputdev-email-error-message {
200
+ color: #ef4444;
201
+ font-size: 12px;
202
+ margin-top: 4px;
203
+ display: block;
204
+ }
205
+ `;
206
+ document.head.appendChild(style);
207
+ }
208
+
209
+ // Retirer l'ancien message d'erreur s'il existe
210
+ this.removeEmailError(emailInput);
211
+
212
+ // Ajouter le message d'erreur
213
+ const errorDiv = document.createElement('div');
214
+ errorDiv.className = 'inputdev-email-error-message';
215
+ errorDiv.innerHTML = '<span style="text-decoration: underline; font-weight: bold;">InputDev SDK</span> : <span style="font-weight: bold;">Veuillez entrer une adresse email valide</span>';
216
+ errorDiv.setAttribute('data-inputdev-error-for', emailInput.id || emailInput.name);
217
+
218
+ // Insérer après l'input
219
+ emailInput.parentNode.insertBefore(errorDiv, emailInput.nextSibling);
220
+ }
221
+
222
+ removeEmailError(emailInput) {
223
+ emailInput.classList.remove('inputdev-email-error');
224
+
225
+ // Retirer le message d'erreur
226
+ const errorMessage = emailInput.parentNode.querySelector(`[data-inputdev-error-for="${emailInput.id || emailInput.name}"]`);
227
+ if (errorMessage) {
228
+ errorMessage.remove();
229
+ }
230
+ }
231
+
232
+ validateEmailsInData(data) {
233
+ const errors = [];
234
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
235
+
236
+ // Parcourir toutes les propriétés de l'objet data
237
+ for (const [key, value] of Object.entries(data)) {
238
+ // Vérifier si le nom de la clé contient "email" (insensible à la casse)
239
+ if (key.toLowerCase().includes('email') && typeof value === 'string') {
240
+ const email = value.trim();
241
+ if (email && !emailRegex.test(email)) {
242
+ errors.push(`L'email "${key}" est invalide: ${email}`);
243
+ }
244
+ }
245
+ }
246
+
247
+ return {
248
+ valid: errors.length === 0,
249
+ errors: errors
250
+ };
251
+ }
252
+
253
+ logEmailValidationError(errors) {
254
+ // Afficher les erreurs de validation email dans la console
255
+ console.warn('%c📧 VALIDATION EMAIL', 'color: #f97316; font-weight: bold; font-size: 14px;');
256
+ console.warn('%cErreurs trouvées:', 'color: #f97316; font-weight: bold; font-size: 12px;');
257
+ errors.forEach((error, index) => {
258
+ console.warn('%c• ' + error, 'color: #f97316; font-size: 11px;');
259
+ });
260
+ console.warn('%c💡 Corrigez les emails avant de soumettre à nouveau', 'color: #6b7280; font-size: 11px;');
261
+ }
262
+
263
+ async submit(apiKey, silence, data) {
264
+ if (!apiKey || typeof apiKey !== 'string') {
265
+ throw new Error('Clé API invalide');
266
+ }
267
+
268
+ // Gérer le paramètre silence
269
+ if (typeof silence === 'object') {
270
+ // Cas: submit(apiKey, data) - ancienne signature
271
+ data = silence;
272
+ silence = undefined;
273
+ }
274
+
275
+ if (!data || typeof data !== 'object') {
276
+ throw new Error('Données invalides');
277
+ }
278
+
279
+ // Validation des emails dans les données JSON
280
+ const emailValidation = this.validateEmailsInData(data);
281
+ if (!emailValidation.valid) {
282
+ // Afficher l'erreur dans la console
283
+ this.logEmailValidationError(emailValidation.errors);
284
+ throw new Error('EMAIL_INVALID: ' + emailValidation.errors.join(', '));
285
+ }
286
+
287
+ // Désactiver temporairement les logs si mode silencieux
288
+ const originalLogsEnabled = this.logsEnabled;
289
+ if (silence === 'silence') {
290
+ this.logsEnabled = false;
291
+ }
292
+
293
+ const url = `${CONFIG.apiEndpoint}/${apiKey}`;
294
+
295
+ // Log automatique de la soumission
296
+ this.logInfo('📤 Envoi du formulaire...', {
297
+ apiKey: '***masqué***',
298
+ data
299
+ });
300
+
301
+ try {
302
+ const controller = new AbortController();
303
+ const timeoutId = setTimeout(() => controller.abort(), CONFIG.timeout);
304
+
305
+ const response = await fetch(url, {
306
+ method: 'POST',
307
+ headers: {
308
+ 'Content-Type': 'application/json',
309
+ },
310
+ body: JSON.stringify({ data }),
311
+ signal: controller.signal
312
+ });
313
+
314
+ clearTimeout(timeoutId);
315
+ const result = await response.json();
316
+
317
+ // Log automatique de la réponse brute du backend
318
+ this.logInfo('📨 Réponse du backend:', {
319
+ status: response.status,
320
+ statusText: response.statusText,
321
+ result: result
322
+ });
323
+
324
+ if (!response.ok) {
325
+ const errorCode = result.error?.code || 'SRV_001';
326
+ const errorMessage = result.error?.message || ERROR_CODES[errorCode] || 'Erreur inconnue';
327
+
328
+ // Log détaillé de l'erreur
329
+ this.logError(`❌ Erreur ${errorCode}`, {
330
+ code: errorCode,
331
+ message: errorMessage,
332
+ status: response.status,
333
+ fullResponse: result
334
+ });
335
+
336
+ // Message spécial pour l'erreur de domaine
337
+ if (errorCode === 'DOM_001') {
338
+ this.logDomainError();
339
+ }
340
+
341
+ // Message spécial pour l'erreur de clé désactivée
342
+ if (errorCode === 'API_003') {
343
+ this.logApiKeyDisabledError();
344
+ }
345
+
346
+ throw new Error(`${errorCode}: ${errorMessage}`);
347
+ }
348
+
349
+ // Log de succès avec détails
350
+ this.logSuccess('✅ Formulaire envoyé avec succès !', {
351
+ submissionId: result.submissionId,
352
+ message: result.message,
353
+ response: result
354
+ });
355
+
356
+ return result;
357
+ } catch (error) {
358
+ // Log automatique de l'erreur
359
+ this.logError('💥 Erreur lors de la soumission:', {
360
+ error: error.message,
361
+ stack: error.stack,
362
+ apiKey: '***masqué***'
363
+ });
364
+
365
+ // Message spécial pour l'erreur de domaine
366
+ if (this.extractErrorCode(error.message) === 'DOM_001') {
367
+ this.logDomainError();
368
+ }
369
+
370
+ // Message spécial pour l'erreur de clé désactivée
371
+ if (this.extractErrorCode(error.message) === 'API_003') {
372
+ this.logApiKeyDisabledError();
373
+ }
374
+
375
+ throw error;
376
+ } finally {
377
+ // Restaurer l'état des logs
378
+ this.logsEnabled = originalLogsEnabled;
379
+ }
380
+ }
381
+
382
+ handleSuccess(form, response) {
383
+ form.reset();
384
+
385
+ // Log automatique du succès
386
+ this.logSuccess('🎉 Formulaire traité avec succès !', {
387
+ form: form.tagName + (form.id ? '#' + form.id : ''),
388
+ submissionId: response.submissionId,
389
+ message: response.message,
390
+ timestamp: new Date().toISOString(),
391
+ response: response
392
+ });
393
+
394
+ const event = new CustomEvent('inputdev:success', {
395
+ detail: { form, response }
396
+ });
397
+ document.dispatchEvent(event);
398
+ }
399
+
400
+ handleError(form, error) {
401
+ // Message spécial pour l'erreur de domaine
402
+ if (this.extractErrorCode(error.message) === 'DOM_001') {
403
+ this.logDomainError();
404
+ }
405
+
406
+ // Message spécial pour l'erreur de clé désactivée
407
+ if (this.extractErrorCode(error.message) === 'API_003') {
408
+ this.logApiKeyDisabledError();
409
+ }
410
+
411
+ const event = new CustomEvent('inputdev:error', {
412
+ detail: { form, error }
413
+ });
414
+ document.dispatchEvent(event);
415
+ }
416
+
417
+ logSuccess(message, data) {
418
+ // Forcer l'affichage avec console.log direct - texte en vert sans contour
419
+ if (this.logsEnabled) {
420
+ console.log('%c🟢 INPUTDEV SUCCESS: ' + message, 'color: #10b981; font-weight: bold; font-size: 12px;');
421
+ console.log('📊 Détails:', data);
422
+ }
423
+ }
424
+
425
+ logError(message, error) {
426
+ // Forcer l'affichage avec console.error direct - texte en rouge sans contour
427
+ if (this.logsEnabled) {
428
+ console.error('%c🔴 INPUTDEV ERROR: ' + message, 'color: #ef4444; font-weight: bold; font-size: 12px;');
429
+ console.error('📊 Détails:', error);
430
+ }
431
+ }
432
+
433
+ logInfo(message, data) {
434
+ // Forcer l'affichage avec console.info direct - texte en bleu sans contour
435
+ if (this.logsEnabled) {
436
+ console.info('%c🔵 INPUTDEV INFO: ' + message, 'color: #3b82f6; font-weight: bold; font-size: 12px;');
437
+ console.info('📊 Détails:', data);
438
+ }
439
+ }
440
+
441
+ logDomainError() {
442
+ // Message spécial pour l'erreur de domaine DOM_001
443
+ if (this.logsEnabled) {
444
+ console.warn('%c⚠️ DOMAINE NON AUTORISÉ (DOM_001)', 'color: #f97316; font-weight: bold; font-size: 14px;');
445
+ console.warn('%c🔧 SOLUTIONS DISPONIBLES:', 'color: #f97316; font-weight: bold; font-size: 12px;');
446
+ console.warn('%c📍 LOCALHOST: Activez le mode TEST pour votre clé API', 'color: #f97316; font-size: 11px;');
447
+ console.warn('%c🌐 PRODUCTION: Seul le domaine configuré est accepté', 'color: #f97316; font-size: 11px;');
448
+ }
449
+ }
450
+
451
+ logApiKeyDisabledError() {
452
+ // Message spécial pour l'erreur de clé désactivée API_003
453
+ if (this.logsEnabled) {
454
+ console.warn('%c🔑 CLÉ API DÉSACTIVÉE (API_003)', 'color: #f97316; font-weight: bold; font-size: 14px;');
455
+ console.warn('%c🔧 ACTION REQUISE:', 'color: #f97316; font-weight: bold; font-size: 12px;');
456
+ console.warn('%c👤 Veuillez vous rendre dans votre compte et activer la clé', 'color: #f97316; font-size: 11px;');
457
+ console.warn('%c⚠️ La clé a été désactivée manuellement ou automatiquement', 'color: #f97316; font-size: 11px;');
458
+ }
459
+ }
460
+
461
+ extractErrorCode(errorMessage) {
462
+ const match = errorMessage.match(/^([A-Z_0-9]+):/);
463
+ return match ? match[1] : 'UNKNOWN';
464
+ }
465
+
466
+ getErrorMessage(errorMessage) {
467
+ const errorCode = this.extractErrorCode(errorMessage);
468
+ const cleanMessage = errorMessage.replace(/^[A-Z_0-9]+:\s*/, '');
469
+ return ERROR_CODES[errorCode] || cleanMessage;
470
+ }
471
+
472
+ // Méthodes pour compatibilité (ne font rien)
473
+ updateConfig() {
474
+ this.logError('⚠️ La configuration n\'est pas modifiable en production');
475
+ }
476
+
477
+ getConfig() {
478
+ return { ...CONFIG };
479
+ }
480
+ }
481
+
482
+ // Créer l'instance globale
483
+ const inputDevSDK = new InputDevSDK();
484
+
485
+ // Exporter
486
+ if (typeof module !== 'undefined' && module.exports) {
487
+ module.exports = { dev: inputDevSDK, InputDevSDK };
488
+ } else if (typeof define === 'function' && define.amd) {
489
+ define(function() { return { dev: inputDevSDK, InputDevSDK }; });
490
+ } else {
491
+ global.dev = inputDevSDK;
492
+ global.InputDevSDK = InputDevSDK;
493
+ }
494
+
495
+ })(typeof window !== 'undefined' ? window : this);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@inputdev/sdk",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Backend de formulaires sans code - SDK JavaScript",
5
- "main": "inputdev-sdk.js",
5
+ "main": "inputdev-sdk.prod.js",
6
6
  "types": "inputdev-sdk.d.ts",
7
7
  "scripts": {
8
8
  "build": "webpack --mode=production",
@@ -33,14 +33,13 @@
33
33
  },
34
34
  "homepage": "https://inputdev.dev",
35
35
  "files": [
36
- "inputdev-sdk.js",
37
- "inputdev-sdk.min.js",
36
+ "inputdev-sdk.prod.js",
38
37
  "inputdev-sdk.d.ts",
39
38
  "README.md",
40
39
  "LICENSE"
41
40
  ],
42
- "browser": "inputdev-sdk.js",
43
- "module": "inputdev-sdk.js",
44
- "unpkg": "inputdev-sdk.min.js",
45
- "jsdelivr": "inputdev-sdk.min.js"
41
+ "browser": "inputdev-sdk.prod.js",
42
+ "module": "inputdev-sdk.prod.js",
43
+ "unpkg": "inputdev-sdk.prod.js",
44
+ "jsdelivr": "inputdev-sdk.prod.js"
46
45
  }