{"id":28764,"date":"2021-08-14T18:29:00","date_gmt":"2021-08-14T16:29:00","guid":{"rendered":"https:\/\/pi3g.com\/?p=28764"},"modified":"2021-08-14T18:29:00","modified_gmt":"2021-08-14T16:29:00","slug":"synchronizing-file-uploads-between-browser-windows","status":"publish","type":"post","link":"https:\/\/pi3g.com\/de\/synchronizing-file-uploads-between-browser-windows\/","title":{"rendered":"Synchronisieren von Dateiuploads zwischen Browserfenstern"},"content":{"rendered":"<p>For <a href=\"https:\/\/picockpit.com\/\">PiCockpit<\/a>, I am currently developing the Digital Nose app.<\/p>\n<p>This app requires an upload for the BSEC configuration file \u2013 which is binary data. <\/p>\n<p>Once the user uploads the file, it gets published via MQTT, and picockpit-client can use it to configure the BSEC AI algorithm to do gas detection.<\/p>\n<p>Here is a little preview of part of the webinterface:<\/p>\n<p><a href=\"https:\/\/pi3g.com\/wp-content\/uploads\/2021\/08\/image.png\"><img loading=\"lazy\" decoding=\"async\" width=\"636\" height=\"275\" title=\"image\" style=\"display: inline; background-image: none;\" alt=\"image\" src=\"https:\/\/pi3g.com\/wp-content\/uploads\/2021\/08\/image_thumb.png\" border=\"0\"><\/a><\/p>\n<p>Vuetify ships with a built-in GUI element, v-file-input:<\/p>\n<blockquote>\n<p>&lt;v-file-input<br \/>&nbsp;&nbsp;&nbsp;&nbsp; v-model=&#8221;bsecFile&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; label=&#8221;BSEC Configuration File from AI Studio (.config)&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; placeholder=&#8221;Upload your BSEC configuration file here&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; prepend-icon=&#8221;mdi-paperclip&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; outlined<br \/>&nbsp;&nbsp;&nbsp;&nbsp; dense<br \/>&nbsp;&nbsp;&nbsp;&nbsp; show-size<br \/>&nbsp;&nbsp;&nbsp;&nbsp; accept=&#8221;.config&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; @change=&#8221;doUpload&#8221;<br \/>\n&gt;<br \/>\n&lt;\/v-file-input&gt;<\/p>\n<p><\/p>\n<\/blockquote>\n<p>In this case, <strong>bsecFile <\/strong>is my model. <\/p>\n<p>Note: Since it is a model, it can also be written to!<\/p>\n<h2>Publishing on MQTT<\/h2>\n<p>This is how the handler code doUpload looks like (still including debug info):<\/p>\n<blockquote>\n<p>&nbsp;&nbsp;&nbsp; doUpload(){<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;model:&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(this.bsecFile);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (this.bsecFile){<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ the code below does not work reliably, although it should in theory<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ if (this.bsecFile.name.split(&#8220;.&#8221;)[1] != &#8220;config&#8221;){<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;The filename does not include .config &#8211; resetting.&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/&nbsp;&nbsp;&nbsp;&nbsp; this.bsecFile = null;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ else {<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;the variable seems to be OK, trying to publish&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var reader = new FileReader();<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reader.addEventListener(&#8216;load&#8217;, (event) =&gt; {<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;File read successfully! publishing (now with Uint8Array).&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ fn = filename, fs = file size, fd = file (binary) data<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const msg = {<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fn: this.bsecFile.name,<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fs: this.bsecFile.size,<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fd: new Uint8Array (event.target.result)<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;message is:&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(msg);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.$emit(&#8220;bsec-upload-event&#8221;, true);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; myMqtt.sendWBasePath(sensorSetBsecPath, msg, 1, true);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reader.readAsArrayBuffer(this.bsecFile);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else {<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;the file was just cleared, so we should remove it &#8230; sending $nofile as true&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const msg = {<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8220;$nofile&#8221;: true <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;message is:&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(msg);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.$emit(&#8220;bsec-upload-event&#8221;, false);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; myMqtt.sendWBasePath(sensorSetBsecPath, msg, 1, true);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp; } <\/p>\n<p><\/p>\n<\/blockquote>\n<p>Note that I publish a message which includes binary data.<\/p>\n<p>I use a constructor for a new Uint8Array to which I pass event.target.result \u2013 an ArrayBuffer. <\/p>\n<p>This way, the data gets passed on precisely as it is. <\/p>\n<p>We use MsgPack to pack the JavaScript Object into a message \u2013 which supports binary data.<\/p>\n<p>On the other side, PiCockpit Client takes the binary message, and can reinstate a (in that case) Python object, including the binary data.<\/p>\n<h2>Making it work for synchronizing across browser windows<\/h2>\n<p>A second task is to synchronize the state across different browser windows and also sessions of the user. <\/p>\n<p>When the user returns, we would like to present the file as already having been uploaded to them. <\/p>\n<p>What I want to do is essentially to build a new File Object instance in JavaScript, from my Uint8Array to recreate the file from the MQTT message!<\/p>\n<p>We subscribe &amp; listen to the MQTT message, of course. And then we monitor the variable for changes, and modify the model bsecFile as follows:<\/p>\n<p><\/p>\n<blockquote>\n<p>globalBsecFileWrapped(val){<br \/>&nbsp;&nbsp;&nbsp;&nbsp; \/\/ this will ONLY be updated if we were not responsible for sending the message in the first place.<br \/>&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;watch &gt; globalBsecFileWrapped &gt; contents:&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp; console.log(this.globalBsecFileWrapped);<br \/>&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;we&#8217;re going to set this as a file now &#8230; &#8220;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp; if (this.globalBsecFileWrapped.hasOwnProperty(&#8220;$nofile&#8221;)){<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (this.globalBsecFileWrapped[&#8216;$nofile&#8217;]){<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ update it to be null &#8211; it was reset from a different browser.<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.bsecFile = null;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp; else if (this.globalBsecFileWrapped.hasOwnProperty(&#8220;fd&#8221;) &amp;&amp; this.globalBsecFileWrapped.hasOwnProperty(&#8220;fn&#8221;)) {<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ attempt 2<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ this.bsecFile = new File(this.globalBsecFileWrapped.fd, this.globalBsecFileWrapped.fn);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.bsecFile = new Blob([this.globalBsecFileWrapped.fd], {type: &#8220;application\/octet-stream&#8221;});<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; this.bsecFile.name = this.globalBsecFileWrapped.fn;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ debug<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var reader = new FileReader();<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reader.addEventListener(&#8216;load&#8217;, (event) =&gt; {<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;Debug &gt;&gt; File read successfully! publishing (now with Uint8Array).&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; \/\/ fn = filename, fs = file size, fd = file (binary) data<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const msg = {<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fn: this.bsecFile.name,<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fs: this.bsecFile.size,<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fd: new Uint8Array (event.target.result)<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(&#8220;message is:&#8221;);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; console.log(msg);<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; });<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; reader.readAsArrayBuffer(this.bsecFile);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br \/>&nbsp;&nbsp;&nbsp;&nbsp; }<br \/>\n}<\/p>\n<\/blockquote>\n<p>note the important parts here:<\/p>\n<blockquote>\n<p>this.bsecFile = new Blob([this.globalBsecFileWrapped.fd], {type: &#8220;application\/octet-stream&#8221;});<br \/>\nthis.bsecFile.name = this.globalBsecFileWrapped.fn;\n<\/p>\n<\/blockquote>\n<p>So, in order to recreate the file (and have the vuetify v-file-input update to the correct file (showing size appropriately, and filename) we are constructing a new Blob. <\/p>\n<p><strong>Important: the Blob needs to have the type of \u201capplication\/octet-stream\u201d \u2013 otherwise it is going to be interpreted as something different and the filesize will not match anymore!<\/strong><\/p>\n<p>And finally, we set the new name \u2013 that\u2019s it!<\/p>\n<p><\/p>\n<h2>Looking to solve similar problems?<\/h2>\n<p><a href=\"https:\/\/pi3g.com\/kontakt\/\">We are available for hire<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>F\u00fcr PiCockpit entwickle ich derzeit die Digital Nose App. Diese App erfordert einen Upload f\u00fcr die BSEC-Konfigurationsdatei, bei der es sich um Bin\u00e4rdaten handelt. Sobald der Benutzer die Datei hochl\u00e4dt, wird sie \u00fcber MQTT ver\u00f6ffentlicht, und der PiCockpit-Client kann sie verwenden, um den BSEC-KI-Algorithmus f\u00fcr die Gaserkennung zu konfigurieren. Hier ist eine kleine...<\/p>","protected":false},"author":830,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[822],"tags":[873,973,974,599,824,975,976,739],"class_list":["post-28764","post","type-post","status-publish","format-standard","hentry","category-javascript","tag-blob","tag-file","tag-file-upload","tag-mqtt","tag-msgpack","tag-sync-between-messages","tag-v-file-input-usage-example","tag-vuetify"],"_links":{"self":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/28764","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/users\/830"}],"replies":[{"embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/comments?post=28764"}],"version-history":[{"count":1,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/28764\/revisions"}],"predecessor-version":[{"id":28765,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/28764\/revisions\/28765"}],"wp:attachment":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/media?parent=28764"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/categories?post=28764"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/tags?post=28764"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}