Skip to content

Instantly share code, notes, and snippets.

@drudge
Last active January 26, 2022 13:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drudge/01f204a6fe30e3f2477bf7834297414f to your computer and use it in GitHub Desktop.
Save drudge/01f204a6fe30e3f2477bf7834297414f to your computer and use it in GitHub Desktop.
NetSuite SuiteScript to queue messages on an AWS SQS queue
/**
* @NModuleScope Public
* @NApiVersion 2.1
*/
function ExampleSQSUsage(log, runtime, sqs) {
const awsAccessKeyId = '...'; // TODO: Pull from a secure storage area
const awsSecretKey = '...'; // TODO: Pull from a secure storage area
const queueName = runtime.envType == runtime.EnvType.PRODUCTION
? '000000000000/order-processing-production'
: '000000000000/order-processing-sandbox';
try {
const messageId = sqs.sendMessage({
awsAccessKeyId,
awsSecretKey,
awsRegion: 'us-east-1',
queueName,
body: JSON.stringify([{ internalid: 1234, quantity: 1 }]),
});
// TODO: Do something with `messageId`
} catch (error) {
// TODO: `Failed to send notification: ${error.message}`
}
}
define([ 'N/log', 'N/runtime', './sqs' ], ExampleSQSUsage);
function CryptoShim() {
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS = CryptoJS || function (h, s) {
var f = {}, g = f.lib = {}, q = function () { }, m = g.Base = { extend: function (a) { q.prototype = this; var c = new q; a && c.mixIn(a); c.hasOwnProperty("init") || (c.init = function () { c.$super.init.apply(this, arguments) }); c.init.prototype = c; c.$super = this; return c }, create: function () { var a = this.extend(); a.init.apply(a, arguments); return a }, init: function () { }, mixIn: function (a) { for (var c in a) a.hasOwnProperty(c) && (this[c] = a[c]); a.hasOwnProperty("toString") && (this.toString = a.toString) }, clone: function () { return this.init.prototype.extend(this) } },
r = g.WordArray = m.extend({
init: function (a, c) { a = this.words = a || []; this.sigBytes = c != s ? c : 4 * a.length }, toString: function (a) { return (a || k).stringify(this) }, concat: function (a) { var c = this.words, d = a.words, b = this.sigBytes; a = a.sigBytes; this.clamp(); if (b % 4) for (var e = 0; e < a; e++)c[b + e >>> 2] |= (d[e >>> 2] >>> 24 - 8 * (e % 4) & 255) << 24 - 8 * ((b + e) % 4); else if (65535 < d.length) for (e = 0; e < a; e += 4)c[b + e >>> 2] = d[e >>> 2]; else c.push.apply(c, d); this.sigBytes += a; return this }, clamp: function () {
var a = this.words, c = this.sigBytes; a[c >>> 2] &= 4294967295 <<
32 - 8 * (c % 4); a.length = h.ceil(c / 4)
}, clone: function () { var a = m.clone.call(this); a.words = this.words.slice(0); return a }, random: function (a) { for (var c = [], d = 0; d < a; d += 4)c.push(4294967296 * h.random() | 0); return new r.init(c, a) }
}), l = f.enc = {}, k = l.Hex = {
stringify: function (a) { var c = a.words; a = a.sigBytes; for (var d = [], b = 0; b < a; b++) { var e = c[b >>> 2] >>> 24 - 8 * (b % 4) & 255; d.push((e >>> 4).toString(16)); d.push((e & 15).toString(16)) } return d.join("") }, parse: function (a) {
for (var c = a.length, d = [], b = 0; b < c; b += 2)d[b >>> 3] |= parseInt(a.substr(b,
2), 16) << 24 - 4 * (b % 8); return new r.init(d, c / 2)
}
}, n = l.Latin1 = { stringify: function (a) { var c = a.words; a = a.sigBytes; for (var d = [], b = 0; b < a; b++)d.push(String.fromCharCode(c[b >>> 2] >>> 24 - 8 * (b % 4) & 255)); return d.join("") }, parse: function (a) { for (var c = a.length, d = [], b = 0; b < c; b++)d[b >>> 2] |= (a.charCodeAt(b) & 255) << 24 - 8 * (b % 4); return new r.init(d, c) } }, j = l.Utf8 = { stringify: function (a) { try { return decodeURIComponent(escape(n.stringify(a))) } catch (c) { throw Error("Malformed UTF-8 data"); } }, parse: function (a) { return n.parse(unescape(encodeURIComponent(a))) } },
u = g.BufferedBlockAlgorithm = m.extend({
reset: function () { this._data = new r.init; this._nDataBytes = 0 }, _append: function (a) { "string" == typeof a && (a = j.parse(a)); this._data.concat(a); this._nDataBytes += a.sigBytes }, _process: function (a) { var c = this._data, d = c.words, b = c.sigBytes, e = this.blockSize, f = b / (4 * e), f = a ? h.ceil(f) : h.max((f | 0) - this._minBufferSize, 0); a = f * e; b = h.min(4 * a, b); if (a) { for (var g = 0; g < a; g += e)this._doProcessBlock(d, g); g = d.splice(0, a); c.sigBytes -= b } return new r.init(g, b) }, clone: function () {
var a = m.clone.call(this);
a._data = this._data.clone(); return a
}, _minBufferSize: 0
}); g.Hasher = u.extend({
cfg: m.extend(), init: function (a) { this.cfg = this.cfg.extend(a); this.reset() }, reset: function () { u.reset.call(this); this._doReset() }, update: function (a) { this._append(a); this._process(); return this }, finalize: function (a) { a && this._append(a); return this._doFinalize() }, blockSize: 16, _createHelper: function (a) { return function (c, d) { return (new a.init(d)).finalize(c) } }, _createHmacHelper: function (a) {
return function (c, d) {
return (new t.HMAC.init(a,
d)).finalize(c)
}
}
}); var t = f.algo = {}; return f
}(Math);
(function (h) {
for (var s = CryptoJS, f = s.lib, g = f.WordArray, q = f.Hasher, f = s.algo, m = [], r = [], l = function (a) { return 4294967296 * (a - (a | 0)) | 0 }, k = 2, n = 0; 64 > n;) { var j; a: { j = k; for (var u = h.sqrt(j), t = 2; t <= u; t++)if (!(j % t)) { j = !1; break a } j = !0 } j && (8 > n && (m[n] = l(h.pow(k, 0.5))), r[n] = l(h.pow(k, 1 / 3)), n++); k++ } var a = [], f = f.SHA256 = q.extend({
_doReset: function () { this._hash = new g.init(m.slice(0)) }, _doProcessBlock: function (c, d) {
for (var b = this._hash.words, e = b[0], f = b[1], g = b[2], j = b[3], h = b[4], m = b[5], n = b[6], q = b[7], p = 0; 64 > p; p++) {
if (16 > p) a[p] =
c[d + p] | 0; else { var k = a[p - 15], l = a[p - 2]; a[p] = ((k << 25 | k >>> 7) ^ (k << 14 | k >>> 18) ^ k >>> 3) + a[p - 7] + ((l << 15 | l >>> 17) ^ (l << 13 | l >>> 19) ^ l >>> 10) + a[p - 16] } k = q + ((h << 26 | h >>> 6) ^ (h << 21 | h >>> 11) ^ (h << 7 | h >>> 25)) + (h & m ^ ~h & n) + r[p] + a[p]; l = ((e << 30 | e >>> 2) ^ (e << 19 | e >>> 13) ^ (e << 10 | e >>> 22)) + (e & f ^ e & g ^ f & g); q = n; n = m; m = h; h = j + k | 0; j = g; g = f; f = e; e = k + l | 0
} b[0] = b[0] + e | 0; b[1] = b[1] + f | 0; b[2] = b[2] + g | 0; b[3] = b[3] + j | 0; b[4] = b[4] + h | 0; b[5] = b[5] + m | 0; b[6] = b[6] + n | 0; b[7] = b[7] + q | 0
}, _doFinalize: function () {
var a = this._data, d = a.words, b = 8 * this._nDataBytes, e = 8 * a.sigBytes;
d[e >>> 5] |= 128 << 24 - e % 32; d[(e + 64 >>> 9 << 4) + 14] = h.floor(b / 4294967296); d[(e + 64 >>> 9 << 4) + 15] = b; a.sigBytes = 4 * d.length; this._process(); return this._hash
}, clone: function () { var a = q.clone.call(this); a._hash = this._hash.clone(); return a }
}); s.SHA256 = q._createHelper(f); s.HmacSHA256 = q._createHmacHelper(f)
})(Math);
(function () {
var h = CryptoJS, s = h.enc.Utf8; h.algo.HMAC = h.lib.Base.extend({
init: function (f, g) { f = this._hasher = new f.init; "string" == typeof g && (g = s.parse(g)); var h = f.blockSize, m = 4 * h; g.sigBytes > m && (g = f.finalize(g)); g.clamp(); for (var r = this._oKey = g.clone(), l = this._iKey = g.clone(), k = r.words, n = l.words, j = 0; j < h; j++)k[j] ^= 1549556828, n[j] ^= 909522486; r.sigBytes = l.sigBytes = m; this.reset() }, reset: function () { var f = this._hasher; f.reset(); f.update(this._iKey) }, update: function (f) { this._hasher.update(f); return this }, finalize: function (f) {
var g =
this._hasher; f = g.finalize(f); g.reset(); return g.finalize(this._oKey.clone().concat(f))
}
})
})();
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
(function () {
var h = CryptoJS, j = h.lib.WordArray; h.enc.Base64 = {
stringify: function (b) {
var e = b.words, f = b.sigBytes, c = this._map; b.clamp(); b = []; for (var a = 0; a < f; a += 3) {
for (var d = (e[a >>> 2] >>> 24 - 8 * (a % 4) & 255) << 16 | (e[a + 1 >>> 2] >>> 24 - 8 * ((a + 1) % 4) & 255) << 8 | e[a + 2 >>> 2] >>> 24 - 8 * ((a + 2) % 4) & 255, g = 0; 4 > g && a + 0.75 * g < f; g++) { b.push(c.charAt(d >>> 6 * (3 - g) & 63)); }
} if (e = c.charAt(64)) {
for (; b.length % 4;) { b.push(e); }
} return b.join("")
}, parse: function (b) {
var e = b.length, f = this._map, c = f.charAt(64); c && (c = b.indexOf(c), -1 != c && (e = c)); for (var c = [], a = 0, d = 0; d <
e; d++) {
if (d % 4) { var g = f.indexOf(b.charAt(d - 1)) << 2 * (d % 4), h = f.indexOf(b.charAt(d)) >>> 6 - 2 * (d % 4); c[a >>> 2] |= (g | h) << 24 - 8 * (a % 4); a++ }
} return j.create(c, a)
}, _map: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="
}
})();
return CryptoJS;
}
define([], CryptoShim);
Copyright 2021 Nicholas Penree <npenree@fye.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of
the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
/**
* sqs.js
*
* Copyright (c) Nicholas Penree. All rights reserved.
* Licensed under the MIT license. See LICENSE for details.
*
* @author Nicholas Penree <npenree@fye.com>
* @NModuleScope Public
* @NApiVersion 2.1
*/
function SQS(crypto, https, log, CryptoJS) {
const SERVICE_NAME_SQS = 'sqs';
function getAmzDate() {
const amzDate = `${new Date().toISOString().split('.')[0]}Z`;
return amzDate.replace(/-/g, '').replace(/:/g, '');
}
function rfc3986EncodeURIComponent(str) {
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16)}`);
}
function getSignatureKey(key, dateStamp, regionName, serviceName) {
const kDate = CryptoJS.HmacSHA256(dateStamp, `AWS4${key}`);
const kRegion = CryptoJS.HmacSHA256(regionName, kDate);
const kService = CryptoJS.HmacSHA256(serviceName, kRegion);
return CryptoJS.HmacSHA256('aws4_request', kService);
}
/**
* Sends a message to an AWS SQS queue.
*
* Options:
* @param awsAccessKeyId string
* @param awsSecretKey string
* @param awsRegion string defaults to us-east-1
* @param queueName string
* @param body? string
* @param attributes? array<Attribute>
*
* Attributes:
* @param name string
* @param value string
* @param type string defaults to String
*
* @api public
* @returns string|null SQS message id if successful
* @param options object
*/
function sendMessage({
awsAccessKeyId: accessKey,
awsSecretKey: secretKey,
awsRegion: region = 'us-east-1',
queueName,
attributes = [],
body = '',
host = SERVICE_NAME_SQS + '.' + region + '.amazonaws.com',
LOGGER_TAG = 'sqs.sendMessage',
}) {
const amzDate = getAmzDate();
const datestamp = amzDate.split("T")[0];
log.debug(LOGGER_TAG, `amz date=${amzDate}`);
let params = "Action=SendMessage";
attributes.forEach((attrib, index) => {
const pos = index + 1;
if (attrib.name) {
params += `&MessageAttribute.${pos}.Name=${encodeURIComponent(attrib.name)}`;
}
params += `&MessageAttribute.${pos}.Value.DataType=${encodeURIComponent(attrib.type || 'String')}`;
if (attrib.value) {
params += `&MessageAttribute.${pos}.Value.StringValue=${encodeURIComponent(attrib.value)}`;
}
});
params += `&MessageBody=${rfc3986EncodeURIComponent(body)}&Version=2012-11-05`;
// ************* TASK 1: CREATE A CANONICAL REQUEST *************
const canonicalUri = `/${queueName}`;
const canonicalQuerystring = params;
const canonicalHeaders = 'host:' + host + '\n' + 'x-amz-date:' + amzDate + '\n';
const signedHeaders = 'host;x-amz-date';
const payloadHash = crypto.createHash({ algorithm: crypto.HashAlg.SHA256 });
const canonicalRequest = 'GET\n' +
canonicalUri + '\n' +
canonicalQuerystring + '\n' +
canonicalHeaders + '\n' +
signedHeaders + '\n' +
payloadHash.digest().toLowerCase();
log.debug(LOGGER_TAG, `canonical request=${JSON.stringify(canonicalRequest)}`);
// ************* TASK 2: CREATE THE STRING TO SIGN*************
const algorithm = 'AWS4-HMAC-SHA256';
const credentialScope = datestamp + '/' + region + '/' + SERVICE_NAME_SQS + '/' + 'aws4_request';
const canonicalHash = crypto.createHash({ algorithm: crypto.HashAlg.SHA256 });
canonicalHash.update({ input: canonicalRequest });
const stringToSign = algorithm + '\n' +
amzDate + '\n' +
credentialScope + '\n' +
canonicalHash.digest().toLowerCase();
log.debug(LOGGER_TAG, 'string to sign=' + stringToSign);
// ************* TASK 3: CALCULATE THE SIGNATURE *************
log.debug(LOGGER_TAG, 'signing key=' + secretKey + "\n" + datestamp + "\n" + region + "\n" + SERVICE_NAME_SQS);
const signingKey = getSignatureKey(secretKey, datestamp, region, SERVICE_NAME_SQS);
const signature = CryptoJS.HmacSHA256(stringToSign, signingKey);
const authorizationHeader = algorithm + ' ' +
'Credential=' + accessKey + '/' + credentialScope + ', ' +
'SignedHeaders=' + signedHeaders + ', ' + 'Signature=' + signature;
const response = https.get({
url: `https://${host}${canonicalUri}?${canonicalQuerystring}`,
headers: {
'host': host,
'x-amz-date': amzDate,
'Authorization': authorizationHeader
},
});
log.debug(LOGGER_TAG, `response=${JSON.stringify(response)}`);
if (response.code == 200) {
const matches = /\<MessageId\>(.*)\<\/MessageId\>/.exec(response.body);
if (matches && matches.length > 1) {
const messageId = matches[1];
log.debug(LOGGER_TAG, `Sent Message to SQS messageId=${messageId}`);
return messageId;
}
}
log.error(LOGGER_TAG, `Sent Message Failed: ${response.body}`);
return null;
}
/**
* Exports `sendMessage`.
*/
return {
sendMessage,
};
}
define([
'N/crypto',
'N/https',
'N/log',
'./crypto',
], SQS);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment