Étant donné que le système de sites Web devient de plus en plus grand, les cookies de différents noms de domaine et même de différents sites Web partenaires peuvent devoir être plus ou moins partagés. Lorsque l'on rencontre cette situation, tout le monde pense généralement à utiliser le centre de connexion pour distribuer le statut des cookies. puis synchronisez-la Solution, le coût est plus élevé et la mise en œuvre est plus compliquée et gênante.
Étant donné que les cookies sont inter-domaines, les navigateurs n'autorisent pas du tout l'accès mutuel. Afin de briser cette restriction, le plan de mise en œuvre suivant est utilisé pour partager des données entre domaines à l'aide du post-message et du stockage local.
Le principe est relativement simple, mais les pièges rencontrés sont nombreux. Voyons cela ici et faisons une sauvegarde.
2. Conception d'APIComme mentionné en arrière-plan, nous utilisons le stockage local au lieu des cookies. Il existe certaines différences d'utilisation entre le stockage local et les cookies. Par exemple, le stockage local a une plus grande capacité, mais il n'y a pas de délai d'expiration. Bien que la capacité soit grande, elle est disponible. différents navigateurs. La limite supérieure de l'espace facilite le crash si le fonctionnement n'est pas bon. De plus, bien que postmessage prenne en charge plusieurs domaines, les problèmes de sécurité et l'API asynchrone entraînent également des problèmes d'utilisation. plus facile à utiliser ?
Jetons un coup d'œil à l'API que j'ai conçue en premier :
import { crosData } from 'base-tools-crossDomainData';var store = new crosData({ iframeUrl:somefile.html, //Adresse iframe partagée, iframe a des exigences particulières, voir le fichier modèle expire :'d,h,s' / /Le délai d'expiration par défaut en jours, heures, secondes peut également être écrasé lors de la plantation});store.set('key','val',{ expire:'d,h,s' //option Peut apporter un délai d'expiration, remplacer expire}).then((data)=>{ //Méthode asynchrone, si elle échoue, elle entrera dans l'événement catch //data {val:'val',key:'key',domain :'domain'};}).catch((err)=>{ console.log(err);}); store.get('key',{ domain:'(.*).sina.cn' //Vous pouvez spécifier un nom de domaine ou utiliser (.*) pour faire correspondre les chaînes normales. Les informations val renvoyées incluront les informations sur le domaine. Si elles ne sont pas renseignées, elles renverront le domaine local }.then((vals) =>{ console.log (val) //Récupère les données stockées de manière asynchrone, il peut y en avoir plusieurs, c'est un tableau [{},{}]}).catch((err)=>{});store.clear ('clé').then(). //Effacer uniquement les clés du domaine actuel. Les clés des autres domaines ne peuvent pas être effacées. Elles peuvent uniquement être lues.La rapidité d'utilisation d'un module dépend de l'API, donc pour un module de partage de données, je pense qu'il est acceptable de prendre en charge les trois méthodes set, get et clear, car postmessage lui-même est un comportement asynchrone unique, et il doit être présenté dans une promesse Plus adaptée et plus facile à utiliser. Étant donné que le stockage local ne prend pas en charge le délai d'expiration, une configuration globale du délai d'expiration est requise. Bien entendu, il peut également être configuré individuellement lors de l'obtention, nous pouvons spécifier d'obtenir des données sous un certain domaine ou des données sous plusieurs domaines, car les noms de clés. peut être répété, mais il n'y a qu'un seul domaine. Cela implique la gestion des données. Parlons-en séparément plus tard. Enfin, les API claires et définies ne peuvent stocker des données que dans ce domaine et ne peuvent pas exploiter de données dans d'autres domaines.
Jetons un coup d'œil aux paramètres client et à l'API :
<!DOCTYPE html><html> <head> <meta charset=utf-8> <title>crosData</title> </head> <body> <script> window.CROS = { domain:/(.*). sina.cn/, //Ou le nom de domaine que vous autorisez, prend en charge les caractères génériques réguliers et * lz:false //S'il faut activer la compression lz des caractères val}; </script> <script src=http://cdn/sdk.js></script> </body></html>Vous pouvez introduire de manière flexible le SDK js du client dans un document HTML dans n'importe quel domaine, puis configurer une liste blanche de domaines qui vous permet d'être implanté dans le domaine où se trouve ce document via des attributs globaux. Il prend en charge les expressions régulières, puis lz indique si. Démarrez la compression lz-string. Je présenterai plus tard ce qu'est la compression lz.
À ce stade, une conception relativement générale de l’API est terminée. Examinons les principes de mise en œuvre et quelques problèmes spécifiques.
3. Principe de mise en œuvreCela semble très simple, mais ce n'est pas réellement écrit. Nous devons d'abord savoir comment utiliser postMessage. Il s'agit d'une API très courante. Il y a un point important à vous dire ici, c'est que postMessage ne peut être utilisé que dans une iframe. ou en utilisant une fenêtre. .open est un moyen d'ouvrir une nouvelle page pour communiquer entre eux. Bien sûr, ici, nous devons d'abord créer une iframe cachée pour plusieurs domaines.
Je suis trop paresseux pour utiliser des outils pour dessiner des images, car le processus est relativement clair. Ici, je vais raconter l'ensemble du processus de communication avec des mots. Tout d'abord, la page parent crée une iframe cachée, puis lorsque des commandes telles que set, get, clear sont créées. , etc. sont exécutés, le message est diffusé via postMessage. Une fois que la page a reçu le message, elle analyse la commande, les données et l'ID de rappel (postMessage ne peut pas transmettre les fonctions et les références en raison de problèmes de compatibilité. Il est préférable de transmettre uniquement le type de chaîne. , les données doivent donc être stringifiées). Ensuite, lorsque la page enfant termine l'opération de stockage local, elle renvoie le cbid et les données correspondants à la page parent via postMessage. La page parent écoute l'événement de message et traite les résultats.
4. EncodageBon, il n’y a que quelques lignes, commençons à coder :
Tout d’abord, présentons les packages tiers que nous utilisons et pourquoi nous les utilisons :
1. url-parse analyse l'URL, principalement en utilisant l'attribut origin, car postMessage lui-même a une vérification stricte de l'origine, et nous devons également prendre en charge la gestion de la liste blanche et des noms de domaine.
2. ms est une bibliothèque d'outils permettant de convertir l'abréviation de temps en millisecondes.
3. lz-string est une boîte à outils pour compresser des chaînes. Voici une introduction scientifique populaire à l'algorithme de compression LZ. Tout d'abord, pour comprendre LZ, vous devez comprendre RLZ, Run Length Encoding, qui est un algorithme très simple de compression sans perte. Il remplace les octets répétés par une simple description des octets répétés et du nombre de répétitions. L'idée derrière l'algorithme de compression LZ est d'utiliser l'algorithme RLE pour remplacer une occurrence précédente d'une référence à la même séquence d'octets. En termes simples, l'algorithme LZ est considéré comme un algorithme de correspondance de chaînes. Par exemple : une certaine chaîne apparaît fréquemment dans un morceau de texte et peut être représentée par un pointeur de chaîne qui apparaît dans le texte précédent.
L'avantage de lz-string lui-même est qu'il peut réduire considérablement votre capacité de stockage. Si le stockage local de 5 Mo est utilisé pour prendre en charge le stockage de données de plusieurs noms de domaine, il sera compressé et utilisé rapidement. Cependant, lz-string lui-même l'est. plus lent et consomme plus d'argent. Si vous avez des exigences de taille pour la quantité de données à transmettre au travail, vous pouvez essayer d'utiliser cet algorithme de compression pour optimiser la longueur de la chaîne.
4. L'API localstorage de store2 elle-même est relativement simple. Afin de réduire la complexité de la logique du code, une bibliothèque d'implémentation de stockage local populaire est sélectionnée pour effectuer les opérations de magasin.
Après avoir parlé du package tiers, voyons comment écrire le js de la page parent :
class crosData { constructor(options) { supportCheck(); this.options = Object.assign({ iframeUrl : '', expire : '30d' }, options this.cid = 0; this.cbs = {}; .iframeBeforeFuns = []; this.parent = window; this.origin = new url(this.options.iframeUrl).origin; this.createIframe(this.options.iframeUrl); addEvent(this.parent, 'message', (evt) => { var data = JSON.parse(evt.data); var origin = evt.origin || evt.originalEvent .origin; //Je ne reçois que le message de l'iframe que j'ai ouvert, les autres sont illégales, et une erreur sera signalée directement if (origin !== this.origin) { rejeter('origine illégale !'); return; } if (data.err) { this.cbs[data.cbid].reject(data.err); else { this.cbs[data.cbid].resolve(data) .ret); } delete this.cbs[data.cbid]; }); createIframe(url) { addEvent(document, 'domready', () => { var frame = document.createElement('iframe'); frame.style.cssText = 'largeur:1px;hauteur:1px;border:0;position:absolute;left:-9999px;top:-9999px;'; 'src', url); frame.onload = () => { this.child = frame.contentWindow; this.iframeBeforeFuns.forEach(item => item()); } document.body.appendChild(frame); }); } postHandle(type, args) { return new Promise((resolve, rejet) => { var cbid = this.cid; var message = { cbid : cbid, origine : nouvelle url (emplacement.href).origin, action : type, args : args } this.child.postMessage(JSON.stringify(message), this.origin); this.cbs[cbid] = { résoudre, rejeter } this.cid++; } } send(type, args) { return new Promise(resolve) => { if (this.child) { return this.postHandle(type, args).then(resolve } else { var self = this; this.iframeBeforeFuns.push(function() { self.postHandle(type, args).then(resolve); }); } }) } set(key, val, options) { options = Object.assign({ expire : ms (this.options.expire) }, options); return this.send('set', [key, val, options] } get(key, options) { options = Object.assign({ domaine : new url(location.href).origin }, options); return this.send('get', [key, options] } clear(key) { return this.send('clear ', [clé]); }}Il n’existe probablement que quelques méthodes. Voici quelques points clés, permettez-moi d’en parler.
1. Les méthodes get, set et clear sont toutes uniformément appelées méthodes d'envoi, mais la partie options est complétée.
2. La méthode send renvoie un objet de promesse. Si l'iframe a été chargé avec succès, la méthode postHandle est directement appelée pour effectuer l'opération postMessage. Si l'iframe est toujours en cours de chargement, l'opération en cours est poussée vers le tableau iframeBeforeFuns, enveloppée par un. et attend la fin du chargement de l'iframe. Après l'appel unifié, la fonction est également encapsulée dans la méthode postHandle.
3. La méthode postHandle encapsule les données avant d'envoyer la demande et génère le cbid, l'origine, l'action et les arguments. L'objet cbs enregistre la résolution et le rejet sous chaque cbid et attend le retour du postMessage de la sous-page avant le traitement. Étant donné que postMessage ne peut pas conserver les références et ne peut pas transmettre de fonctions, cette méthode est choisie ici pour l'association.
4. Le constructeur est facile à comprendre.Lorsque cette classe est initialisée, nous définissons certains attributs d'options dont nous avons besoin, créons une iframe, puis écoutons l'événement de message et traitons le message renvoyé par la sous-page.
5. Dans l'événement de message de la page parent, nous devons vérifier que le message qui m'a été envoyé doit être la fenêtre iframe que j'ai ouverte, sinon une erreur sera signalée, puis la résolution et le rejet dans cbs seront exécutés selon le identifiant d'erreur dans les données.
6. Dans la méthode createIframe, le rappel dans iframe onload gère la méthode d'appel de mise en cache avant la création. Faites attention à l'utilisation de domready ici, car le SDK peut être exécuté avant que le corps ne soit analysé.
Voici le code de la partie enfant :
class iframe { set(key, val, options, origin) { //Vérifie la taille de la val, qui ne peut pas dépasser 20k val = val.toString(); valsize = sizeof(val, 'utf16'); //localStorage stocke les octets en utilisant le codage utf16 if (valsize > this.maxsize) { return { err : 'votre valeur de magasin : ' + valstr + ' size est ' + valsize + 'b, maxsize :' + this.maxsize + 'b , utilisez utf16' } } key = `${this.prefix}_${key}, ${new url(origin).origin}`; var data = { val : val, lasttime : Date.now(), expire : Date.now() + options.expire }; store.set(key, data); //S'il est supérieur au nombre maximum de stockage, supprimez la dernière mise à jour if (store.size() > this.storemax) { var keys = store.keys(); clés.sort( (a, b) => { var item1 = store.get(a), item2 = store.get(b); return item2.lasttime - item1.lasttime; }); ceci.storemax - store.size()); while (removesize) { store.remove(keys.pop()); removesize--; } } return { ret: data } } get(key, options) { var message = {}; clés = store.keys(); var regexp = new RegExp('^' + this.prefix + '_' + key + ',' + options.domain + '$'); keys.filter((key) => { return regexp.test(key); }).map((storeKey) => { var data = store.get(storeKey); data.key = key; data.domain = storeKey .split(',')[1]; if (data.expire < Date.now()) { store.remove(storeKey); return undefined; else { //Mise à jour lasttime; val : data.val, lasttime : Date.now(), expire : data.expire }); data.val = this.lz ? filter(item => { return !!item; //Filtre non défini }); return message; clear(key, origin) { store.remove(`${this.prefix}_${key},${origin}`); return {}; } clearOtherKey() { //Supprimer les clés illégales var clés = store.keys(); new RegExp('^' + this.prefix); keys.forEach(key => { if (!keyReg.test(key)) { store.remove(key); } }); } constructor(safeDomain, lz) { supportCheck(); this.safeDomain = safeDomain || /.*/; this.prefix = '_cros'; if (Object.prototype.toString.call(this. safeDomain) !== '[object RegExp]') { throw new Error('safeDomain doit être une expression rationnelle' } this.lz = lz; this.storemax = 100 ; this.maxsize = 20 * 1024 ; //byte addEvent(window, 'message', (evt) => { var data = JSON.parse(evt.data); var originHostName = new url( evt .origin).hostname ; var origin = evt.origin, action = data.action, cbid = data.cbid, args = data.args ; //Diffusion légale if (evt.origin === data.origin && this.safeDomain.test(originHostName)) { args.push(origin); var whiteAction = ['set', 'get', 'clear'] ; if (whiteAction.indexOf(action) > -1) { var message = this[action].apply(this, args); window.top.postMessage(JSON.stringify(message), origin); } } else { window.top.postMessage(JSON.stringify({ cbid: cbid, err: 'Domaine illégal' }), origin); ; }}Il n'y a pas beaucoup de code. Voici une brève introduction à l'utilisation et à la relation organisationnelle de chaque méthode :
1. Dans la partie constructeur, la classe ci-dessus vérifie également la prise en charge des fonctionnalités du navigateur, puis définit des attributs tels que la valeur du préfixe du magasin, le nombre maximum et la taille maximale de chaque clé. Ensuite, nous créons un canal de message et attendons que la page parent l'appelle.
2. Dans le message, nous vérifions l'origine de la diffusion, puis vérifions la méthode appelée, appelons les méthodes set, get et clear correspondantes, puis obtenons le résultat de l'exécution, lions cbid et enfin renvoyons la page parent postMessage.
3. clearOtherKey supprime certaines données de stockage illégales et ne conserve que les données conformes au format.
4. Dans la méthode set, la vérification de la taille et la compression lz sont effectuées sur chaque élément de données. Les données enregistrées incluent la valeur, la clé, l'heure d'expiration et l'heure de mise à jour (utilisée pour le calcul LRU).
5. Dans la méthode set, si le nombre de ls stockés dépasse la limite maximale, une opération de suppression est requise à ce moment-là. LRU est l'abréviation de Least Récemment utilisé, c'est-à-dire le moins récemment utilisé. Nous parcourons toutes les valeurs de clé, trions les valeurs de clé, passons la dernière fois, puis effectuons l'opération pop du tableau de clés pour obtenir les clés qui doivent être effacées à la fin de la pile, puis les supprimons une par une. .
6. Dans la méthode get, nous parcourons toutes les valeurs de clé, faisons correspondre la clé du domaine que nous devons obtenir, puis démontons la clé dans la valeur de retour (nous la stockons au format clé et domaine), car L'API nécessite le renvoi de plusieurs valeurs correspondantes. Nous effectuons un filtre final sur les données expirées, puis utilisons lz pour décompresser la valeur val afin de garantir que l'utilisateur obtient le résultat correct.
Ce qui précède est notre processus global de codage et d’examen de la mise en œuvre. Parlons des pièges rencontrés.
5. Quelques écueils rencontrésParce que seul le code principal est donné ci-dessus, ce n'est pas le code complet. Parce que la logique elle-même est relativement claire, elle peut être écrite en peu de temps. Parlons des pièges ci-dessous.
1. Calculez la valeur de stockage du stockage local.
Parce que nous savons tous qu'il existe une limite de 5 Mo, la quantité maximale requise pour chaque élément de données ne peut pas dépasser 20*1024 octets. Pour le calcul des octets, le stockage local doit utiliser le codage utf16 pour la conversion. Veuillez vous référer à cet article : Calcul des caractères JS. Occupé par le nombre de sections de Strings
2. Compatibilité
Il est préférable de transmettre des chaînes dans postMessage sous IE8. Les événements doivent être lissés et JSON doit être lissé.
3. Traitement asynchrone lors de la création d'une iframe
Ici, nous avons précédemment effectué une attente récursive pour setTimeout, mais nous l'avons ensuite modifié pour la méthode d'implémentation ci-dessus. Après le chargement, la nouvelle promesse est traitée uniformément pour assurer l'unification de l'API de promesse.
4. Lors de la sauvegarde des données, complexité spatiale par rapport à la complexité temporelle.
La première version n'est pas l'implémentation ci-dessus, j'ai implémenté 3 versions :
La première version enregistre un tableau LRU afin de réduire la complexité temporelle, mais elle gaspille la complexité spatiale. De plus, après les tests, la méthode get du magasin prend relativement du temps, principalement en raison du temps d'analyse.
Dans la deuxième version, afin de maximiser le taux de compression de lz-string, j'ai enregistré toutes les données, y compris le tableau LRU, dans une valeur clé. Par conséquent, lz-string et getItem, la consommation de temps d'analyse est très importante lorsqu'il y en a. beaucoup de données. Bien que le calcul La complexité temporelle soit la plus faible.
La dernière version est celle ci-dessus. J'ai sacrifié une certaine complexité temporelle et spatiale, mais comme le goulot d'étranglement réside dans la vitesse de lecture et d'écriture de set et get, la vitesse de lecture et d'écriture d'une seule sauvegarde est extrêmement rapide. Les clés sont parce que la couche inférieure est utilisée pour le stockage local, les performances sont toujours très bonnes. 100 entrées peuvent être stockées dans 20 Ko, et le temps de lecture et d'écriture est d'environ 1 seconde.
6. Résumé et comparaisonAprès avoir écrit le module, j'ai réalisé qu'il existait une telle bibliothèque : zendesk/cross-storage
Mais j'ai vérifié son API et son code source et comparé les méthodes d'implémentation, je pense que ma version est plus importante.
1. Ma version contrôle le nom de domaine et la gestion des données.
2. Ma version de l'API promise est plus simplifiée, avec un onConnect de moins qu'elle. Vous pouvez vous référer à son implémentation C'est bien plus que ce que j'ai écrit, et cela ne résout pas le problème de l'iframe en attente asynchrone.
3. Les données compressées LZ ne sont pas prises en charge.
4. La gestion du pool de stockage LRU n'est pas prise en charge. Il se peut donc qu'il y ait trop de stockage, ce qui pourrait entraîner un échec d'écriture.
5. Il semble créer une iframe pour chaque interaction, ce qui est un gaspillage d'opérations et de diffusion DOM. Je pense qu'il n'y a aucun problème à la laisser activée. Bien sûr, il devra peut-être se connecter à plusieurs clients pour le gérer de cette façon. .
RésumerCe qui précède est l'introduction de l'éditeur au problème de l'utilisation du stockage local au lieu des cookies pour réaliser le partage de données entre domaines. J'espère que cela vous sera utile. Si vous avez des questions, veuillez me laisser un message et l'éditeur vous répondra. dans le temps. Je tiens également à remercier tout le monde pour votre soutien au site d'arts martiaux VeVb !