@slack/interactive-messages
Advanced tools
Comparing version 1.3.0 to 1.4.0
@@ -14,3 +14,3 @@ /// <reference types="node" /> | ||
/** | ||
* The number of milliseconds to wait before flushing a syncrhonous response to an incoming request and falling back | ||
* The number of milliseconds to wait before flushing a synchronous response to an incoming request and falling back | ||
* to an asynchronous response. | ||
@@ -31,3 +31,3 @@ */ | ||
* @param signingSecret - Slack app signing secret used to authenticate request | ||
* @param options.syncResponseTimeout - number of milliseconds to wait before flushing a syncrhonous response to an | ||
* @param options.syncResponseTimeout - number of milliseconds to wait before flushing a synchronous response to an | ||
* incoming request and falling back to an asynchronous response. | ||
@@ -80,5 +80,5 @@ * @param options.lateResponseFallbackEnabled - whether or not promises that resolve after the syncResponseTimeout can | ||
* :-----:|:-----:|:-----:|:-----:|:-----:|:-----: | ||
* **Button Press**| Message in response | When resolved before `syncResposeTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | Create a new message instead of replacing using `replace_original: false` | ||
* **Menu Selection**| Message in response | When resolved before `syncResposeTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | Create a new message instead of replacing using `replace_original: false` | ||
* **Message Action** | Message in response | When resolved before `syncResposeTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | | ||
* **Button Press**| Message in response | When resolved before `syncResponseTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | Create a new message instead of replacing using `replace_original: false` | ||
* **Menu Selection**| Message in response | When resolved before `syncResponseTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | Create a new message instead of replacing using `replace_original: false` | ||
* **Message Action** | Message in response | When resolved before `syncResponseTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | | ||
* **Dialog Submission**| Error list in response | Error list in response | Empty response | Message in request to `response_url` | Returning a Promise that takes longer than 3 seconds to resolve can result in the user seeing an error. Warning logged if a promise isn't completed before `syncResponseTimeout`. | ||
@@ -109,2 +109,27 @@ * | ||
/** | ||
* Add a handler for view submission. | ||
* | ||
* The value returned from the `callback` determines the response sent back to Slack. The handler can return a plain | ||
* object with a `response_action` property to dismiss the modal, push a view into the modal, display validation | ||
* errors, or update the view. Alternatively, the handler can return a Promise for this kind of object, which resolves | ||
* before `syncResponseTimeout` or `lateResponseFallbackEnabled: false`, to perform the same response actions. If the | ||
* Promise resolves afterwards or `lateResponseFallbackEnabled: true` then the modal will be dismissed. If the handler | ||
* returns `undefined` the modal will be dismissed. | ||
* | ||
* @param matchingConstraints - the callback ID (as a string or RegExp) or an object describing the constraints to | ||
* match view submissions for the handler. | ||
* @param callback - the function to run when an view submission is matched | ||
* @returns this instance (for chaining) | ||
*/ | ||
viewSubmission(matchingConstraints: string | RegExp | ViewConstraints, callback: ViewSubmissionHandler): this; | ||
/** | ||
* Add a handler for view closed interaction. The handler should not return a value. | ||
* | ||
* @param matchingConstraints - the callback ID (as a string or RegExp) or an object describing the constraints to | ||
* match view closed interactions for the handler. | ||
* @param callback - the function to run when an view closed interaction is matched | ||
* @returns this instance (for chaining) | ||
*/ | ||
viewClosed(matchingConstraints: string | RegExp | ViewConstraints, callback: ViewClosedHandler): this; | ||
/** | ||
* Dispatches the contents of an HTTP request to the registered handlers. | ||
@@ -193,2 +218,19 @@ * | ||
/** | ||
* Constraints on when to call a view submission or view closed handler. | ||
*/ | ||
export interface ViewConstraints { | ||
/** | ||
* A string or RegExp to match against the `callback_id` | ||
*/ | ||
callbackId?: string | RegExp; | ||
/** | ||
* A string to match against the `external_id` | ||
*/ | ||
externalId?: string | RegExp; | ||
/** | ||
* A string to match against the `view_id` | ||
*/ | ||
viewId?: string; | ||
} | ||
/** | ||
* A function used to send message updates after an action is handled. This function can be used | ||
@@ -221,3 +263,3 @@ * up to 5 times in 30 minutes. | ||
* this object is a list of [validation errors](https://api.slack.com/dialogs#input_validation). It may also be a | ||
* Promise for a list of validation errors, and if so and the Promise takes longer than the `syncReponseTimeout` to | ||
* Promise for a list of validation errors, and if so and the Promise takes longer than the `syncResponseTimeout` to | ||
* complete, Slack will display an error to the user. If there is no return value, then button presses and menu | ||
@@ -241,2 +283,14 @@ * selections do not update the message and dialog submissions will validate and dismiss. | ||
declare type OptionsHandler = (payload: any) => any | Promise<any> | undefined; | ||
/** | ||
* A handler function for view submission requests. | ||
* | ||
* TODO: describe the payload and return values more specifically? | ||
*/ | ||
declare type ViewSubmissionHandler = (payload: any) => any | Promise<any> | undefined; | ||
/** | ||
* A handler function for view closed requests. | ||
* | ||
* TODO: describe the payload and return values more specifically? | ||
*/ | ||
declare type ViewClosedHandler = (payload: any) => void; | ||
//# sourceMappingURL=adapter.d.ts.map |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
@@ -82,2 +83,5 @@ }); | ||
} | ||
if (!hasBlockRelatedConstraints(matchingConstraints)) { | ||
return false; | ||
} | ||
if (!util_1.isFalsy(matchingConstraints.blockId) && | ||
@@ -105,6 +109,24 @@ !(lodash_isstring_1.default(matchingConstraints.blockId) || lodash_isregexp_1.default(matchingConstraints.blockId))) { | ||
} | ||
// We don't need to validate unfurl, we'll just cooerce it to a boolean | ||
// We don't need to validate unfurl, we'll just coerce it to a boolean | ||
return false; | ||
} | ||
/** | ||
* Validates properties of a matching constraints object specific to registering a view submission or view closed | ||
* request | ||
* @param viewConstraints - object describing the constraints on a view submission or view closed handler | ||
* @returns `false` represents successful validation, an error represents failure and describes why validation failed. | ||
*/ | ||
function validateViewConstraints(viewConstraints) { | ||
if (viewConstraints.externalId === null || | ||
((!util_1.isFalsy(viewConstraints.externalId) && | ||
!(lodash_isstring_1.default(viewConstraints.externalId) || lodash_isregexp_1.default(viewConstraints.externalId))))) { | ||
return new TypeError('External ID must be a string or RegExp'); | ||
} | ||
if (viewConstraints.viewId === null || | ||
(!util_1.isFalsy(viewConstraints.viewId) && !lodash_isstring_1.default(viewConstraints.viewId))) { | ||
return new TypeError('View ID must be a string'); | ||
} | ||
return false; | ||
} | ||
/** | ||
* An adapter for Slack's interactive message components such as buttons, menus, and dialogs. | ||
@@ -118,3 +140,3 @@ * @typicalname slackInteractions | ||
* @param signingSecret - Slack app signing secret used to authenticate request | ||
* @param options.syncResponseTimeout - number of milliseconds to wait before flushing a syncrhonous response to an | ||
* @param options.syncResponseTimeout - number of milliseconds to wait before flushing a synchronous response to an | ||
* incoming request and falling back to an asynchronous response. | ||
@@ -230,5 +252,5 @@ * @param options.lateResponseFallbackEnabled - whether or not promises that resolve after the syncResponseTimeout can | ||
* :-----:|:-----:|:-----:|:-----:|:-----:|:-----: | ||
* **Button Press**| Message in response | When resolved before `syncResposeTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | Create a new message instead of replacing using `replace_original: false` | ||
* **Menu Selection**| Message in response | When resolved before `syncResposeTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | Create a new message instead of replacing using `replace_original: false` | ||
* **Message Action** | Message in response | When resolved before `syncResposeTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | | ||
* **Button Press**| Message in response | When resolved before `syncResponseTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | Create a new message instead of replacing using `replace_original: false` | ||
* **Menu Selection**| Message in response | When resolved before `syncResponseTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | Create a new message instead of replacing using `replace_original: false` | ||
* **Message Action** | Message in response | When resolved before `syncResponseTimeout` or `lateResponseFallbackEnabled: false`, message in response<br />When resolved after `syncResponseTimeout` and `lateResponseFallbackEnabled: true`, message in request to `response_url` | Empty response | Message in request to `response_url` | | ||
* **Dialog Submission**| Error list in response | Error list in response | Empty response | Message in request to `response_url` | Returning a Promise that takes longer than 3 seconds to resolve can result in the user seeing an error. Warning logged if a promise isn't completed before `syncResponseTimeout`. | ||
@@ -284,2 +306,49 @@ * | ||
}; | ||
/** | ||
* Add a handler for view submission. | ||
* | ||
* The value returned from the `callback` determines the response sent back to Slack. The handler can return a plain | ||
* object with a `response_action` property to dismiss the modal, push a view into the modal, display validation | ||
* errors, or update the view. Alternatively, the handler can return a Promise for this kind of object, which resolves | ||
* before `syncResponseTimeout` or `lateResponseFallbackEnabled: false`, to perform the same response actions. If the | ||
* Promise resolves afterwards or `lateResponseFallbackEnabled: true` then the modal will be dismissed. If the handler | ||
* returns `undefined` the modal will be dismissed. | ||
* | ||
* @param matchingConstraints - the callback ID (as a string or RegExp) or an object describing the constraints to | ||
* match view submissions for the handler. | ||
* @param callback - the function to run when an view submission is matched | ||
* @returns this instance (for chaining) | ||
*/ | ||
SlackMessageAdapter.prototype.viewSubmission = function (matchingConstraints, callback) { | ||
var viewConstraints = formatMatchingConstraints(matchingConstraints); | ||
var error = validateConstraints(viewConstraints) || validateViewConstraints(viewConstraints); | ||
if (error) { | ||
debug('view submission could not be registered: %s', error.message); | ||
throw error; | ||
} | ||
var storableConstraints = Object.assign(viewConstraints, { | ||
handlerType: "view_submission" /* ViewSubmission */, | ||
}); | ||
return this.registerCallback(storableConstraints, callback); | ||
}; | ||
/** | ||
* Add a handler for view closed interaction. The handler should not return a value. | ||
* | ||
* @param matchingConstraints - the callback ID (as a string or RegExp) or an object describing the constraints to | ||
* match view closed interactions for the handler. | ||
* @param callback - the function to run when an view closed interaction is matched | ||
* @returns this instance (for chaining) | ||
*/ | ||
SlackMessageAdapter.prototype.viewClosed = function (matchingConstraints, callback) { | ||
var viewConstraints = formatMatchingConstraints(matchingConstraints); | ||
var error = validateConstraints(viewConstraints) || validateViewConstraints(viewConstraints); | ||
if (error) { | ||
debug('view closed could not be registered: %s', error.message); | ||
throw error; | ||
} | ||
var storableConstraints = Object.assign(viewConstraints, { | ||
handlerType: "view_closed" /* ViewClosed */, | ||
}); | ||
return this.registerCallback(storableConstraints, callback); | ||
}; | ||
/* Interface for HTTP servers (like express middleware) */ | ||
@@ -351,4 +420,5 @@ /** | ||
// * "no replacement" for message actions | ||
// * "submission is valid" for dialog submissions | ||
// * "submission is valid" for dialog submissions and view submissions | ||
// * "no suggestions" for menu options TODO: check that this is true | ||
// * "ack" for view closed | ||
return Promise.resolve({ status: 200 }); | ||
@@ -370,6 +440,9 @@ }; | ||
if (!util_1.isFalsy(constraints.callbackId)) { | ||
if (lodash_isstring_1.default(constraints.callbackId) && payload.callback_id !== constraints.callbackId) { | ||
// The callback ID is located at a different path in the payload for view submission and view closed | ||
var callbackId = (constraints.handlerType === "view_submission" /* ViewSubmission */ || | ||
constraints.handlerType === "view_closed" /* ViewClosed */) ? payload.view.callback_id : payload.callback_id; | ||
if (lodash_isstring_1.default(constraints.callbackId) && callbackId !== constraints.callbackId) { | ||
return false; | ||
} | ||
if (lodash_isregexp_1.default(constraints.callbackId) && !constraints.callbackId.test(payload.callback_id)) { | ||
if (lodash_isregexp_1.default(constraints.callbackId) && !constraints.callbackId.test(callbackId)) { | ||
return false; | ||
@@ -466,2 +539,29 @@ } | ||
} | ||
if (constraints.handlerType === "view_submission" /* ViewSubmission */ || | ||
constraints.handlerType === "view_closed" /* ViewClosed */) { | ||
// a payload that represents a view submission always has a type property set to view_submission, | ||
// a payload that represents a view closed interaction always has a type property set to view_closed | ||
if (!util_1.isFalsy(payload.type) && | ||
(constraints.handlerType === "view_submission" /* ViewSubmission */ && payload.type !== 'view_submission') || | ||
(constraints.handlerType === "view_closed" /* ViewClosed */ && payload.type !== 'view_closed')) { | ||
return false; | ||
} | ||
// if there's no view in this payload, this payload is malformed - abort matching. | ||
if (util_1.isFalsy(payload.view)) { | ||
return false; | ||
} | ||
// if the view ID constraint is specified, only continue if it matches | ||
if (!util_1.isFalsy(constraints.viewId) && payload.view.id !== constraints.viewId) { | ||
return false; | ||
} | ||
// if the external ID constraint is specified, only continue if it matches | ||
if (!util_1.isFalsy(constraints.externalId)) { | ||
if (lodash_isstring_1.default(constraints.externalId) && payload.view.external_id !== constraints.externalId) { | ||
return false; | ||
} | ||
if (lodash_isregexp_1.default(constraints.externalId) && !constraints.externalId.test(payload.view.external_id)) { | ||
return false; | ||
} | ||
} | ||
} | ||
// if there's no reason to eliminate this callback, then its a match! | ||
@@ -481,2 +581,6 @@ return true; | ||
})(ResponseStatus || (ResponseStatus = {})); | ||
function hasBlockRelatedConstraints(constraints) { | ||
var asBlockRelatedConstraints = constraints; | ||
return !(util_1.isFalsy(asBlockRelatedConstraints.blockId) && util_1.isFalsy(asBlockRelatedConstraints.actionId)); | ||
} | ||
//# sourceMappingURL=adapter.js.map |
{ | ||
"name": "@slack/interactive-messages", | ||
"version": "1.3.0", | ||
"version": "1.4.0", | ||
"description": "Official library for using the Slack Platform's Interactive Buttons, Menus, Dialogs, Actions, and Block Actions", | ||
@@ -86,3 +86,3 @@ "author": "Slack Technologies, Inc.", | ||
}, | ||
"gitHead": "3a0e06381d632bb6aae74016c45158e962d15ea5" | ||
"gitHead": "1503609d79abf035e9e21bad7360e124e4211594" | ||
} |
129
README.md
@@ -360,2 +360,113 @@ # Slack Interactive Messages for Node | ||
### Handling view submission and view closed interactions | ||
View submissions are generated when a user clicks on the submission button of a | ||
[Modal](https://api.slack.com/surfaces/modals). View closed interactions are generated when a user clicks on the cancel | ||
button of a Modal, or dismisses the modal using the `×` in the corner. | ||
Apps register functions, called **handlers**, to be triggered when a submissions are received by the adapter using the | ||
`.viewSubmission(constraints, handler)` method or when closed interactions are received using the | ||
`.viewClosed(constraints, handler)` method. When registering a handler, you describe which submissions and closed | ||
interactions you'd like the handler to match using **constraints**. Constraints are [described in detail](#constraints) | ||
below. The adapter will call the handler whose constraints match the interaction best. | ||
These handlers receive a single `payload` argument. The `payload` describes the | ||
[view submission](https://api.slack.com/reference/interaction-payloads/views#view_submission) or | ||
[view closed](https://api.slack.com/reference/interaction-payloads/views#view_closed) | ||
interaction that occurred. | ||
For view submissions, handlers can return an object, or a `Promise` for a object which must resolve within the | ||
`syncResponseTimeout` (default: 2500ms). The contents of the object depend on what you'd like to happen to the view. | ||
Your app can update the view, push a new view into the stack, close the view, or display validation errors to the user. | ||
In the documentation, the shape of the objects for each of those possible outcomes, which the handler would return, are | ||
described as `response_action`s. If the handler returns no value, or a Promise that resolves to no value, the view will | ||
simply be dismissed on submission. | ||
View closed interactions only occur if the view was opened with the `notify_on_close` property set to `true`. For these | ||
interactions the handler should not return a value. | ||
```javascript | ||
const { createMessageAdapter } = require('@slack/interactive-messages'); | ||
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET; | ||
const slackInteractions = createMessageAdapter(slackSigningSecret); | ||
const port = process.env.PORT || 3000; | ||
// Example of handling a simple view submission | ||
slackInteractions.viewSubmission('simple_modal_callback_id', (payload) => { | ||
// Log the input elements from the view submission. | ||
console.log(payload.view.state); | ||
// The previous value is an object keyed by block_id, which contains objects keyed by action_id, | ||
// which contains value properties that contain the input data. Let's log one specific value. | ||
console.log(payload.view.state.my_block_id.my_action_id.value); | ||
// Validate the inputs (errors is of the shape in https://api.slack.com/surfaces/modals/using#displaying_errors) | ||
const errors = validate(payload.view.state); | ||
// Return validation errors if there were errors in the inputs | ||
if (errors) { | ||
return errors; | ||
} | ||
// Process the submission | ||
doWork(); | ||
}); | ||
// Example of handling a view submission which pushes another view onto the stack | ||
slackInteractions.viewSubmission('first_step_callback_id', () => { | ||
const errors = validate(payload.view.state); | ||
if (errors) { | ||
return errors; | ||
} | ||
// Process the submission (needs to complete under 2.5 seconds) | ||
return doWork() | ||
.then(() => { | ||
return { | ||
response_action: 'push', | ||
view: { | ||
type: 'modal', | ||
callback_id: 'second_step_callback_id', | ||
title: { | ||
type: 'plain_text', | ||
text: 'Second step', | ||
}, | ||
blocks: [ | ||
{ | ||
type: 'input', | ||
block_id: 'last_thing', | ||
element: { | ||
type: 'plain_text_input', | ||
action_id: 'text', | ||
}, | ||
label: { | ||
type: 'plain_text', | ||
text: 'One last thing...', | ||
}, | ||
}, | ||
], | ||
}, | ||
}; | ||
}) | ||
.catch((error) => { | ||
// Log the error. In your app, inform the user of a failure using a DM or some other area in Slack. | ||
console.log(error); | ||
}); | ||
}); | ||
// Example of handling view closed | ||
slackInteractions.viewClosed('my_modal_callback_id', (payload) => { | ||
// If you accumulated partial state using block actions, now is a good time to clear it | ||
clearPartialState(); | ||
}); | ||
(async () => { | ||
const server = await slackInteractions.start(port); | ||
console.log(`Listening for events on ${server.address().port}`); | ||
})(); | ||
``` | ||
--- | ||
### Constraints | ||
@@ -369,10 +480,12 @@ | ||
| Property name | Type | Description | Used with `.actions()` | Used with `.options()` | | ||
|---------------|------|-------------|------------------------|------------------------| | ||
| `callbackId` | `string` or `RegExp` | Match the `callback_id` for attachment or dialog | ✅ | ✅ | | ||
| `blockId` | `string` or `RegExp` | Match the `block_id` for a block action | ✅ | ✅ | | ||
| `actionId` | `string` or `RegExp` | Match the `action_id` for a block action | ✅ | ✅ | | ||
| `type` | any block action element type or `message_actions` or `dialog_submission` or `button` or `select` | Match the kind of interaction | ✅ | 🚫 | | ||
| `within` | `block_actions` or `interactive_message` or `dialog` | Match the source of options request | 🚫 | ✅ | | ||
| `unfurl` | `boolean` | Whether or not the `button`, `select`, or `block_action` occurred in an App Unfurl | ✅ | 🚫 | | ||
| Property name | Type | Description | Used with `.action()` | Used with `.options()` | Used with `.viewSubmission()` and `.viewClosed()` | | ||
|---------------|------|-------------|-----------------------|------------------------|---------------------------------------------------| | ||
| `callbackId` | `string` or `RegExp` | Match the `callback_id` for attachment or dialog | ✅ | ✅ | ✅ | | ||
| `blockId` | `string` or `RegExp` | Match the `block_id` for a block action | ✅ | ✅ | 🚫 | | ||
| `actionId` | `string` or `RegExp` | Match the `action_id` for a block action | ✅ | ✅ | 🚫 | | ||
| `type` | any block action element type or `message_actions` or `dialog_submission` or `button` or `select` | Match the kind of interaction | ✅ | 🚫 | 🚫 | | ||
| `within` | `block_actions` or `interactive_message` or `dialog` | Match the source of options request | 🚫 | ✅ | 🚫 | | ||
| `unfurl` | `boolean` | Whether or not the `button`, `select`, or `block_action` occurred in an App Unfurl | ✅ | 🚫 | 🚫 | | ||
| `viewId` | `string` | Match the `view_id` for view submissions | 🚫 | 🚫 | ✅ | | ||
| `externalId` | `string` or `RegExp` | Match the `external_id` for view submissions | 🚫 | 🚫 | ✅ | | ||
@@ -379,0 +492,0 @@ All of the properties are optional, its just a matter of how specific you want to the handler's behavior to be. A |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
115404
1206
615