From 7ac983018d8e7111862feba05d9e5189286e2b15 Mon Sep 17 00:00:00 2001 From: Shushma44 Date: Fri, 2 Aug 2024 16:19:19 +0530 Subject: [PATCH] Add files via upload edgekv auto token renewal changes --- public_examples/edgekv-tokens.js | 10 + public_examples/edgekv.js | 346 +++++++++++++++++++++++++++++++ public_examples/main.js | 37 ++++ 3 files changed, 393 insertions(+) create mode 100644 public_examples/edgekv-tokens.js create mode 100644 public_examples/edgekv.js create mode 100644 public_examples/main.js diff --git a/public_examples/edgekv-tokens.js b/public_examples/edgekv-tokens.js new file mode 100644 index 00000000..2f5c8f23 --- /dev/null +++ b/public_examples/edgekv-tokens.js @@ -0,0 +1,10 @@ + + + var edgekv_access_tokens = { + "namespace-test": { + "name": "my_auto_token_07252024", + "reference": "9e427ea3-33f4-5430-8794-22f1c21b1378", + } + }; + export { edgekv_access_tokens }; + \ No newline at end of file diff --git a/public_examples/edgekv.js b/public_examples/edgekv.js new file mode 100644 index 00000000..3dfcf375 --- /dev/null +++ b/public_examples/edgekv.js @@ -0,0 +1,346 @@ +/* +(c) Copyright 2020 Akamai Technologies, Inc. Licensed under Apache 2 license. +Version: 0.6.2 +Purpose: Provide a helper class to simplify the interaction with EdgeKV in an EdgeWorker. +Repo: https://github.com/akamai/edgeworkers-examples/tree/master/edgekv/lib +*/ + +import { TextDecoderStream } from 'text-encode-transform'; +import { WritableStream } from 'streams'; +import { httpRequest } from 'http-request'; +/** + * You must include edgekv_tokens.js in your bundle for this class to function properly. + * edgekv_tokens.js must include all namespaces you are going to use in the bundle. + */ +import { edgekv_access_tokens } from './edgekv_tokens.js'; + +export class EdgeKV { + #namespace; + #group; + #edgekv_uri; + #token_override; + #num_retries_on_timeout; + #sandbox_id; + #sandbox_fallback; + + /** + * Constructor to allow setting default namespace and group + * These defaults can be overriden when making individual GET, PUT, and DELETE operations + * @param {string} [$0.namepsace="default"] the default namespace to use for all GET, PUT, and DELETE operations + * Namespace must be 32 characters or less, consisting of A-Z a-z 0-9 _ or - + * @param {string} [$0.group="default"] the default group to use for all GET, PUT, and DELETE operations + * Group must be 128 characters or less, consisting of A-Z a-z 0-9 _ or - + * @param {number} [$0.num_retries_on_timeout=0] the number of times to retry a GET requests when the sub request times out + * @param {object} [$0.ew_request=null] passes the request object from the EdgeWorkers event handler to enable access to EdgeKV data in sandbox environments + * @param {boolean} [$0.sandbox_fallback=false] whether to fallback to retrieving staging data if the sandbox data does not exist, instead of returning null or the specified default value + */ + constructor(namespace = "default", group = "default") { + if (typeof namespace === "object") { + this.#namespace = namespace.namespace || "default"; + this.#group = namespace.group || "default"; + this.#edgekv_uri = namespace.edgekv_uri || "https://edgekv.akamai-edge-svcs.net"; + this.#token_override = namespace.token_override || null; + this.#num_retries_on_timeout = namespace.num_retries_on_timeout || 0; + this.#sandbox_id = (namespace.ew_request ? (namespace.ew_request.sandboxId || null) : null); + this.#sandbox_fallback = namespace.sandbox_fallback || false; + } else { + this.#namespace = namespace; + this.#group = group; + this.#edgekv_uri = "https://akab-2qyg6wxni4xgsfmr-cbg4lshd3gcnvwp5.luna.akamaiapis.net"; + this.#token_override = null; + this.#num_retries_on_timeout = 0; + this.#sandbox_id = null; + this.#sandbox_fallback = false; + } + } + + throwError(failed_reason, status, body) { + throw { + failed: failed_reason, + status: status, + body: body, + toString: function () { return JSON.stringify(this); } + }; + } + + async requestHandlerTemplate(http_request, handler_200, handler_large_200, error_text, default_value, num_retries_on_timeout) { + try { + let response = await http_request(); + switch (response.status) { + case 200: + // need to handle content length > 128000 bytes differently in EdgeWorkers + let contentLength = response.getHeader('Content-Length'); + if (!contentLength || contentLength.length == 0 || contentLength[0] >= 128000) { + return handler_large_200(response); + } else { + return handler_200(response); + } + case 404: + return default_value; + default: + let content = ""; + try { + content = await response.text(); + content = JSON.parse(content); + } catch (error) { } + throw { status: response.status, body: content }; // to be caught in surrounding catch block + } + } catch (error) { + if (num_retries_on_timeout > 0 && /^.*subrequest to URL.*timed out.*$/.test(error.toString())) { + return this.requestHandlerTemplate(http_request, handler_200, handler_large_200, error_text, default_value, num_retries_on_timeout - 1); + } + if (error.hasOwnProperty('status')) { + this.throwError(error_text + " FAILED", error.status, error.body); + } + this.throwError(error_text + " FAILED", 0, error.toString()); + } + } + + validate({ namespace = null, group = null, item = null }) { + if (!namespace || !/^[A-Za-z0-9_-]{1,32}$/.test(namespace)) { + throw "Namespace is not valid. Must be 32 characters or less, consisting of A-Z a-z 0-9 _ or -"; + } + if (!group || !/^[A-Za-z0-9_-]{1,128}$/.test(group)) { + throw "Group is not valid. Must be 128 characters or less, consisting of A-Z a-z 0-9 _ or -"; + } + if (!item || !/^[A-Za-z0-9_-]{1,512}$/.test(item)) { + throw "Item is not valid. Must be 512 characters or less, consisting of A-Z a-z 0-9 _ or -"; + } + } + + getNamespaceToken(namespace) { + if (this.#token_override) { + return this.#token_override; + } + let name = "namespace-" + namespace; + if (!(name in edgekv_access_tokens)) { + throw "MISSING ACCESS TOKEN. No EdgeKV Access Token defined for namespace '" + namespace + "'."; + } + return edgekv_access_tokens[name]["reference"]; + } + + addTimeout(options, timeout) { + if (timeout && (typeof timeout !== 'number' || !isFinite(timeout) || timeout <= 0 || timeout > 1000)) { + throw "Timeout is not valid. Must be a number greater than 0 and less than 1000."; + } + if (timeout) { + options.timeout = timeout; + } + return options; + } + + addSandboxId(uri) { + if (this.#sandbox_id) { + uri = uri + "?sandboxId=" + this.#sandbox_id; + if (this.#sandbox_fallback) { + uri = uri + "&sandboxFallback=true"; + } + } + return uri; + } + + async streamText(response_body) { + let result = ""; + await response_body + .pipeThrough(new TextDecoderStream()) + .pipeTo(new WritableStream({ + write(chunk) { + result += chunk; + } + }), { preventAbort: true }); + return result; + } + + async streamJson(response_body) { + return JSON.parse(await this.streamText(response_body)); + } + + putRequest({ namespace = this.#namespace, group = this.#group, item, value, timeout = null } = {}) { + this.validate({ namespace: namespace, group: group, item: item }); + let uri = this.#edgekv_uri + "/api/v1/namespaces/" + namespace + "/groups/" + group + "/items/" + item; + return httpRequest(this.addSandboxId(uri), this.addTimeout({ + method: "PUT", + body: typeof value === "object" ? JSON.stringify(value) : value, + headers: { "X-Akamai-EdgeDB-Auth": [this.getNamespaceToken(namespace)] } + }, timeout)); + } + + /** + * async PUT text into an item in the EdgeKV. + * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default + * @param {string} [$0.group=this.#group] specify a group other than the default + * @param {string} $0.item item key to put into the EdgeKV + * @param {string} $0.value text value to put into the EdgeKV + * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response + * @returns {Promise} if the operation was successful, the response from the EdgeKV + * @throws {object} if the operation was not successful, + * an object describing the non-200 response from the EdgeKV: {failed, status, body} + */ + async putText({ namespace = this.#namespace, group = this.#group, item, value, timeout = null } = {}) { + return this.requestHandlerTemplate( + () => this.putRequest({ namespace: namespace, group: group, item: item, value: value, timeout: timeout }), + (response) => response.text(), + (response) => this.streamText(response.body), + "PUT", + null, + 0 + ); + } + + /** + * PUT text into an item in the EdgeKV while only waiting for the request to send and not for the response. + * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default + * @param {string} [$0.group=this.#group] specify a group other than the default + * @param {string} $0.item item key to put into the EdgeKV + * @param {string} $0.value text value to put into the EdgeKV + * @throws {object} if the operation was not successful at sending the request, + * an object describing the error: {failed, status, body} + */ + putTextNoWait({ namespace = this.#namespace, group = this.#group, item, value } = {}) { + try { + this.putRequest({ namespace: namespace, group: group, item: item, value: value }); + } catch (error) { + this.throwError("PUT FAILED", 0, error.toString()); + } + } + + /** + * async PUT json into an item in the EdgeKV. + * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default + * @param {string} [$0.group=this.#group] specify a group other than the default + * @param {string} $0.item item key to put into the EdgeKV + * @param {object} $0.value json value to put into the EdgeKV + * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response + * @returns {Promise} if the operation was successful, the response from the EdgeKV + * @throws {object} if the operation was not successful, + * an object describing the non-200 response from the EdgeKV: {failed, status, body} + */ + async putJson({ namespace = this.#namespace, group = this.#group, item, value, timeout = null } = {}) { + return this.requestHandlerTemplate( + () => this.putRequest({ namespace: namespace, group: group, item: item, value: JSON.stringify(value), timeout: timeout }), + (response) => response.text(), + (response) => this.streamText(response.body), + "PUT", + null, + 0 + ); + } + + /** + * PUT json into an item in the EdgeKV while only waiting for the request to send and not for the response. + * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default + * @param {string} [$0.group=this.#group] specify a group other than the default + * @param {string} $0.item item key to put into the EdgeKV + * @param {object} $0.value json value to put into the EdgeKV + * @throws {object} if the operation was not successful at sending the request, + * an object describing the error: {failed, status, body} + */ + putJsonNoWait({ namespace = this.#namespace, group = this.#group, item, value } = {}) { + try { + this.putRequest({ namespace: namespace, group: group, item: item, value: JSON.stringify(value) }); + } catch (error) { + this.throwError("PUT FAILED", 0, error.toString()); + } + } + + getRequest({ namespace = this.#namespace, group = this.#group, item, timeout = null } = {}) { + this.validate({ namespace: namespace, group: group, item: item }); + let uri = this.#edgekv_uri + "/api/v1/namespaces/" + namespace + "/groups/" + group + "/items/" + item; + return httpRequest(this.addSandboxId(uri), this.addTimeout({ + method: "GET", + headers: { "X-Akamai-EdgeDB-Auth": [this.getNamespaceToken(namespace)] } + }, timeout)); + } + + /** + * async GET text from an item in the EdgeKV. + * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default + * @param {string} [$0.group=this.#group] specify a group other than the default + * @param {string} $0.item item key to get from the EdgeKV + * @param {string} [$0.default_value=null] the default value to return if a 404 response is returned from EdgeKV + * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response + * @param {number} [$0.num_retries_on_timeout=null] the number of times to retry a requests when the sub request times out + * @returns {Promise} if the operation was successful, the text response from the EdgeKV or the default_value on 404 + * @throws {object} if the operation was not successful, + * an object describing the non-200 and non-404 response from the EdgeKV: {failed, status, body} + */ + async getText({ namespace = this.#namespace, group = this.#group, item, default_value = null, timeout = null, num_retries_on_timeout = null } = {}) { + return this.requestHandlerTemplate( + () => this.getRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), + (response) => response.text(), + (response) => this.streamText(response.body), + "GET TEXT", + default_value, + num_retries_on_timeout ?? this.#num_retries_on_timeout + ); + } + + /** + * async GET json from an item in the EdgeKV. + * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default + * @param {string} [$0.group=this.#group] specify a group other than the default + * @param {string} $0.item item key to get from the EdgeKV + * @param {object} [$0.default_value=null] the default value to return if a 404 response is returned from EdgeKV + * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response + * @param {number} [$0.num_retries_on_timeout=null] the number of times to retry a requests when the sub request times out + * @returns {Promise} if the operation was successful, the json response from the EdgeKV or the default_value on 404 + * @throws {object} if the operation was not successful, + * an object describing the non-200 and non-404 response from the EdgeKV: {failed, status, body} + */ + async getJson({ namespace = this.#namespace, group = this.#group, item, default_value = null, timeout = null, num_retries_on_timeout = null } = {}) { + return this.requestHandlerTemplate( + () => this.getRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), + (response) => response.json(), + (response) => this.streamJson(response.body), + "GET JSON", + default_value, + num_retries_on_timeout ?? this.#num_retries_on_timeout + ); + } + + deleteRequest({ namespace = this.#namespace, group = this.#group, item, timeout = null } = {}) { + this.validate({ namespace: namespace, group: group, item: item }); + let uri = this.#edgekv_uri + "/api/v1/namespaces/" + namespace + "/groups/" + group + "/items/" + item; + return httpRequest(this.addSandboxId(uri), this.addTimeout({ + method: "DELETE", + headers: { "X-Akamai-EdgeDB-Auth": [this.getNamespaceToken(namespace)] } + }, timeout)); + } + + /** + * async DELETE an item in the EdgeKV. + * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default + * @param {string} [$0.group=this.#group] specify a group other than the default + * @param {string} $0.item item key to delete from the EdgeKV + * @param {number} [$0.timeout=null] the maximum time, between 1 and 1000 milliseconds, to wait for the response + * @returns {Promise} if the operation was successful, the text response from the EdgeKV + * @throws {object} if the operation was not successful, + * an object describing the non-200 response from the EdgeKV: {failed, status, body} + */ + async delete({ namespace = this.#namespace, group = this.#group, item, timeout = null} = {}) { + return this.requestHandlerTemplate( + () => this.deleteRequest({ namespace: namespace, group: group, item: item, timeout: timeout }), + (response) => response.text(), + (response) => this.streamText(response.body), + "DELETE", + null, + 0 + ); + } + + /** + * DELETE an item in the EdgeKV while only waiting for the request to send and not for the response. + * @param {string} [$0.namepsace=this.#namespace] specify a namespace other than the default + * @param {string} [$0.group=this.#group] specify a group other than the default + * @param {string} $0.item item key to delete from the EdgeKV + * @throws {object} if the operation was not successful at sending the request, + * an object describing the error: {failed, status, body} + */ + deleteNoWait({ namespace = this.#namespace, group = this.#group, item } = {}) { + try { + this.delete({ namespace: namespace, group: group, item: item }); + } catch (error) { + this.throwError("DELETE FAILED", 0, error.toString()); + } + } +} \ No newline at end of file diff --git a/public_examples/main.js b/public_examples/main.js new file mode 100644 index 00000000..a2a6fc5d --- /dev/null +++ b/public_examples/main.js @@ -0,0 +1,37 @@ +/* +(c) Copyright 2019 Akamai Technologies, Inc. Licensed under Apache 2 license. +Version: 0.1 +Purpose: Respond with empty JSON if cart cookie not part of request. +*/ +import { EdgeKV } from "./edgekv.js"; +import { Rule, defaultEngine, Operator } from "js-rules-engine"; +import { logger } from "log"; + +init(); + + +function init() { + const caseInsensitivelyEquals = new Operator( + "caseInsensitivelyEquals", + function (a, b) { + return a.toUpperCase() === b.toUpperCase(); + } + ); + + defaultEngine.addOperator(caseInsensitivelyEquals); + +} + + +export async function responseProvider(request) +{ + //logger.log(`In responseProvider function request - ${JSON.stringify(decodeURI(request))}`); + try + { + //let dccFeatureObj = JSON.parse(request.getVariable("PMUSER_DCC_CONFIG")); + const edgeKv = new EdgeKV({ namespace: "edgekv-token-test", group: "1111" }); + logger.log('responseProvider function edgeKv: ', edgeKv); + }catch(error){ + logger.log('responseProvider function error: ', error); + } +} \ No newline at end of file