-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Added I/O events sample. * Added EOF empty lines and license headers. * Adding EOF empty line and README updated. * Updated README with correct event payload. * Creating parent folder to hold IO events apps. --------- Co-authored-by: Gabriel Ranghetti <gabriel.ranghetti@toyota.com>
- Loading branch information
1 parent
d241ce3
commit 1897a20
Showing
11 changed files
with
14,115 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# Adobe I/O Events Applications | ||
|
||
This section contains a collection of application examples that demonstrate the use of Adobe I/O Events. | ||
Each application serves as a example demonstrating how to effectively use Adobe I/O Events to take action when a shopper performs an action on an Adobe product. | ||
|
||
## Applications List | ||
|
||
The applications covered in this section include: | ||
|
||
- Adobe Commerce customer login. | ||
|
||
Please refer to the README file of each individual application for more details. | ||
|
||
## Getting Started | ||
|
||
To get started with these applications, simply navigate to the directory of the application you are interested in and follow the instructions provided in its README file. | ||
|
||
## Note | ||
|
||
Please note that this section is still a **work in progress**. Application examples will be available soon. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"env": { | ||
"es6": true, | ||
"node": true | ||
}, | ||
"extends": ["eslint:recommended", "plugin:jest/recommended"], | ||
"parserOptions": { | ||
"ecmaVersion": "latest" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
|
||
# package directories | ||
node_modules | ||
jspm_packages | ||
|
||
# build | ||
build | ||
dist | ||
.manifest-dist.yml | ||
|
||
# Config | ||
config.json | ||
.env* | ||
.aio | ||
|
||
# Adobe I/O console config | ||
console.json | ||
|
||
# Test output | ||
junit.xml | ||
|
||
# IDE & Temp | ||
.cache | ||
.idea | ||
.nyc_output | ||
.vscode | ||
coverage | ||
.aws.tmp.creds.json | ||
.wskdebug.props.tmp | ||
|
||
# Parcel | ||
.parcel-cache | ||
|
||
# OSX | ||
.DS_Store | ||
|
||
# yeoman | ||
.yo-repository | ||
|
||
# logs folder for aio-run-detached | ||
logs | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Adobe I/O Events Sample | ||
|
||
This is an example for I/O Events. | ||
|
||
## Table of Contents | ||
|
||
- [Introduction](#introduction) | ||
- [Prerequisites](#prerequisites) | ||
- [Explanation](#explanation) | ||
|
||
## Introduction | ||
|
||
With Adobe I/O Events, developers can create event-driven applications that take action when a shopper performs an action on an Adobe product. In this example the app will listen to the Adobe Commerce ```observer.customer_login``` event, get the payload and send to a Slack channel. | ||
|
||
## Prerequisites | ||
|
||
Before you begin, ensure you have the following: | ||
|
||
- An Adobe Developer account | ||
- Node.js and npm installed on your local machine (nvm 18.x.x (Mac/Linux) or nvm-windows (Windows)) | ||
- App Builder project created and configured to work with I/O Events. | ||
- A Slack App configured to receive incoming webhooks. See link: [Slack API](https://api.slack.com/messaging/webhooks) | ||
|
||
## Explanation | ||
|
||
The runtime action ```events/customer-login``` will receive the Commerce event payload and send to the configured Slack channel. | ||
|
||
### Commerce configured io_events.xml file: | ||
|
||
```xml | ||
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_AdobeCommerceEventsClient:etc/io_events.xsd"> | ||
<event name="observer.customer_login"> | ||
<fields> | ||
<field name="customer.firstname" /> | ||
<field name="customer.lastname" /> | ||
</fields> | ||
</event> | ||
</config> | ||
``` | ||
|
||
### Commerce event payload example: | ||
|
||
```json | ||
{ | ||
"data": { | ||
"key": "4fbbb851-285f-4733-a3b4-77556e019691", | ||
"value": { | ||
"customer": { | ||
"firstname": "John", | ||
"lastname": "Doe" | ||
} | ||
}, | ||
"source": "evergreen.evergreen_staging", | ||
"_metadata": { | ||
"commerceEdition": "Adobe Commerce + B2B", | ||
"commerceVersion": "2.4.6-p4", | ||
"eventsClientVersion": "1.5.0", | ||
"storeId": "1", | ||
"websiteId": "1", | ||
"storeGroupId": "1" | ||
} | ||
}, | ||
"id": "f7813962-c119-401c-aa8c-266421aa1053", | ||
"source": "urn:uuid:420ba332-4e1c-4373-8e23-81175c9e79fd", | ||
"specversion": "1.0", | ||
"type": "com.adobe.commerce.observer.customer_login", | ||
"datacontenttype": "application/json", | ||
"time": "2024-06-04T16:05:04.542Z", | ||
"event_id": "7c9ae597-f382-49bc-85b7-3a2b4da6e0bf", | ||
"recipient_client_id": "531f2f763b2f4c34b03001d8bf2bd4df" | ||
} | ||
``` |
84 changes: 84 additions & 0 deletions
84
events/commerce-customer-login/actions/customer-login/index.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
Copyright 2024 Adobe. All rights reserved. | ||
This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. You may obtain a copy | ||
of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
OF ANY KIND, either express or implied. See the License for the specific language | ||
governing permissions and limitations under the License. | ||
*/ | ||
|
||
/** | ||
* This is a sample action showcasing how to access an external API | ||
* | ||
* Note: | ||
* You might want to disable authentication and authorization checks against Adobe Identity Management System for a generic action. In that case: | ||
* - Remove the require-adobe-auth annotation for this action in the manifest.yml of your application | ||
* - Remove the Authorization header from the array passed in checkMissingRequestInputs | ||
* - The two steps above imply that every client knowing the URL to this deployed action will be able to invoke it without any authentication and authorization checks against Adobe Identity Management System | ||
* - Make sure to validate these changes against your security requirements before deploying the action | ||
*/ | ||
|
||
|
||
const fetch = require('node-fetch') | ||
const { Core } = require('@adobe/aio-sdk') | ||
const { errorResponse, stringParameters, checkMissingRequestInputs } = require('../utils') | ||
|
||
// main function that will be executed by Adobe I/O Runtime | ||
const main = async params => { | ||
// create a Logger | ||
const logger = Core.Logger('main', { level: params.LOG_LEVEL || 'info' }) | ||
|
||
try { | ||
// 'info' is the default level if not set | ||
logger.info('Calling the main action of customer-login') | ||
|
||
// log parameters, only if params.LOG_LEVEL === 'debug' | ||
logger.debug(stringParameters(params)) | ||
|
||
// check for missing request input parameters and headers | ||
const requiredHeaders = [] | ||
const errorMessage = checkMissingRequestInputs(params, requiredHeaders) | ||
if (errorMessage) { | ||
// return and log client errors | ||
return errorResponse(400, errorMessage, logger) | ||
} | ||
|
||
// post the message to external api endpoint | ||
var slackText = "Customer Login - " + JSON.stringify(params) | ||
|
||
const payload = { | ||
"text": slackText | ||
} | ||
|
||
const res = await fetch(params.SLACK_WEBHOOK, { | ||
method: 'POST', | ||
headers: { | ||
'Content-Type': 'application/json' | ||
}, | ||
body: JSON.stringify(payload) | ||
}) | ||
if (!res.ok) { | ||
return errorResponse(res.status, 'Something is wrong with your Slack webhook URL.', logger) | ||
} | ||
|
||
const response = { | ||
statusCode: 200, | ||
body: { | ||
message: "Commerce event information sent successfully." | ||
} | ||
} | ||
|
||
// log the response status code | ||
logger.info(`${response.statusCode}: successful request`) | ||
return response | ||
} catch (error) { | ||
// log any server errors | ||
logger.error(error) | ||
// return with 500 | ||
return errorResponse(500, 'server error', logger) | ||
} | ||
}; | ||
|
||
exports.main = main |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
/* | ||
Copyright 2024 Adobe. All rights reserved. | ||
This file is licensed to you under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. You may obtain a copy | ||
of the License at http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS | ||
OF ANY KIND, either express or implied. See the License for the specific language | ||
governing permissions and limitations under the License. | ||
*/ | ||
|
||
/* This file exposes some common utilities for your actions */ | ||
|
||
/** | ||
* | ||
* Returns a log ready string of the action input parameters. | ||
* The `Authorization` header content will be replaced by '<hidden>'. | ||
* | ||
* @param {object} params action input parameters. | ||
* | ||
* @returns {string} | ||
* | ||
*/ | ||
function stringParameters (params) { | ||
// hide authorization token without overriding params | ||
let headers = params.__ow_headers || {} | ||
if (headers.authorization) { | ||
headers = { ...headers, authorization: '<hidden>' } | ||
} | ||
return JSON.stringify({ ...params, __ow_headers: headers }) | ||
} | ||
|
||
/** | ||
* | ||
* Returns the list of missing keys giving an object and its required keys. | ||
* A parameter is missing if its value is undefined or ''. | ||
* A value of 0 or null is not considered as missing. | ||
* | ||
* @param {object} obj object to check. | ||
* @param {array} required list of required keys. | ||
* Each element can be multi level deep using a '.' separator e.g. 'myRequiredObj.myRequiredKey' | ||
* | ||
* @returns {array} | ||
* @private | ||
*/ | ||
function getMissingKeys (obj, required) { | ||
return required.filter(r => { | ||
const splits = r.split('.') | ||
const last = splits[splits.length - 1] | ||
const traverse = splits.slice(0, -1).reduce((tObj, split) => { tObj = (tObj[split] || {}); return tObj }, obj) | ||
return traverse[last] === undefined || traverse[last] === '' // missing default params are empty string | ||
}) | ||
} | ||
|
||
/** | ||
* | ||
* Returns the list of missing keys giving an object and its required keys. | ||
* A parameter is missing if its value is undefined or ''. | ||
* A value of 0 or null is not considered as missing. | ||
* | ||
* @param {object} params action input parameters. | ||
* @param {array} requiredHeaders list of required input headers. | ||
* @param {array} requiredParams list of required input parameters. | ||
* Each element can be multi level deep using a '.' separator e.g. 'myRequiredObj.myRequiredKey'. | ||
* | ||
* @returns {string} if the return value is not null, then it holds an error message describing the missing inputs. | ||
* | ||
*/ | ||
function checkMissingRequestInputs (params, requiredParams = [], requiredHeaders = []) { | ||
let errorMessage = null | ||
|
||
// input headers are always lowercase | ||
requiredHeaders = requiredHeaders.map(h => h.toLowerCase()) | ||
// check for missing headers | ||
const missingHeaders = getMissingKeys(params.__ow_headers || {}, requiredHeaders) | ||
if (missingHeaders.length > 0) { | ||
errorMessage = `missing header(s) '${missingHeaders}'` | ||
} | ||
|
||
// check for missing parameters | ||
const missingParams = getMissingKeys(params, requiredParams) | ||
if (missingParams.length > 0) { | ||
if (errorMessage) { | ||
errorMessage += ' and ' | ||
} else { | ||
errorMessage = '' | ||
} | ||
errorMessage += `missing parameter(s) '${missingParams}'` | ||
} | ||
|
||
return errorMessage | ||
} | ||
|
||
/** | ||
* | ||
* Extracts the bearer token string from the Authorization header in the request parameters. | ||
* | ||
* @param {object} params action input parameters. | ||
* | ||
* @returns {string|undefined} the token string or undefined if not set in request headers. | ||
* | ||
*/ | ||
function getBearerToken (params) { | ||
if (params.__ow_headers && | ||
params.__ow_headers.authorization && | ||
params.__ow_headers.authorization.startsWith('Bearer ')) { | ||
return params.__ow_headers.authorization.substring('Bearer '.length) | ||
} | ||
return undefined | ||
} | ||
/** | ||
* | ||
* Returns an error response object and attempts to log.info the status code and error message | ||
* | ||
* @param {number} statusCode the error status code. | ||
* e.g. 400 | ||
* @param {string} message the error message. | ||
* e.g. 'missing xyz parameter' | ||
* @param {*} [logger] an optional logger instance object with an `info` method | ||
* e.g. `new require('@adobe/aio-sdk').Core.Logger('name')` | ||
* | ||
* @returns {object} the error object, ready to be returned from the action main's function. | ||
* | ||
*/ | ||
function errorResponse (statusCode, message, logger) { | ||
if (logger && typeof logger.info === 'function') { | ||
logger.info(`${statusCode}: ${message}`) | ||
} | ||
return { | ||
error: { | ||
statusCode, | ||
body: { | ||
error: message | ||
} | ||
} | ||
} | ||
} | ||
|
||
module.exports = { | ||
errorResponse, | ||
getBearerToken, | ||
stringParameters, | ||
checkMissingRequestInputs | ||
} |
Oops, something went wrong.