JavaScript: proper user of SJCL encryption in Google Apps Script

I’m using the minified version of SJCL to encrypt the backup files requested by the users. It is in a Google Apps Script add-on for Google Sheets.

I’m mostly concerned with correct use, exception handling and best practices of adding the encryption part in the script. It is currently working as expected in backup and in restore process.

The Backup (and Encryption)

User enter passphrase

It starts by asking the user to enter and re-enter the passphrase in a prompt. 1) One concern here is that the text field of the prompt doesn’t mask the passphrase. To fix that I would show a dialog with HTML and form (password input). But then I would need to work client-server call and, while that is not an issue, the prompt is much simpler, less complicated and it won’t be necessary to put a HTML – that could be questionable by the user?

const passphrase1 = ui.prompt(
  'Backup',
  'Enter passphrase:',
  ui.ButtonSet.OK_CANCEL);
if (passphrase1.getSelectedButton() === ui.Button.CANCEL) return 0;

const passphrase2 = ui.prompt(
  'Backup',
  'Please re-enter this passphrase:',
  ui.ButtonSet.OK_CANCEL);
if (passphrase2.getSelectedButton() === ui.Button.CANCEL) return 0;

Test passphrase

Here, the script tests if the passphrases match, and it also tests passphrase length and if it has at least upper, lower, numbers and special characters. It is a very simple test – so pA$$w0rd00 pass the test – and I actually didn’t want to test anything like GnuPG does. In this case, is nothing (no test, like GnuPG) better than this simple test?

const passphrase = passphrase1.getResponseText();
if (passphrase !== passphrase2.getResponseText() || passphrase.length < 10 || testPassphrasePolicy(passphrase)) {
  ui.alert(
    'Backup',
    'Invalid passphrase.',
    ui.ButtonSet.OK);
  return 1;
}

function testPassphrasePolicy (passphrase) {
  if (!/(a-z)+/.test(passphrase)) return 1;
  if (!/(A-Z)+/.test(passphrase)) return 1;
  if (!/(0-9)+/.test(passphrase)) return 1;
  if (!/(~!@#$%^*-_=+({)}/;:,.?)+/.test(passphrase)) return 1;

  return 0;
}

Encrypt data

The encryption part is pretty straight forward. The backupis a object like backup = { foe: 'abc', bar: 123 }. The steps are as follow:

  1. Stringify backup
  2. Encode to base64
  3. Compute SHA256
  4. Concatenate the base64 with : and the SHA256
  5. Encrypt with AES-128 in GCM mode and using the SHA256 as authentication data. If there is an error, simply return and show a generic message error.
  6. Test decryption. If there is an error, simply return and show a generic message error.
  7. Return a blob of the encrypted backup

I suspect steps 3 and 4 are overdoing, but before the addition of encryption that is how the script was doing an simple integrity test (with SHA1).

function encryptBackup_ (backup, passphrase) {
  const string = JSON.stringify(backup);
  const webSafeCode = Utilities.base64EncodeWebSafe(string, Utilities.Charset.UTF_8);

  const sha = computeDigest('SHA_256', webSafeCode, 'UTF_8');
  const data = webSafeCode + ':' + sha;

  let encrypted = '';
  try {
    encrypted = sjcl.encrypt(passphrase, data, { mode: "gcm", adata: sha });
  } catch (err) {
    ConsoleLog.error(err);
    return 0;
  }

  try {
    const decrypted = sjcl.decrypt(passphrase, encrypted);
    const parts = decrypted.split(':');
    const test_sha = computeDigest('SHA_256', parts(0), 'UTF_8');

    if (test_sha !== parts(1)) throw new Error('digestBackup_(): Bad decryption.');
  } catch (err) {
    ConsoleLog.error(err);
    return 0;
  }

  const date = Utilities.formatDate(DATE_NOW, 'GMT', 'yyyy-MM-dd-HH-mm-ss');
  const name = 'data' + date + '.backup';
  const blob = Utilities.newBlob(encrypted, 'application/octet-stream', name);

  return blob;
}

The Restore (and Decryption)

Once the user select a Drive file, the script requests the passphrase and tries to decrypt the file. This part is testing if it is possible to decrypt the file selected by the user.

The passphrase is being cached in a cache instance scoped to the current user and script so the script can retrieve it later to actually decrypt the file and restore the data. The cache expires in 120 seconds (session), and once it is retrieved later, it is immediately removed.

const ui = SpreadsheetApp.getUi();
const passphrase = ui.prompt(
  'Restore',
  'Enter passphrase:',
  ui.ButtonSet.OK_CANCEL);
if (passphrase.getSelectedButton() === ui.Button.CANCEL) return 0;

let decrypted = null;

try {
  decrypted = sjcl.decrypt(passphrase.getResponseText(), data);
} catch (err) {
  ConsoleLog.error(err);
  return 4;
}

const address = computeDigest(
  'SHA_1',
  file.getId() + SpreadsheetApp2.getActiveSpreadsheet().getId(),
  'UTF_8');
CacheService2.put('user', address, 'string', passphrase.getResponseText(), 120);

const parts = decrypted.split(':');
const test_sha = computeDigest('SHA_256', parts(0), 'UTF_8');

if (test_sha !== parts(1)) return 4;

const string = base64DecodeWebSafe(parts(0), 'UTF_8');
return JSON.parse(string);

javascript – Como identificar os estados de um google charts de mapa do Brasil para poder trocar a cor de fundo individualmente?

<div class="col-md-7 col-sm-12">
        <div id="geochart-colors">

        </div>
    </div>
    <div class="col-md-5 col-sm-12">
        <div class="map-info-contanier">
            <div class="search-box">
                <input placeholder="Buscar" onkeypress="pesquisarPrefeito()" id="busca" type="search">
                <div class="search-box-sections" id="busca-box" style="display: none;">
                    <div class="search-box-section-results" id="resultado">

                    </div>
                </div>
                <div class="campo">
                    <div class="titulo">Município:<br></div>
                    <label id="nome-municipio"></label>
                </div>
                <div class="campo">
                    <div class="titulo">Prefeito:<br></div>
                    <label id="nome-prefeito"></label>
                </div>
            </div>
            <div class="map-info">

            </div>
        </div>
    </div>
    
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

    <script>
        function preencherCampos(cidade, prefeito){
            var divResultado = document.getElementById('busca-box');
            divResultado.style.display = 'none';

            var nomeMunicipio = document.getElementById('nome-municipio');
            nomeMunicipio.innerText = cidade;

            var nomePrefeito = document.getElementById('nome-prefeito');
            nomePrefeito.innerText = prefeito;
        }

        function Get(link){
            var Httpreq = new XMLHttpRequest(); // a new request
            Httpreq.open("GET",link,false);
            Httpreq.send(null);
            return Httpreq.responseText;      
        }

        function pesquisarPrefeito() {
            var pesquisa = document.getElementById("busca").value;
            var link = "http://teste.teste.com.br/GetPrefeitos?filtro=" + pesquisa;

            console.log(Get(link));
            var cidades = JSON.parse(Get(link));
            var html = "";
            $(cidades).each(
                        function (i) {
                            console.log(cidades(i))
                            html += "<div onclick="preencherCampos('" + cidades(i).Cidade + " / " + cidades(i).Estado + "' , '" + cidades(i).NomePrefeito + "')"> " + cidades(i).Cidade + " / " + cidades(i).Estado + " </div> ";
                        }
                    );

                    $("#busca-box").html(html);

                    var divResultado = document.getElementById('busca-box');
                    divResultado.style.display = 'block';
        }

        google.load('visualization', '1', { 'packages': ('geochart') });
        google.setOnLoadCallback(drawVisualization);

        function drawVisualization() {
            var data = google.visualization.arrayToDataTable((
                ('State', 'City'),
                ('BR-AC', 'Acre'),
                ('BR-AL', 'Alagoas'),
                ('BR-AM', 'Amazonas'),
                ('BR-AP', 'Amapá'),
                ('BR-BA', 'Bahia'),
                ('BR-CE', 'Ceará'),
                ('BR-DF', 'Distrito Federal'),
                ('BR-ES', 'Espírito Santo'),
                ('BR-GO', 'Goiás'),
                ('BR-MA', 'Maranhão'),
                ('BR-MG', 'Minas Gerais'),
                ('BR-MS', 'Mato Grosso do Sul'),
                ('BR-MT', 'Mato Grosso'),
                ('BR-PA', 'Pará'),
                ('BR-PB', 'Paraíba'),
                ('BR-PE', 'Pernambuco'),
                ('BR-PI', 'Piauí'),
                ('BR-PR', 'Paraná'),
                ('BR-RJ', 'Rio de Janeiro'),
                ('BR-RN', 'Rio Grande do Norte'),
                ('BR-RO', 'Rondônia'),
                ('BR-RR', 'Roraima'),
                ('BR-RS', 'Rio Grande do Sul'),
                ('BR-SC', 'Santa Catarina'),
                ('BR-SE', 'Sergipe'),
                ('BR-SP', 'São Paulo'),
                ('BR-TO', 'Tocantins')
            ));

            var options = {
                region: 'BR',
                // displayMode: 'regions',
                resolution: 'provinces',
                datalessRegionColor: 'transparent',
                forceIFrame: false,
                colorAxis: '#f75192',
                backgroundColor: 'transparent',
                defaultColor: '#f75192',
                enableRegionInteractivity: true,
                tooltip: {
                    isHtml: true
                }
            };

            var chart = new google.visualization.GeoChart(document.getElementById('geochart-colors'));
            chart.draw(data, options);

        };

    </script>

javascript – Alternative for using video element on drawImage IOS Safari

Apparently Safari on IOS doesn’t support drawImage

https://developer.apple.com/documentation/webkitjs/canvasrenderingcontext2d/1630282-drawimage

I want to implement a blurred background for a video using the video element. If there is an alternative for imageDraw?, would there any alternative to implement the same feature? Or perhaps a different approach?

Here is what I have so far, it works on all browsers except Safari on IOS.

import { useLayoutEffect, useRef, useCallback } from 'react';

const useVideoBlurredOverlay = () => {
    const videoRef = useRef(null);
    const overlayRef = useRef(null);
    const requestAnimationRef = useRef(null);

    const animateOverlay = useCallback((video, canvasContext) => {
        const canvasClientWidth = canvasContext.canvas.clientWidth;
        const canvasClientHeight = canvasContext.canvas.clientHeight;

        // Bail when video is full width
        if (canvasClientWidth <= video.clientWidth)
            return false;

        // Prevent black flash
        if (video.currentTime === 0)
            return false;

        canvasContext.canvas.width = canvasClientWidth;
        canvasContext.canvas.height = canvasClientHeight;

        // Stretch
        canvasContext.drawImage(video, 0, 0, canvasClientWidth, canvasClientHeight);

        return null;
    }, ());

    const handleOverlay = useCallback((video, canvasContext) => {
        if (video.paused || video.ended)
            return false;

        animateOverlay(video, canvasContext);
        requestAnimationRef.current = requestAnimationFrame(() => handleOverlay(video, canvasContext));

        return null;
    }, ( animateOverlay ));

    useLayoutEffect(() => {
        const video = videoRef.current;
        const overlay = overlayRef.current;

        if (video && overlay) {
            const canvasContext = overlay.getContext('2d');
            video.addEventListener('play', () => handleOverlay(video, canvasContext));
        }

        return () => {
            video.removeEventListener('play', handleOverlay);
        };
    }, ( videoRef, overlayRef, handleOverlay ));

    return {
        videoRef,
        overlayRef
    };
};

export default useVideoBlurredOverlay;

javascript – Como crear Boton que muestre día de la semana, correspondiente a fecha seleccionada en Input Type Date

Hola necesito crear un boton que muestre el día de semana y que se corresponda con la fecha seleccionada a partir de un input type date, estoy usando este código pero no doy con la solución. Soy nueva en este mundo y comenzando en Java. No logro ni siquiera un botón que muestre la fecha. No se que estoy haciendo mal.

<html>
      <head>
          <script type="text/javascript">


function mostrardiasemana(){ 
var d=new Date(document.getElementById("fecha").value);
d.setDate(d.getDay() + 1);

var dia=new Array(7);
dia(0)="Domingo";
dia(1)="Lunes";
dia(2)="Martes";
dia(3)="Miércoles";
dia(4)="Jueves";
dia(5)="Viernes";
dia(6)="Sábado";
var n= dia(d.getDay());
document.getElementById("fecha").innerHTML=n;


alert("La fecha seleccionada en el elemento fecha es un:" + n)}

 
   </script>
</head>
      <body>
                <form>
<p> Fecha de la Encuesta </p>
                <input type="date" id="fecha" name="fecha" min="2019-01-01"
                  max="2021-12-31" value="2019-01-01"/> 
 <button class="botonera" value="Mostrar Dia Semana" onclick="mostrardiasemana"> Mostrar <br/> Día Semana </button>

</form>
       </body>
       </html>

javascript – Smooth sidebar toggle animation with vuejs and tailwind

I’m making a slide sidebar with vuejs and tailwind. It works but feels kind of sluggish. Is there a way to make it smoother ?

working example: https://codepen.io/tuturu1014/pen/oNzRXeW

<button @click="isOpen = !isOpen" class="bg-blue-200 p-5">
  <span v-if="isOpen">Open</span>
  <span v-else>Close</span>
</button>
<div class="flex flex-row max-w-7xl mx-auto min-h-screen">
  <transition name="slide">
    <div class="flex flex-col w-64  shadow-xl sm:rounded-lg bg-blue-200" v-if="isOpen">
      <div class="min-h-screen">sidebar</div>
    </div>
  </transition>

  <div class="flex w-full  min-h-screen bg-red-400">
    content
  </div>
</div>
<style>
  .slide-enter-active {
    animation: slideIn 1s ease;
  }
  .slide-leave-active {
    animation: slideIn 1s ease reverse;
  }
  @keyframes slideIn {
    0%   {max-width: 0%;}
    50%   {max-width: 50%;}
    100% {max-width: 100%}
  }
<style>

javascript – Abrir arquivo de formato específico no aplicativo que estou desenvolvendo — react native

Ola Devs!!
Estou com uma duvida aqui…
Sobre a possibilidade de configurar no aplicativo (que estou desenvolvendo) a opção de abrir um arquivo kml (arquivo de extensão).
Por exemplo, quando a pessoa clicar nesse arquivo de formato kml sendo através do whatsapp ou pelo gerenciado de arquivos do celular, daria a opção de abrir com o aplicativo.
Então a ideia seria configurar no aplicativo essa possibilidade de abrir o arquivo kml no aplicativo que estou desenvolvendo.

Stack de desenvolvimento: React Native + node.js

javascript – FizzBuzz solution – Code Review Stack Exchange

I’m preparing for junior developer interviews and am trying to come up with a more interesting/versatile solution to FizzBuzz than I’ve done in the past. Any ideas of how I might DRY this up? Is it too difficult to read?

const isMultiple = (num, mod) => {
    return num % mod == 0
}

const fizzBuzz = (range, array) => {
    return (...Array(range)).fill('').map((el,i) => {
    i ++
    for (let element of array) { 
        isMultiple(i,element.number) && (el += element.name) 
    }
        if (el == "") el = i
    return el
  });
}

const objArr = (
  {
    number: 3, 
    name: "Fizz"
  },
  {
    number: 5, 
    name: "Buzz"
  }
)

console.log(fizzBuzz(100,objArr))

javascript – Montar objeto JSON a partir de uma lista obedecendo schema

Preciso criar uma função que receba uma lista e um schema. Esta função deve transformar a lista em um objeto com o mesmo formato to schema passado.

Exemplo de lista:

const rows = [
    {
      "id": "f9d0d801-9645-4d09-b49b-b84b572fd918",
      "name": "Produto 1",
      "price": null,
      "attributes.id": "23d687b6-565f-469f-9aec-84558cfec692",
      "attributes.name": "tamanho",
      "attributes.type": "select",
      "attributes.multiple": false,
      "attributes.price": 0,
      "attributes.options.id": "345180c1-381d-495d-9645-2a075ca832e3",
      "attributes.options.text": "P",
      "attributes.options.price": 15
    },
    {
      "id": "f9d0d801-9645-4d09-b49b-b84b572fd918",
      "name": "Produto 1",
      "price": null,
      "attributes.id": "23d687b6-565f-469f-9aec-84558cfec692",
      "attributes.name": "tamanho",
      "attributes.type": "select",
      "attributes.multiple": false,
      "attributes.price": 0,
      "attributes.options.id": "15562510-2bbe-4140-808a-7527c14d8044",
      "attributes.options.text": "M",
      "attributes.options.price": 25
    },
    {
      "id": "f9d0d801-9645-4d09-b49b-b84b572fd918",
      "name": "Produto 1",
      "price": null,
      "attributes.id": "23d687b6-565f-469f-9aec-84558cfec692",
      "attributes.name": "tamanho",
      "attributes.type": "select",
      "attributes.multiple": false,
      "attributes.price": 0,
      "attributes.options.id": "9fda61e0-b4cc-49bc-a099-7abd0458615e",
      "attributes.options.text": "G",
      "attributes.options.price": 30
    },
    {
      "id": "f9d0d801-9645-4d09-b49b-b84b572fd918",
      "name": "Produto 1",
      "price": null,
      "attributes.id": "23d687b6-565f-469f-9aec-84558cfec692",
      "attributes.name": "tamanho",
      "attributes.type": "select",
      "attributes.multiple": false,
      "attributes.price": 0,
      "attributes.options.id": "b0c688db-db9d-42f4-91fe-f9a4c66cba4b",
      "attributes.options.text": "GG",
      "attributes.options.price": 35
    },
    {
      "id": "f9d0d801-9645-4d09-b49b-b84b572fd918",
      "name": "Produto 1",
      "price": null,
      "attributes.id": "464d23d6-55a2-4fca-a591-eeb8bbee5405",
      "attributes.name": "embrulhar",
      "attributes.type": "boolean",
      "attributes.multiple": false,
      "attributes.price": 2,
      "attributes.options.id": null,
      "attributes.options.text": null,
      "attributes.options.price": null
    }
  ]

Exemplo de schema:

export const ProductSchema = {
    id: 'string',
    name: 'string',
    price: 'number',
    attributes: [
        {
            id: 'string',
            name: 'string',
            type: 'string',
            multiple: 'boolean',
            price: 'number',
            options: [
                {
                    id: 'string',
                    text: 'string',
                    price: 'number'
                }
            ]
        }
    ]
}

O resultado esperado para esta lista e este schema é:

{
  "id": "f9d0d801-9645-4d09-b49b-b84b572fd918",
  "name": "Produto 1",
  "price": null,
  "attributes": [
    {
      "id": "23d687b6-565f-469f-9aec-84558cfec692",
      "name": "tamanho",
      "type": "select",
      "multiple": false,
      "price": 0,
      "options": [
        {
          "id": "345180c1-381d-495d-9645-2a075ca832e3",
          "text": "P",
          "price": 15
        },
        {
          "id": "15562510-2bbe-4140-808a-7527c14d8044",
          "text": "M",
          "price": 25
        },
        {
          "id": "9fda61e0-b4cc-49bc-a099-7abd0458615e",
          "text": "G",
          "price": 30
        },
        {
          "id": "b0c688db-db9d-42f4-91fe-f9a4c66cba4b",
          "text": "GG",
          "price": 35
        }
      ]
    },
    {
      "id": "464d23d6-55a2-4fca-a591-eeb8bbee5405",
      "name": "embrulhar",
      "type": "boolean",
      "multiple": false,
      "price": 2,
      "options": []
    }
  ]
}

Não encontrei nenhuma biblioteca no NPM que faça esse trabalho.

javascript – Cómo reiniciar una función para que no se acumulen los objetos creados

Estoy creando un link, y lo estoy agregando con appendChild al div con id box pero cada vez que ingreso un nombre este se va acumulando ¿Cómo hago para que no se acumule si no que al ingresar un nuevo nombre se ‘reinicie’ la function y sólo quede el recién creado? Aclaro que esto es un ejemplo de lo que busco, ya que tengo un app creada el cual es un buscador y todo lo que encuentra lo agrega con appendChild y sería imposible por tiempo, cambiar todo el código.

var text = document.getElementById("text");
var add = document.getElementById("add");

var box = document.getElementById("box");

add.onclick = function() {

var a = document.createElement("a");
a.href = "#";

a.innerText = text.value;

box.appendChild(a);

}
<input id="text" type="text" placeholder="Pon un nombre al link...">
<button id="add">Crear Link</button>


<div id="box"></div>

javascript – Why is my app failing in a conditional operator?

my code is

export default function useFetch(fetchFunction, param) {
  const cache = useContext(cacheContext);
  const (state, dispatch) = useReducer(fetchReducer, initialValue);
  useEffect(() => {
    if (cache.page(param)) {
      dispatch({ type: ACTIONS.success, payload: cache.page(param) });
      console.log("cache");
      return;
    }
                  // other code below
)

But my app is failing in the conditional operator. Im expecting that if cache.page(param) is empty, keep executing the code below. For example, if i got the param "0" i get the error "TypeError: Cannot read property '0' of null" and my app crashes