Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fpco 26696 - Framework agnostic final PR #62

Open
wants to merge 68 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
6f57e0a
Framework agnostic flow initial commit
jinalviranii Dec 6, 2023
f0eed97
Add backward compatible express file path
jinalviranii Dec 7, 2023
10cd0a7
Add test case for webhook integration
jinalviranii Dec 11, 2023
8382fad
Initial commit for fastify
jinalviranii Dec 17, 2023
d294cd9
Fix cookie issue
jinalviranii Dec 18, 2023
a4d6279
Fix cookie issue
jinalviranii Dec 18, 2023
297408f
POC
jinalviranii Dec 20, 2023
aeea2f7
Update build
jinalviranii Dec 26, 2023
4800dcf
Update package dependecy
jinalviranii Dec 26, 2023
22274a9
Generated typing for entire library
jinalviranii Dec 27, 2023
bc4afda
add package.json in build folder
jinalviranii Dec 27, 2023
1ef6a14
rename controller
jinalviranii Dec 27, 2023
2514d21
Rename controller spec name
jinalviranii Dec 27, 2023
14b43e2
Add nest js middleware
jinalviranii Dec 27, 2023
bbcf459
Fix applicationProxyRoutes middleware
jinalviranii Dec 29, 2023
854e98b
update typings
jinalviranii Dec 29, 2023
24e0d78
Add named function for handlers.
jinalviranii Jan 3, 2024
10c0522
Add example for custom oauth support
jinalviranii Jan 3, 2024
9a961f8
Resolve review comments
jinalviranii Jan 3, 2024
22a8d69
Add appropriate comments
jinalviranii Jan 3, 2024
da1339f
Add debug mode for SDK function
jinalviranii Jan 6, 2024
495f152
update readme file for base storage class
jinalviranii Jan 6, 2024
82307e0
updated readme file
jinalviranii Jan 8, 2024
ee37704
Add comment
jinalviranii Jan 8, 2024
b017e10
Readme file content
jinalviranii Jan 11, 2024
e06ec42
Agnostic change for master pull
jinalviranii Feb 13, 2024
8f6c8a0
update parameter table link in readme
jinalviranii Feb 14, 2024
503412a
Merge branch 'main' into fpco-26696
jinalviranii Feb 20, 2024
5c00bd4
update package json version
jinalviranii Feb 20, 2024
f14171c
Master pull
jinalviranii Aug 26, 2024
7dc2542
Merge branch 'main' into fpco-26696
jinalviranii Aug 26, 2024
fc265c4
CI/CD test case setup
jinalviranii Aug 27, 2024
48b2016
Add redis in CI/Cd
jinalviranii Aug 27, 2024
6ae6cfd
Add build for nest js
jinalviranii Aug 27, 2024
966bce6
Remove .map files
jinalviranii Aug 28, 2024
22eaf15
Add CI/CD test case
jinalviranii Aug 28, 2024
69a420a
Add redis in CI/CD
jinalviranii Aug 28, 2024
858a4a8
Try install in CI/CD
jinalviranii Aug 28, 2024
5442304
Fix redis conn issue
jinalviranii Aug 28, 2024
1ff521e
Run for ubuntu
jinalviranii Aug 28, 2024
fa8c048
Test CI/CD
jinalviranii Aug 28, 2024
34e150b
Test for other plateforms
jinalviranii Aug 28, 2024
79c02e5
Test redis for windows
jinalviranii Aug 28, 2024
5e84bad
Test CI/CD using redis install for diff os
jinalviranii Aug 28, 2024
0b845ce
Remove redis for windows
jinalviranii Aug 28, 2024
292498e
Test for windows
jinalviranii Aug 28, 2024
f554bde
Add redis in docker
jinalviranii Aug 28, 2024
e8d7739
Test
jinalviranii Aug 28, 2024
31d6b28
Test
jinalviranii Aug 28, 2024
63e8c01
Test
jinalviranii Aug 28, 2024
8d8fb40
Move common files to lob folder
jinalviranii Aug 29, 2024
52bd5a8
Add typeing to types folder
jinalviranii Aug 29, 2024
52577b0
Add types folder
jinalviranii Aug 29, 2024
32a4e23
Update readme file
jinalviranii Aug 29, 2024
7ca8835
Update readme file
jinalviranii Aug 29, 2024
18b8452
Remove types folder from coverage
jinalviranii Aug 29, 2024
bf082cf
Remove storage classs from express
jinalviranii Aug 30, 2024
fc38d1e
Fix code review comments
jinalviranii Sep 3, 2024
56b544c
Update test case and & coverage support in readme
jinalviranii Sep 9, 2024
68c9096
Fix test case
jinalviranii Sep 9, 2024
c57cd0b
Update lock file
jinalviranii Sep 9, 2024
b355b55
Update packages
jinalviranii Sep 9, 2024
13b6d51
Fix file path for windows test case
jinalviranii Sep 9, 2024
e82d9c8
Update path
jinalviranii Sep 9, 2024
d7eb41a
Debug windows test fail
jinalviranii Sep 9, 2024
c3aea7e
Update test case cmd
jinalviranii Sep 9, 2024
c953588
Update test case cmd
jinalviranii Sep 9, 2024
0854b1a
Update relative url in readme file
jinalviranii Sep 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"presets": ["@babel/preset-env"],
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}
40 changes: 40 additions & 0 deletions .github/workflows/call_test_cases.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Run test cases

on: [pull_request, push]

jobs:
run-test-cases:
runs-on: ${{ matrix.os }}

strategy:
matrix:
os: [
ubuntu-latest,
windows-latest,
macos-latest,
]
node-version: [16.x, 18.x, 20.x]

steps:
- name: Get branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: get_branch

- uses: actions/checkout@v2

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}

- name: Install packages and run test cases
run: |
npm install
npm run test

- name: Coveralls (Uploading test report)
if: ${{ steps.get_branch.outputs.branch == 'main' }}
uses: coverallsapp/github-action@main
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v0.8.0] - 2024-08-26
### Added
- Added support for new frameworks such as Fastify and NestJS.
- Enabled integration of extension library into any user-preferred framework.
- Introduced support to access `getPlatformClient`, `getPartnerClient`, and `getApplicationClient` via the `setupFdk` function.
- Implemented support for `getSessionData` function to retrieve valid user sessions, accessible through the `setupFdk` function.
- Implemented support for `getUserData` function to fetch user data via the `setupFdk` function.
- Implemented support for `getApplicationConfig` function to acquire `application`, `applicationConfig`, and `applicationClient` objects through the `setupFdk` function.
### Changed
- Updated README documentation to include instructions for integrating the extension library within frameworks such as Fastify, NestJS, or any other preferred frameworks.
- Updated README documentation to include guidelines for implementing custom storage classes, enabling the integration of extension storage into the user's preferred database.
---
## [v0.7.9] - 2024-08-20
### Changed
- Use api.fynd.com as default cluster domain for webhooks.
Expand Down
254 changes: 205 additions & 49 deletions README.md

Large diffs are not rendered by default.

7 changes: 0 additions & 7 deletions examples/extension.handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ async function auth(req) {
return req.extension.base_url + `?company_id=${req.query.company_id}`;
}

async function autoInstall(req) {
// Write you code here to do any special handling on auto install event is received.
// Here any handling on auth call back should under here as well since this event will be called when extension is installed
// but auth callback won't be triggered until user launches extension
}

async function uninstall(req) {
// Write your code here to cleanup data related to extension
// If task is time taking then process it async on other process.
Expand All @@ -19,5 +13,4 @@ async function uninstall(req) {
module.exports = {
auth: auth,
uninstall: uninstall,
auto_install: autoInstall
};
2 changes: 1 addition & 1 deletion examples/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const cookieParser = require('cookie-parser');
const {
setupFdk
} = require("../express");
const { RedisStorage } = require("./../express/storage");
const { RedisStorage } = require("./../storage");
const extensionHandler = require("./extension.handler");
const Redis = require("ioredis");

Expand Down
193 changes: 193 additions & 0 deletions express/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# fdk-extension-javascript

This library facilitates seamless configuration of authentication for accessing Fynd Platform APIs and webhook subscription.

#### Initial Setup

```javascript
const express = require("express");
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
const { setupFdk } = require("fdk-extension-javascript/express");
const { RedisStorage } = require("fdk-extension-javascript/express/storage"); // Import RedisStorage (default storage class). Use your custom class if you have implemented other databases.
const Redis = require("ioredis");

const app = express();
app.use(cookieParser("ext.session"));
app.use(bodyParser.json({ limit: "2mb" }));

const redis = new Redis();

let extensionHandler = {
auth: async function (data) {
console.log("called auth callback");
},

uninstall: async function (data) {
console.log("called uninstall callback");
},
};

let fdkClient = setupFdk({
api_key: "<API_KEY>", // API Key of an extension for authentication.
api_secret: "<API_SECRET>", // API Secret of an extension for authentication.
base_url: baseUrl, // optional. Base URL for extension.
scopes: ["company/products"], // optional. An array of scopes indicating the specific permissions needed for an extension.
callbacks: extensionHandler, // The callback function to handle extension-related tasks.
storage: new RedisStorage(redis), // An instance of storage (e.g., RedisStorage) for data storage.
access_mode: "offline", // Access mode of an extension. It can be `online` or `offline`.
cluster: "https://api.fyndx0.de", // optional. The API url of the Fynd Platform cluster.
});
app.use(fdkClient.fdkHandler);

app.listen(8080);
```
Refer to [paramerter](https://github.com/gofynd/fdk-extension-javascript/blob/fpco-26696/README.md#parameters-of-setupfdk-function) table of a setupFdk function.

#### How to call platform apis?

To call platform api you need to have instance of `PlatformClient`. This instance holds methods for SDK classes. All routes registered under `platformApiRoutes`, express router will have `platformClient` under request object which is instance of `PlatformClient`.

> Here `platformApiRoutes` has middleware attached which allows passing such request which are called after launching extension under any company.

```javascript
fdkClient.platformApiRoutes.get("/test/routes", async (req, res, next) => {
try {
let data = await req.platformClient.lead.getTickets();
res.json(data);
} catch (error) {
console.error(error);
next(error);
}
});

app.use(fdkClient.platformApiRoutes);
```

#### How to call platform apis in background tasks?

Background tasks running under some consumer or webhook or under any queue can get platform client via method `getPlatformClient`. It will return instance of `PlatformClient` as well.

> Here FdkClient `access_mode` should be **offline**. Cause such client can only access PlatformClient in background task.

```javascript
function backgroundHandler(companyId) {
try {
const platformClient = await fdkExtension.getPlatformClient(companyId);
let data = await platformClient.lead.getTickets();
// Some business logic here
res.json({ success: true });
} catch (err) {
console.error(err);
res.status(404).json({ success: false });
}
}
```

#### How to call application apis?

To interact with an application's API using Express, you must have an instance of `ApplicationClient`. This instance holds methods for SDK classes. All routes registered under `applicationProxyRoutes`, express router will have `applicationClient` under request object.

```javascript
fdkClient.applicationProxyRoutes.get("/app/routes", async (req, res, next) => {
try {
let data = await req.applicationClient.lead.getTickets();
res.json(data);
} catch (error) {
console.error(error);
next(error);
}
});

app.use(fdkClient.applicationProxyRoutes);
```

#### How to call partner apis?

To call partner api you need to have instance of `PartnerClient`. Instance holds methods for SDK classes. All routes registered under `partnerApiRoutes` express router will have `partnerClient` under request object which is instance of `PartnerClient`.

> Here `partnerApiRoutes` has middleware attached which allows passing such request which are called after launching extension under any company.

```javascript
fdkClient.partnerApiRoutes.get("/test/routes", async (req, res, next) => {
try {
let data = await req.partnerClient.theme.getThemes();
res.json(data);
} catch (error) {
console.error(error);
next(error);
}
});

app.use(fdkClient.partnerApiRoutes);
```

#### How to register for webhook events?

Webhook events can be helpful to handle tasks when certain events occur on platform. You can subscribe to such events by passing `webhook_config` in setupFdk function.
Please refer [webhook documentation](https://partners.fynd.com/help/docs/partners/webhooks/webhook-events/article#payload) to know about event payload and it's structure.

```javascript

let fdkClient = setupFdk({
api_key: "<API_KEY>",
api_secret: "<API_SECRET>",
base_url: baseUrl,
scopes: ["company/products"],
callbacks: extensionHandler,
storage: new RedisStorage(redis),
access_mode: "offline",
cluster: "https://api.fyndx0.de",
webhook_config: {
api_path: "/api/v1/webhooks", // API endpoint to process webhooks event.
notification_email: "test@abc.com", // Email address for webhook related notifications.
subscribe_on_install: false, // optional. Whether to auto subscribe to all webhooks on extension installation. It can be true or false.
subscribed_saleschannel: 'specific', // optional. If `specific` then you have to manually subscribe to sales channel/website level events for individual sales channels. Value can be `all` or `optional`.
event_map: { // required
'company/brand/create': { // Event topic name follows {category}/{name}/{type} structure. Refer event payload to get 'category', 'name' and 'type' for required events
version: '1', // API version of specified event
handler: handleBrandCreate // A handler function when specified event occures
},
'company/location/update': {
version: '1',
handler: handleLocationUpdate
},
'application/coupon/create': {
version: '1',
handler: handleCouponCreate
}
}
},
debug: true // optional. Enable debug logs if it is `true`. Value can be `true` or `false`.
});
```
> By default all webhook events all subscribed for all companies whenever they are installed. To disable this behavior set `subscribe_on_install` to `false`. If `subscribe_on_install` is set to false, you need to manually enable webhook event subscription by calling `syncEvents` method of `webhookRegistry`

There should be view on given api path to receive webhook call. It should be `POST` api path. Api view should call `processWebhook` method of `webhookRegistry` object available under `fdkClient` here.

> Here `processWebhook` will do payload validation with signature and calls individual handlers for event passed with webhook config.

```javascript

// Register webhooks after OAuth completes
app.post('/api/v1/webhooks', async (req, res, next) => {
try {
await fdkClient.webhookRegistry.processWebhook(req);
return res.status(200).json({"success": true});
}
catch(err) {
logger.error(err);
return res.status(500).json({"success": false});
}
});
```

> Setting `subscribed_saleschannel` as "specific" means, you will have to manually subscribe saleschannel level event for individual saleschannel. Default value here is "all" and event will be subscribed for all sales channels. For enabling events manually use function `enableSalesChannelWebhook`. To disable receiving events for a saleschannel use function `disableSalesChannelWebhook`.


##### How webhook registery subscribes to webhooks on Fynd Platform?
After webhook config is passed to setupFdk whenever extension is launched to any of companies where extension is installed or to be installed, webhook config data is used to create webhook subscriber on Fynd Platform for that company.

> Any update to webhook config will not automatically update subscriber data on Fynd Platform for a company until extension is opened atleast once after the update.

Other way to update webhook config manually for a company is to call `syncEvents` function of webhookRegistery.
25 changes: 9 additions & 16 deletions express/api_routes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
'use strict';
const { extension } = require('./extension');
const { extension } = require('../lib/extension');
const express = require('express');
const { sessionMiddleware, partnerSessionMiddleware } = require('./middleware/session_middleware');
const { sessionMiddleware, partnerSessionMiddleware } = require('../lib/middleware/session_middleware');
const { getApplicationConfig, getUserData } = require('../lib/utils');
const { ApplicationConfig, ApplicationClient } = require("@gofynd/fdk-client-javascript");


Expand All @@ -12,20 +13,12 @@ function setupProxyRoutes(configData) {

applicationProxyRoutes.use(async (req, res, next) => {
try {
if (req.headers["x-user-data"]) {
req.user = JSON.parse(req.headers["x-user-data"]);
req.user.user_id = req.user._id;
}
if (req.headers["x-application-data"]) {
req.application = JSON.parse(req.headers["x-application-data"]);
req.applicationConfig = new ApplicationConfig({
applicationID: req.application._id,
applicationToken: req.application.token,
domain: extension.cluster,
logLevel: configData.debug === true? "debug": null
});
req.applicationClient = new ApplicationClient(req.applicationConfig);
}
const user = await getUserData(req.headers["x-user-data"]);
const { application, applicationConfig, applicationClient } = await getApplicationConfig(req.headers["x-application-data"], extension)
req.user = user;
req.application = application;
req.applicationConfig = applicationConfig;
req.applicationClient = applicationClient;
next();
} catch (error) {
next(error);
Expand Down
Loading
Loading