I can't figure out why these two pieces of code generate different signatures when using the same message and key as inputs. Does anyone know what I'm doing wrong?
I've tried putting \0 or \n at the end of either message.
I've tried using ASCII encoding instead of UTF-8 for Apps Script.
I've tried different salt lengths for crypto.subtle (but I think Apps Script must use salt length of 0, because it generates the same signature every time.)
Google AppsScript
function testSign() {
const message = "test";
const key = `-----BEGIN PRIVATE KEY-----
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
-----END PRIVATE KEY-----
`;
const sig = Utilities.computeRsaSha256Signature(message, key, Utilities.Charset.UTF_8);
console.log(sig.slice(0,4)); // [ 45, -76, -60, -59 ]
}
Crypto.subtle:
const message = "test";
const pemContents = `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`;
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function importRsaKey(pemContents) {
const binaryDerString = window.atob(pemContents);
const binaryDer = str2ab(binaryDerString);
return window.crypto.subtle.importKey(
"pkcs8",
binaryDer,
{
name: "RSA-PSS",
hash: "SHA-256"
},
true,
["sign"]
);
}
const key = await importRsaKey(pemContents);
const sig = await window.crypto.subtle.sign(
{
name: "RSA-PSS",
saltLength: 0,
},
key,
(new TextEncoder()).encode(message)
);
const sigArray = new Int8Array(sig);
console.log(sigArray[0], sigArray[1], sigArray[2], sigArray[3]); // -97 -106 92 29
Looks like the problem was the algorithm for crypto.subtle. Following code for crypto.subtle matches appsscript:
const message = "test";
const pemContents = `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`;
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function importRsaKey(pemContents) {
const binaryDerString = window.atob(pemContents);
const binaryDer = str2ab(binaryDerString);
return window.crypto.subtle.importKey(
"pkcs8",
binaryDer,
{
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256"
},
true,
["sign"]
);
}
const key = await importRsaKey(pemContents);
const sig = await window.crypto.subtle.sign(
{
name: "RSASSA-PKCS1-v1_5"
},
key,
(new TextEncoder()).encode(message)
);
const sigArray = new Int8Array(sig);
console.log(sigArray[0], sigArray[1], sigArray[2], sigArray[3]); // 45 -76 -60 -59
Related
I have a script (thankyou Cooper) that I'm trying to use to generate a list of all folders and files from within one particular folder in a shared drive, however, the script is returning the following error:
Exception: Argument too large: value
gff # Folder Listing.gs:67
(anonymous) # Folder Listing.gs:72
gff # Folder Listing.gs:68
(anonymous) # Folder Listing.gs:72
gff # Folder Listing.gs:68
(anonymous) # Folder Listing.gs:72
gff # Folder Listing.gs:68
(anonymous) # Folder Listing.gs:72
gff # Folder Listing.gs:68
(anonymous) # Folder Listing.gs:72
I'm assuming the problem is the script is grabbing all folders and files within the shared drive file and it's far too large to run the script, so I'm trying to reduce the source size by targeting a particular folder (or just returning the folder names and not worry about files at all).
What can I change on this script reduce the size issue?
function sharedDriveTrees() {
const ss = SpreadsheetApp.openById("blah");//need to change the ssid for the output spreadsheet
const r = Drive.Drives.list();
const drives = JSON.parse(r).items;
const shts = ss.getSheets().filter((sh, i) => i < drives.length).filter(e => e);
var trees = [];
drives.forEach((obj, i) => {
obj["title"] = obj.name;
let ob =JSON.parse(Drive.Files.get(obj.id,{ supportsAllDrives: true, supportsTeamDrives: true }));
obj["alternateLink"] = ob.alternateLink;
Logger.log('Drive Title: %s Time: %s', obj.title, Utilities.formatDate(new Date(), ss.getSpreadsheetTimeZone(), "HH:mm:ss"));
shts[i].setName(`${obj.title}\n${Utilities.formatDate(new Date(),ss.getSpreadsheetTimeZone(),"yyyy.MM.dd HH:mm:ss")}`);
let tree = [];
CacheService.getScriptCache().put("tree", JSON.stringify(tree), 60);
level = 1;
gff(obj)
tree = JSON.parse(CacheService.getScriptCache().get("tree"));
let l = tree.reduce((a, c) => {
if (c.length > a) {
a = c.length;
}
return a;
}, 0);
tree.forEach((a, j) => {
if ((l - a.length) > 0) {
let arr = [...Array.from(new Array(l - a.length).keys(), x => "")];
tree[j] = a.concat(arr);
}
});
trees.push(tree);
const sh = shts[i];
sh.clearContents();
sh.getRange(1, 1, tree.length, tree[0].length).setValues(tree);
SpreadsheetApp.flush();
});
}
level = 1;
function gff(fobj) {
//Logger.log('Drive Title: %s', fobj.title);
const r = Drive.Children.list(fobj.id);
const fldrMime = "application/vnd.google-apps.folder";
let tree = JSON.parse(CacheService.getScriptCache().get("tree"));
let files = [];
let subfolders = [];
fobj["level"] = level;
let children = JSON.parse(r).items;
children.forEach((obj, i) => {
let o = JSON.parse(Drive.Files.get(obj.id, { supportsAllDrives: true, supportsTeamDrives: true }));
o["level"] = level;
if (o.mimeType == fldrMime) {
subfolders.push(o);
} else {
files.push(o);
}
});
//Logger.log('level: %s', level);
let arr1 = [...Array.from(new Array(level).keys(), x => { if (x < (level - 1)) { return '' } else { return `=HYPERLINK("${fobj.alternateLink}","${fobj.title}")` } })];
//Logger.log('arr1: %s', JSON.stringify(arr1));
tree.push(arr1)
if (files && files.length > 0) {
files.forEach(obj => {
let arr2 = [...Array.from(new Array(level + 1).keys(), x => { if (x < (level)) { return '' } else { return `=HYPERLINK("${obj.alternateLink}","${obj.title}")` } })];
//Logger.log('arr2: %s', JSON.stringify(arr2));
tree.push(arr2)
})
}
CacheService.getScriptCache().put("tree", JSON.stringify(tree), 60);
subfolders.forEach(obj => {
level++;
obj.level = level;
CacheService.getScriptCache().put("tree", JSON.stringify(tree), 60);
gff(obj);
tree = JSON.parse(CacheService.getScriptCache().get("tree"))
});
level--;
return;
}
Edit:
After checking the affected line I found out that the issue is happening at CacheService.getScriptCache().put("tree", JSON.stringify(tree), 60). The Cache Documentation explains that the limit for the second parameter value is 100KB. The original script creator was using the CacheService as kind of a global variable to save every iteration of the loop as it kept adding levels to the tree. Since your folder structure is pretty large it grew beyond the 100KB limit.
As far as I can tell there's no way to raise this limit so I rewrote a few lines to pass the object to the gff() function instead of using the cache.
I kept the hyperlinks, but I also added a couple commented lines that you can switch to just return folder names as you requested. You can find them at let arr1 = and let arr2 =. You can switch between them to see if there's a performance improvement. You could also just change those lines in the original code that uses the cache, but you may still eventually run into the limit:
function folderTrees() {
const ss = SpreadsheetApp.openById("<Your spreadsheet id>");//need to change the ssid for the output spreadsheet
const f = Drive.Files.get("<Folder ID>", { supportsAllDrives: true, supportsTeamDrives: true });
const obj = JSON.parse(f);
const sh = ss.getSheets()[0];
var trees = [];
Logger.log('Folder Title: %s Time: %s', obj.title, Utilities.formatDate(new Date(), ss.getSpreadsheetTimeZone(), "HH:mm:ss"));
sh.setName(`${obj.title}\n${Utilities.formatDate(new Date(), ss.getSpreadsheetTimeZone(), "yyyy.MM.dd HH:mm:ss")}`);
let tree = [];
level = 1;
tree = gff(obj, tree)
let l = tree.reduce((a, c) => {
if (c.length > a) {
a = c.length;
}
return a;
}, 0);
tree.forEach((a, j) => {
if ((l - a.length) > 0) {
let arr = [...Array.from(new Array(l - a.length).keys(), x => "")];
tree[j] = a.concat(arr);
}
});
trees.push(tree);
sh.clearContents();
sh.getRange(1, 1, tree.length, tree[0].length).setValues(tree);
SpreadsheetApp.flush();
}
level = 1;
function gff(fobj, treeobj) {
const r = Drive.Children.list(fobj.id);
const fldrMime = "application/vnd.google-apps.folder";
let tree = treeobj;
let files = [];
let subfolders = [];
fobj["level"] = level;
let children = JSON.parse(r).items;
children.forEach((obj, i) => {
let o = JSON.parse(Drive.Files.get(obj.id, { supportsAllDrives: true, supportsTeamDrives: true }));
o["level"] = level;
if (o.mimeType == fldrMime) {
subfolders.push(o);
} else {
files.push(o);
}
});
//first line adds the hyperlinks and the second one returns only text
let arr1 = [...Array.from(new Array(level).keys(), x => { if (x < (level - 1)) { return '' } else { return `=HYPERLINK("${fobj.alternateLink}","${fobj.title}")` } })];
//let arr1 = [...Array.from(new Array(level).keys(), x => { if (x < (level - 1)) { return '' } else { return fobj.title } })];
tree.push(arr1)
if (files && files.length > 0) {
files.forEach(obj => {
//first line adds the hyperlinks and the second one returns only text
let arr2 = [...Array.from(new Array(level + 1).keys(), x => { if (x < (level)) { return '' } else { return `=HYPERLINK("${obj.alternateLink}","${obj.title}")` } })];
//let arr2 = [...Array.from(new Array(level + 1).keys(), x => { if (x < (level)) { return '' } else { return obj.title } })];
tree.push(arr2)
})
}
subfolders.forEach(obj => {
level++;
obj.level = level;
tree = gff(obj, tree);
});
level--;
return tree;
}
And here's the output:
A few things to note:
You'll need to get the folder ID to plug into the f variable.
The original script looped through all the shared Drives and wrote each one's tree to a different sheet within your spreadsheet. Since you only seemed to want a single folder's tree now it just writes to the first sheet in the file.
My goal is to see the Revit rooms in the Forge viewer. The application is in .NET Core. I have tried implementing GenerateMasterViews.
The code I am using to achieve this is:
[Route("api/forge/modelderivative/jobs")]
public async Task<dynamic> TranslateObject([FromBody]TranslateObjectModel objModel)
{
dynamic oauth = await OAuthController.GetInternalAsync();
// prepare the payload
var advOutputPayload = new JobSvf2OutputPayloadAdvanced();
advOutputPayload.GenerateMasterViews = true;
List<JobPayloadItem> outputs = new List<JobPayloadItem>()
{
new JobPayloadItem(
JobPayloadItem.TypeEnum.Svf2,
new List<JobPayloadItem.ViewsEnum>()
{
JobPayloadItem.ViewsEnum._2d,
JobPayloadItem.ViewsEnum._3d
},
advOutputPayload
)
};
JobPayload job;
job = new JobPayload(new JobPayloadInput(objModel.objectName), new JobPayloadOutput(outputs));
// start the translation
DerivativesApi derivative = new DerivativesApi();
derivative.Configuration.AccessToken = oauth.access_token;
dynamic jobPosted = await derivative.TranslateAsync(job);
return jobPosted;
}
Autodesk.Viewing.Initializer(options, () => {
viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer'));
viewer.start();
var documentId = 'urn:' + urn;
Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
}
function onDocumentLoadSuccess(doc) {
var viewables = doc.getRoot().getDefaultGeometry();
viewer.loadDocumentNode(doc, viewables).then(i => {
// documented loaded, any action?
});
}
But I can't get it to work.
I have looked for information, but this url: https://forge.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/option2/ and this url:
https://forge.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/option1/ they don't work and I couldn't see how to do it.
To check if the object is in the room, we can do the following:
Get bounds for each room and object
getBoundingBox(dbId, model) {
const it = model.getInstanceTree();
const fragList = model.getFragmentList();
let bounds = new THREE.Box3();
it.enumNodeFragments(dbId, (fragId) => {
let box = new THREE.Box3();
fragList.getWorldBounds(fragId, box);
bounds.union(box);
}, true);
return bounds;
}
Iterate rooms and objects and use containsBox or containsPoint to check if their bounding box has intersection.
If you want to do an acute collision check, you can take advantage of the ThreeCSG.js to do geometry intersection. Here is a blog post demonstrating how to integrate ThreeCSG.js with Forge Viewer.
https://forge.autodesk.com/blog/boolean-operations-forge-viewer
Note. This process would reduce the viewer performance since JavaScript is running on a single thread on the Web Browser, so you may use some technologies like the web worker to do the complex calculations on a separate thread.
Update:
Here is a working sample extension demonstrating the above idea:
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
(function () {
const Utility = {
/**
* Rest an object
* #param {Object} obj An object to be reset.
* ref: https://stackoverflow.com/a/24090180
*/
resetObject: function (obj) {
for (let key in Object.getOwnPropertyNames(obj)) {
if (!obj.hasOwnProperty(key)) continue;
let val = obj[key];
switch (typeof val) {
case 'string':
obj[key] = ''; break;
case 'number':
obj[key] = 0; break;
case 'boolean':
obj[key] = false; break;
case 'object':
if (val === null) break;
if (val instanceof Array) {
while (obj[key].length > 0) {
obj[key].pop();
}
break;
}
val = {};
//Or recursively clear the sub-object
//resetObject(val);
break;
}
}
}
};
/**
* A Forge Viewer extension for loading and rendering Revit Grids by AEC Model Data
* #class
*/
class RoomLocatorExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.roomCategoryName = options.roomCategoryName || 'Revit Rooms';//'Revit Habitaciones'
this.onContextMenu = this.onContextMenu.bind(this);
}
onContextMenu(menu, status) {
if (status.hasSelected) {
menu.push({
title: 'Find room',
target: async () => {
let selSet = this.viewer.getSelection();
this.viewer.clearSelection();
const roomDbIds = await this.locateElementByRoom(selSet[0]);
if (!roomDbIds || roomDbIds.length <= 0) return;
this.viewer.select(roomDbIds);
}
});
}
}
async getPropertiesAsync(dbId, model) {
return new Promise((resolve, reject) => {
model.getProperties2(
dbId,
(result) => resolve(result),
(error) => reject(error)
);
});
}
async getElementsByCategoryAsync(category) {
return new Promise((resolve, reject) => {
this.viewer.search(
category,
(dbIds) => resolve(dbIds),
(error) => reject(error),
['Category'],
{ searchHidden: true }
);
});
}
async getRoomDbIds() {
try {
const roomDbIds = await this.getElementsByCategoryAsync(this.roomCategoryName);
if (!roomDbIds || roomDbIds.length <= 0) {
throw new Error('No Rooms found in current model');
}
return roomDbIds;
} catch (ex) {
console.warn(`[RoomLocatorExtension]: ${ex}`);
throw new Error('No room found');
}
}
getBoundingBox(dbId, model) {
const it = model.getInstanceTree();
const fragList = model.getFragmentList();
let bounds = new THREE.Box3();
it.enumNodeFragments(dbId, (fragId) => {
let box = new THREE.Box3();
fragList.getWorldBounds(fragId, box);
bounds.union(box);
}, true);
return bounds;
}
getLeafFragIds(model, leafId) {
const instanceTree = model.getData().instanceTree;
const fragIds = [];
instanceTree.enumNodeFragments(leafId, function (fragId) {
fragIds.push(fragId);
});
return fragIds;
}
getComponentGeometryInfo(dbId, model) {
const viewer = this.viewer;
const viewerImpl = viewer.impl;
const fragIds = this.getLeafFragIds(model, dbId);
let matrixWorld = null;
const meshes = fragIds.map((fragId) => {
const renderProxy = viewerImpl.getRenderProxy(model, fragId);
const geometry = renderProxy.geometry;
const attributes = geometry.attributes;
const positions = geometry.vb ? geometry.vb : attributes.position.array;
const indices = attributes.index.array || geometry.ib;
const stride = geometry.vb ? geometry.vbstride : 3;
const offsets = geometry.offsets;
matrixWorld = matrixWorld || renderProxy.matrixWorld.elements;
return {
positions,
indices,
offsets,
stride
};
});
return {
matrixWorld,
meshes
};
}
getComponentGeometry(data, vertexArray) {
const offsets = [
{
count: data.indices.length,
index: 0,
start: 0
}
];
for (let oi = 0, ol = offsets.length; oi < ol; ++oi) {
let start = offsets[oi].start;
let count = offsets[oi].count;
let index = offsets[oi].index;
for (let i = start, il = start + count; i < il; i += 3) {
const a = index + data.indices[i];
const b = index + data.indices[i + 1];
const c = index + data.indices[i + 2];
const vA = new THREE.Vector3();
const vB = new THREE.Vector3();
const vC = new THREE.Vector3();
vA.fromArray(data.positions, a * data.stride);
vB.fromArray(data.positions, b * data.stride);
vC.fromArray(data.positions, c * data.stride);
vertexArray.push(vA);
vertexArray.push(vB);
vertexArray.push(vC);
}
}
}
buildComponentMesh(data) {
const vertexArray = [];
for (let idx = 0; idx < data.nbMeshes; ++idx) {
const meshData = {
positions: data['positions' + idx],
indices: data['indices' + idx],
stride: data['stride' + idx]
}
this.getComponentGeometry(meshData, vertexArray);
}
const geometry = new THREE.Geometry();
for (let i = 0; i < vertexArray.length; i += 3) {
geometry.vertices.push(vertexArray[i]);
geometry.vertices.push(vertexArray[i + 1]);
geometry.vertices.push(vertexArray[i + 2]);
const face = new THREE.Face3(i, i + 1, i + 2);
geometry.faces.push(face);
}
const matrixWorld = new THREE.Matrix4();
matrixWorld.fromArray(data.matrixWorld);
const mesh = new THREE.Mesh(geometry);
mesh.applyMatrix(matrixWorld);
mesh.boundingBox = data.boundingBox;
mesh.bsp = new ThreeBSP(mesh)
mesh.dbId = data.dbId;
return mesh;
}
buildCsgMesh(dbId, model) {
const geometry = this.getComponentGeometryInfo(dbId, model);
const data = {
boundingBox: this.getBoundingBox(dbId, model),
matrixWorld: geometry.matrixWorld,
nbMeshes: geometry.meshes.length,
dbId
};
geometry.meshes.forEach((mesh, idx) => {
data['positions' + idx] = mesh.positions;
data['indices' + idx] = mesh.indices;
data['stride' + idx] = mesh.stride;
});
return this.buildComponentMesh(data);
}
async buildBBoxes() {
try {
const model = this.viewer.model;
const roomBBoxes = {};
const roomDbIds = await this.getRoomDbIds();
for (let i = 0; i < roomDbIds.length; i++) {
let dbId = roomDbIds[i];
let bbox = await this.getBoundingBox(dbId, model);
roomBBoxes[dbId] = bbox;
}
this.cachedBBoxes['rooms'] = roomBBoxes;
} catch (ex) {
console.warn(`[RoomLocatorExtension]: ${ex}`);
throw new Error('Cannot build bounding boxes from rooms');
}
}
async locateElementByRoom(dbId) {
let bbox = await this.getBoundingBox(dbId, this.viewer.model);
const roomDbIds = Object.keys(this.cachedBBoxes['rooms']);
const roomBoxes = Object.values(this.cachedBBoxes['rooms']);
// Coarse Phase Collision
const coarseResult = [];
for (let i = 0; i < roomDbIds.length; i++) {
let roomDbId = roomDbIds[i];
let roomBox = roomBoxes[i];
if (roomBox.containsBox(bbox)) {
coarseResult.push(parseInt(roomDbId));
} else {
if (roomBox.containsPoint(bbox.min) || roomBox.containsPoint(bbox.max) || roomBox.containsPoint(bbox.center())) {
coarseResult.push(parseInt(roomDbId));
}
}
}
// Fine Phase Collision
const fineResult = [];
let elementCsgMesh = this.buildCsgMesh(dbId, this.viewer.model);
for (let i = 0; i < coarseResult.length; i++) {
let roomDbId = coarseResult[i];
let roomCsgMesh = this.buildCsgMesh(roomDbId, this.viewer.model);
let result = elementCsgMesh.bsp.intersect(roomCsgMesh.bsp);
if (result.tree.polygons.length <= 0) {
result = roomCsgMesh.bsp.intersect(elementCsgMesh.bsp);
// if (!this.viewer.overlays.hasScene('csg'))
// this.viewer.overlays.addScene('csg');
// else
// this.viewer.overlays.clearScene('csg');
// let mat = new THREE.MeshBasicMaterial({ color: 'red' })
// let mesh = result.toMesh(mat);
// this.viewer.overlays.addMesh(mesh, 'csg')
if (result.tree.polygons.length <= 0) continue;
}
fineResult.push(roomDbId);
}
return fineResult;
}
async load() {
await Autodesk.Viewing.Private.theResourceLoader.loadScript(
'https://cdn.jsdelivr.net/gh/Wilt/ThreeCSG#develop/ThreeCSG.js',
'ThreeBSP'
);
if (!window.ThreeBSP)
throw new Error('Cannot load ThreeCSG.js, please download a copy from https://github.com/Wilt/ThreeCSG/blob/develop/ThreeCSG.js')
await this.viewer.waitForLoadDone();
this.cachedBBoxes = {};
await this.buildBBoxes();
this.viewer.registerContextMenuCallback(
'RoomLocatorExtension',
this.onContextMenu
);
return true;
}
unload() {
Utility.resetObject(this.cachedBBoxes);
this.viewer.unregisterContextMenuCallback(
'RoomLocatorExtension',
this.onContextMenu
);
return true;
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('RoomLocatorExtension', RoomLocatorExtension);
})();
Snapshots:
I am trying to decrypt AES with GAS. The target of decryption is a document file retrieved by Amazon Selling Partner API.
The key, iv, and URL are obtained by the API, and I want to decrypt the data downloaded by accessing the URL with the key and iv.
However, the decrypted text is either empty or garbled.
Can you please tell me what is wrong with the following code? The code uses cCryptoGS, which is a wrapper library for CryptoJS.
const decrypt_test = () => {
const url = 'https://tortuga-prod-fe.s3-us-west-2.amazonaws.com/%2FNinetyDays/amzn1.tortuga.3.5d4685fe-cdf1-4f37-8dfc-a25b85468e34.T1J5QXLEXAMPLE';
const response = UrlFetchApp.fetch(url);
const file = response.getContentText();
const key = 'xiZ8FGT6pYo49ZwfvAplJxKgO0qW46Morzs5aEXAMPLE';
const iv = 'aoGh0rhbB3ALlCFKiEXAMPLE';
const enc_key = cCryptoGS.CryptoJS.enc.Base64.parse(key);
const enc_iv = cCryptoGS.CryptoJS.enc.Base64.parse(iv);
const cipherParams = cCryptoGS.CryptoJS.lib.CipherParams.create({
ciphertext: file//cCryptoGS.CryptoJS.enc.Base64.parse(file)
});
console.log(`enc_key_length:${enc_key.words.length}`);
console.log(`enc_iv_length:${enc_iv.words.length}`);
const decryptedMessage = cCryptoGS.CryptoJS.AES.decrypt(cipherParams, enc_key, { iv: enc_iv, mode: cCryptoGS.CryptoJS.mode.CBC}).toString();
console.log(`decryptedMessage:${decryptedMessage}`);
return decryptedMessage;
};
[output]
2021/06/20 20:04:04 debug enc_key_length:8
2021/06/20 20:04:04 debug enc_iv_length:4
2021/06/20 20:04:04 debug decryptedMessage:bfc095f3ecec221e8585ceb68031078d25112f5f26ea2c1f80470f5f4f19f2e1c2cd94638e8666c3486fa29191b568bcd9e8d5a3bdcbbc05456f0567bb6cdae675fa044f94e560379d16b1d370cd7c4a9c5afbbcf4fde2694ed01c1b7950eaabc65e46c4640d8f0814bfe66e8ae65f7768136ac4615624be25373d665ee8fde82742e26664d7c09c61ac8994dc3052f0f22d5042f0b407d696e3c84a3906350dc60c46001ef7865d0c6594c57c5af22616688e028f52d4f12b538d0580c420fdcb0ee61287d4ee2629cd7d39f739d63e84dd75e948eaffb4383076f0c66997
The following code solved the problem
const decrypt_test = () => {
const url = 'https://tortuga-prod-fe.s3-us-west-2.amazonaws.com/%2FNinetyDays/EXAMPLE';
let options = {
'method': 'get',
'muteHttpExceptions': true,
};
const response = UrlFetchApp.fetch(url, options);
const file = response.getBlob().getBytes();
const key = 'xiZ8FGT6pYo49ZwfvAplJxKgO0qW46MoEXAMPLE';
const iv = 'aoGh0rhbB3ALlCFKiuJEXAMPLE';
const enc_key = cCryptoGS.CryptoJS.enc.Base64.parse(key);
const enc_iv = cCryptoGS.CryptoJS.enc.Base64.parse(iv);
const cipherParams = cCryptoGS.CryptoJS.lib.CipherParams.create({
ciphertext: cCryptoGS.CryptoJS.enc.Hex.parse(hexes(file))
});
const decryptedMessage = cCryptoGS.CryptoJS.AES.decrypt(cipherParams, enc_key,
{ iv: enc_iv, mode: cCryptoGS.CryptoJS.mode.CBC}).toString();
console.log(`decryptedMessage:${decryptedMessage}`);
const bin = bytes(decryptedMessage)
const myBlob = Utilities.newBlob(bin, MimeType.TEXT, "decrypted.csv");
DriveApp.createFile(myBlob);
};
const bytes = (hexstr) => {
ary = [];
for (var i = 0; i < hexstr.length; i += 2) {
ary.push(parseInt(hexstr.substr(i, 2), 16));
}
return ary;
}
const hexes = (ary) => {
return ary.map((e) => ( '00' + (e < 0 ? e += 0x0100 : e).toString(16)).slice(-2)).join('')
}
I am trying to get an "image URL field" convert to a base64 blob data, however the updated field console log data is showing as ZoneAwarePromise {__zone_symbol__state: null, __zone_symbol__value: Array(0)}
this.http.get(this.api).subscribe(data => {
this.mainContact = data;
for (var a = 0; a < this.mainContact.length; a++) {
for (var j = 0; j < this.mainContact[a].acf.project_gallery.length; j++) {
var mainUrl = 'https://cdnwebapp.azureedge.net'+this.mainContact[a].acf.project_gallery[j].url;
//Below updates the all field value to the converted blob
this.mainContact[a].acf.project_gallery[j].url = this.convertToDataURLviaCanvas(mainUrl, "image/jpeg", 1)
.then(base64Img => {
return base64Img;
// console.log(base64Img); *As Expected blob*
})
//console.log(this.mainContact[a].acf.project_gallery[j].url) * The update field data console log is showing as ZoneAwarePromise
}
}
});
convertToDataURLviaCanvas(url, outputFormat, osize){
return new Promise( (resolve, reject) => {
let img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function(){
let canvas = <HTMLCanvasElement> document.createElement('CANVAS'),
ctx = canvas.getContext('2d'),
dataURL;
canvas.height = 959;
canvas.width = 1365;
ctx.drawImage(img, 0, 0);
dataURL = canvas.toDataURL(outputFormat, osize);
//callback(dataURL);
canvas = null;
resolve(dataURL);
};
img.src = url;
});
}
I think you have to assign in on the .then.
Try:
this.convertToDataURLviaCanvas(mainUrl, "image/jpeg", 1)
.then(base64Img => {
this.mainContact[a].acf.project_gallery[j].url = base64img
console.log(base64Img);
console.log(this.mainContact[a].acf.project_gallery[j].url);
})
Is there any way to increase the chrome.storage.sync.QUOTA_BYTES_PER_ITEM ?
For me, the default 4096 Bytes is a little bit short.
I tried to execute
chrome.storage.sync.QUOTA_BYTES_PER_ITEM = 8192;
However, it seems that the actual limit doesn't change.
How can I do this?
No, QUOTA_BYTES_PER_ITEM is there for reference only; it is not a settable value. You could use the value of QUOTA_BYTES_PER_ITEM to split an item up into multiple items, though:
function syncStore(key, objectToStore, callback) {
var jsonstr = JSON.stringify(objectToStore);
var i = 0;
var storageObj = {};
// split jsonstr into chunks and store them in an object indexed by `key_i`
while(jsonstr.length > 0) {
var index = key + "_" + i++;
// since the key uses up some per-item quota, see how much is left for the value
// also trim off 2 for quotes added by storage-time `stringify`
var valueLength = chrome.storage.sync.QUOTA_BYTES_PER_ITEM - index.length - 2;
// trim down segment so it will be small enough even when run through `JSON.stringify` again at storage time
var segment = jsonstr.substr(0, valueLength);
while(JSON.stringify(segment).length > valueLength)
segment = jsonstr.substr(0, --valueLength);
storageObj[index] = segment;
jsonstr = jsonstr.substr(valueLength);
}
// store all the chunks
chrome.storage.sync.set(storageObj, callback);
}
Then write an analogous fetch function that fetches by key and glues the object back together.
just modify answer of #apsilliers
function syncStore(key, objectToStore) {
var jsonstr = JSON.stringify(objectToStore);
var i = 0;
var storageObj = {};
// split jsonstr into chunks and store them in an object indexed by `key_i`
while(jsonstr.length > 0) {
var index = key + "_" + i++;
// since the key uses up some per-item quota, see how much is left for the value
// also trim off 2 for quotes added by storage-time `stringify`
const maxLength = chrome.storage.sync.QUOTA_BYTES_PER_ITEM - index.length - 2;
var valueLength = jsonstr.length;
if(valueLength > maxLength){
valueLength = maxLength;
}
// trim down segment so it will be small enough even when run through `JSON.stringify` again at storage time
//max try is QUOTA_BYTES_PER_ITEM to avoid infinite loop
var segment = jsonstr.substr(0, valueLength);
for(let i = 0; i < chrome.storage.sync.QUOTA_BYTES_PER_ITEM; i++){
const jsonLength = JSON.stringify(segment).length;
if(jsonLength > maxLength){
segment = jsonstr.substr(0, --valueLength);
}else {
break;
}
}
storageObj[index] = segment;
jsonstr = jsonstr.substr(valueLength);
}
also function to read each partition and merge again
function syncGet(key, callback) {
chrome.storage.sync.get(key, (data) => {
console.log(data[key]);
console.log(typeof data[key]);
if(data != undefined && data != "undefined" && data != {} && data[key] != undefined && data[key] != "undefined"){
const keyArr = new Array();
for(let i = 0; i <= data[key].count; i++) {
keyArr.push(`${data[key].prefix}${i}`)
}
chrome.storage.sync.get(keyArr, (items) => {
console.log(data)
const keys = Object.keys( items );
const length = keys.length;
let results = "";
if(length > 0){
const sepPos = keys[0].lastIndexOf("_");
const prefix = keys[0].substring(0, sepPos);
for(let x = 0; x < length; x ++){
results += items[`${prefix }_${x}`];
}
callback(JSON.parse(results));
return;
}
callback(undefined);
});
} else {
callback(undefined);
}
});
}
it tested and it works for my case
this is a better version of #uncle bob's functions, working with manifest v3 (you can use it just like how you can use the normal sync.set or sync.get function)
NOTE: it only works with JSONs (arrays and objects) since a string shouldn't be that long
let browserServices;
if (typeof browser === "undefined") {
browserServices = chrome;
} else {
browserServices = browser;
}
function syncSet(obj = {}) {
return new Promise((resolve, reject) => {
var storageObj = {};
for (let u = 0; u < Object.keys(obj).length; u++) {
const key = Object.keys(obj)[u];
const objectToStore = obj[key]
var jsonstr = JSON.stringify(objectToStore);
var i = 0;
// split jsonstr into chunks and store them in an object indexed by `key_i`
while (jsonstr.length > 0) {
var index = key + "USEDTOSEPERATE" + i++;
// since the key uses up some per-item quota, see how much is left for the value
// also trim off 2 for quotes added by storage-time `stringify`
const maxLength = browserServices.storage.sync.QUOTA_BYTES_PER_ITEM - index.length - 2;
var valueLength = jsonstr.length;
if (valueLength > maxLength) {
valueLength = maxLength;
}
// trim down segment so it will be small enough even when run through `JSON.stringify` again at storage time
//max try is QUOTA_BYTES_PER_ITEM to avoid infinite loop
var segment = jsonstr.substring(0, valueLength);
var jsonLength = JSON.stringify(segment).length;
segment = jsonstr.substring(0, valueLength = (valueLength - (jsonLength - maxLength) - 1));
for (let i = 0; i < browserServices.storage.sync.QUOTA_BYTES_PER_ITEM; i++) {
jsonLength = JSON.stringify(segment).length;
if (jsonLength > maxLength) {
segment = jsonstr.substring(0, --valueLength);
} else {
break;
}
}
storageObj[index] = segment;
jsonstr = jsonstr.substring(valueLength, Infinity);
}
}
chrome.storage.sync.set(storageObj).then(() => {
resolve()
})
})
}
function syncGet(uniqueKeys = []) {
return new Promise((resolve, reject) => {
browserServices.storage.sync.get(null).then((data) => {
const keyArr = Object.keys(data).filter(e => uniqueKeys.filter(j => e.indexOf(j) == 0).length > 0)
browserServices.storage.sync.get(keyArr).then((items) => {
var results = {};
for (let i = 0; i < uniqueKeys.length; i++) {
const uniqueKey = uniqueKeys[i];
const keysFiltered = keyArr.filter(e => e.split("USEDTOSEPERATE")[0] == uniqueKey)
if (keysFiltered.length > 0) {
results[uniqueKey] = ""
for (let x = 0; x < keysFiltered.length; x++) {
results[uniqueKey] += items[`${keysFiltered[x]}`];
}
results[uniqueKey] = JSON.parse(results[uniqueKey])
}
}
resolve(results)
});
});
})
}
example of usage:
syncSet({
"keyTest": ["a lot of text"],
"keyTest1": ["a lot of text"]
}
)
syncGet(["keyTest","keyTest1"]).then(results=>console.log(results))
// {keyTest:["a lot of text"],keyTest1:["a lot of text"]}