Zapier Cli - Unhandled error - zapier

I’m creating an app that (for now) takes a file as input and returns its hash (sha256).
It works fine as long as the user use a file as input, but if he puts something else (a string for example), the application silently fails (there is a stack trace in the app’s logs but Zapier displays nothing particular) and returns a wrong hash.
I do no have the impression that the error is handleable by my code and the stack is pretty obfuscated:
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Log │ Unhandled error: Error: Error: Could not find the method to call: authentication.test │
│ │ What happened: │
│ │ Executing authentication.test with bundle │
│ │ Error: Could not find the method to call: authentication.test │
│ │ Error: Error: Could not find the method to call: authentication.test │
│ │ at execute (:censored:9:d1ba0cf2aa:/node_modules/zapier-platform-core/src/execute.js:83:11) │
│ │ at input (:censored:9:d1ba0cf2aa:/node_modules/zapier-platform-core/src/create-command-handler.js:26:12) │
│ │ at Object.beforeMiddleware.then.newInput (:censored:9:d1ba0cf2aa:/node_modules/zapier-platform-core/src/middleware.js:90:22) │
│ │ at bound (domain.js:280:14) │
│ │ at Object.runBound (domain.js:293:12) │
│ │ at Object.tryCatcher (:censored:9:d1ba0cf2aa:/node_modules/bluebird/js/release/util.js:16:23) │
│ │ at Promise._settlePromiseFromHandler (:censored:9:d1ba0cf2aa:/node_modules/bluebird/js/release/promise.js:512:31) │
│ │ at Promise._settlePromise (:censored:9:d1ba0cf2aa:/node_modules/bluebird/js/release/promise.js:569:18) │
│ │ at Promise._settlePromise0 (:censored:9:d1ba0cf2aa:/node_modules/bluebird/js/release/promise.js:614:10) │
│ │ at Promise._settlePromises (:censored:9:d1ba0cf2aa:/node_modules/bluebird/js/release/promise.js:693:18) │
│ │ at Async._drainQueue (:censored:9:d1ba0cf2aa:/node_modules/bluebird/js/release/async.js:133:16) │
│ │ at Async._drainQueues (:censored:9:d1ba0cf2aa:/node_modules/bluebird/js/release/async.js:143:10) │
│ │ at Immediate.Async.drainQueues (:censored:9:d1ba0cf2aa:/node_modules/bluebird/js/release/async.js:17:14) │
│ │ at runCallback (timers.js:672:20) │
│ │ at tryOnImmediate (timers.js:645:5) │
│ │ at processImmediate [as _immediateCallback] (timers.js:617:5) │
│ Version │ 1.0.7 │
│ Step │ │
│ Timestamp │ 2018-05-24T03:57:57-05:00 │
└───────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
My code is essentially a copy of the "Files" Example App with the creates/uploadFile.js file replaced by:
const request = require('request');
const crypto = require('crypto');
function hashStream(stream) {
const hash = crypto.createHash('sha256').setEncoding('hex');
return new Promise((resolve, reject) => {
stream.pipe(hash)
.on('finish', () => resolve(hash.read()))
.on('error', reject)
})
}
function hashFile(z, bundle) {
const fileStream = request(bundle.inputData.file);
return hashStream(fileStream).then((hash) => ({hash}));
};
module.exports = {
key: 'hashFile',
noun: 'File',
display: {
label: 'Hash File',
description: 'Performs a sha256 on file.'
},
operation: {
inputFields: [
{key: 'file', required: true, type: 'file', label: 'File'},
],
perform: hashFile,
sample: { filename: 'example.pdf' },
outputFields: [
{key: 'hash', type: 'string', label: 'Hash'}
],
}
};
Update:
I eventually found my mistake: I was assuming that the request function would throw an error upon failure.
So the hashFile function were certainly hashing the error page.
Changing the hashFile function solved the problem:
function hashFile(z, bundle) {
return new Promise((resolve, reject) => {
request(bundle.inputData.file)
.on('response', function ({statusCode}) {
if (200 === statusCode) {
hashStream(this).then((hash) => resolve({hash}));
} else {
reject(new Error(`Invalid status code ${statusCode}`));
}
})
.on('error', (err) => reject(err))
})
}
But: I'm not able to catch "Unhandled errors"; I tried with
process.on('unhandledRejection', (reason, p) => {
console.log('Unhandled Rejection at:', p, 'reason:', reason);
process.exit(1);
});
but I think that the Zapier engine prevents that kind of tricks because it has no effect...

David here, from the Zapier Platform team. There's no magic or tricks going on, but it looks like you're making life a little bit harder for yourself. Much of the zapier code doesn't support that event pattern (.on('x', function(){...)). Instead, everything uses promises already. Unhandled rejection refers to a rejected promise that you're not handling with a catch clause. We also provide a z.request function which is going to flow a little better than using the request package itself (though this is allowed if you want to!). Check out the doc section about making HTTP requests. We've also got a z.hash(algo, string) method. and then update your code to be something like the following:
function hashFile(z, bundle) {
return z.request(bundle.inputData.file).then(response => {
if (response.code !== 200) {
throw new Error('something bad')
}
// might need to pull something else out of the response
// if response.content isn't the string contents of the file
return {hash: z.hash('sha256', response.content)}
})
};
We've also made it pretty easy to unit test your code - if it works locally, it'll work on our servers as well. Again, no magic! 😀

Related

Recursively expanding children in OneDrive graph API

I would like to get all the contents of a folder in OneDrive. The folder has child folders within it, and child folders within them, e.g.:
Parent/
└── Child1/
├── Child1.1/
│ ├── File1.1.1
│ └── File1.1.2
└── Child1.2/
├── File1.2.1
└── File1.2.2
I would like to maintain knowledge about folder hierarchy, basically something like Parent/Child1/File1.1.1 so I can't use the empty query graph search approach referenced here, which returns everything in a flat format without any information about the hierarchy.
I could recursively call each folder one by one but that would be a lot since there are thousands of folders in the parent directory.
Based on the docs (and here too), I would have expected the expand=children parameter to work, but it seems not to. Any ideas?
GET https://graph.microsoft.com/v1.0/me/drive/items/<id>/children?$expand=children
{
"error": {
"code": "notSupported",
"message": "Operation not supported",
"innerError": {
"date": "2022-12-17T17:03:28",
"request-id": "",
"client-request-id": ""
}
}
}

Prevent default response code in Swagger/OpenAPI definition with NestJS

What I'm trying
I'm implementing a DELETE route using NestJS.
Since the route is only ever supposed to return a HTTP 204 No Content on successful deletion, utilizing the #nestjs/swagger decorators, I've annotated it as follows:
#ApiParam({
name: 'id',
required: true,
type: 'string',
example: 'abc123',
})
#ApiNoContentResponse({
description: 'All items for the given id have been deleted',
})
#Delete('/accounts/:id/items')
async deleteItems(
#Param('id') id: string,
) {
// do stuff
}
Stack
#nestjs/cli: 9.1.6
#nestjs/common: 9.1.6
#nestjs/core: 9.1.6
#nestjs/platform-express: 9.1.6
#nestjs/swagger: 6.1.2
nest-cli.json
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "#nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
"#nestjs/swagger/plugin"
]
}
}
Question
How can I prevent this (default?) response from being generated?
What happens
When running the server with npm run start:dev, the swagger description for my route is created. Although I've not explicitly defined it, the description now contains an entry for HTTP 200 in the Response section of the route. The entry contains no description and no indicator that it's not explicitly defined.
What I expected to happen
When running the server with npm run start:dev, the swagger description for my route is created. The description only contains and entry for HTTP 204 in the Response section of the route.
What I've tried
Explicitly defined an #ApiOkResponse:
// they all have no effect
#ApiOkResponse()
// OR
#ApiOkResponse({})
// OR
#ApiOkResponse({ status: 204 })
Defined an #ApiDefaultResponse:
// they all create a response 'default' with no description
#ApiDefaultResponse()
// OR
#ApiDefaultResponse({})
// OR
#ApiDefaultResponse({ status: 204 })
You can just add #HttpCode(204) as an annotation to your code. 200 is added by default, because this is the default response status code for DELETE requests.This default behavior would be overwritten with the annotation mentioned above.
More information about the status codes can be found here: https://docs.nestjs.com/controllers

'Insufficient privileges to complete the operation' when trying to create group via Microsoft Graph

Desired Behaviour
Create a group via Microsoft Graph, using the JavaScript SDK and MSAL in a single tenant application with delegated API permissions.
Actual Behaviour
{
"statusCode": 403,
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"requestId": "6e865d96-ef8b-408e-a905-5393b4adcfdc",
"date": "2020-05-16T20:20:15.000Z",
"body": "{\"code\":\"Authorization_RequestDenied\",\"message\":\"Insufficient privileges to complete the operation.\",\"innerError\":{\"request-id\":\"6e865d96-ef8b-408e-a905-5393b4adcfdc\",\"date\":\"2020-05-17T06:20:15\"}}"
}
What I've Tried
To create the code, I used the following API reference instructions:
Create group
Group properties
Sign-in/sign-out and all other API requests are working.
Subscription
Microsoft 365 Business Standard Trial
API Permissions
Directory.AccessAsUser.All +++
Directory.ReadWrite.All ***
Group.ReadWrite.All
GroupMember.ReadWrite.All ***
openid
profile
Sites.Read.All
Tasks.ReadWrite
User.Read
*** I added these after reading this answer, but still get the same error.
+++ I added this after suggestion in answer below.
I have clicked the 'Grant admin consent' button in Azure app registrations area.
index.html
<!-- msal -->
<!-- from: https://github.com/AzureAD/microsoft-authentication-library-for-js/tree/dev/lib/msal-core -->
<script type="text/javascript" src="https://alcdn.msauth.net/lib/1.3.0/js/msal.js" integrity="****" crossorigin="anonymous"></script>
<!-- javascript sdk -->
<!-- from: https://github.com/microsoftgraph/msgraph-sdk-javascript -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/#microsoft/microsoft-graph-client/lib/graph-js-sdk.js"></script>
config.js
// msal options
const msalConfig = {
auth: {
clientId: "client-id-here",
redirectUri: "http://localhost:8080",
authority: "https://login.microsoftonline.com/tenant-id-here"
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: false,
forceRefresh: false
}
};
// define application permissions
const scopes = ['directory.accessasuser.all', 'directory.readwrite.all', 'group.readwrite.all', 'groupmember.readwrite.all', 'openid', 'profile', 'sites.read.all', 'user.read', 'tasks.readwrite' ];
auth.js
const msalApplication = new Msal.UserAgentApplication(msalConfig);
const loginRequest = {
scopes: scopes
}
async function sign_in() {
try {
await msalApplication.loginPopup(loginRequest);
if (msalApplication.getAccount()) {
console.log("sign in success");
}
} catch (error) {
console.log("sign in error");
console.log(error);
}
}
function sign_out() {
msalApplication.logout();
}
graph.js
var path = "/groups";
var group = {
description: "Here is a description.",
displayName: "test_test_test",
groupTypes: [
"Unified",
"DynamicMembership"
],
mailEnabled: true,
mailNickname: "test_test_test",
securityEnabled: false, // initially i had this as true, doesn't seem to make a difference either way
visibility: "Hiddenmembership",
"owners#odata.bind": [
"https://graph.microsoft.com/v1.0/users/my-user-id-here"
],
"members#odata.bind": [
"https://graph.microsoft.com/v1.0/users/my-user-id-here"
]
};
var response = await client.api(path)
.post(group);
Related reading:
Compare groups
Learn about Microsoft 365 Groups
Overview of Microsoft 365 Groups for administrators
Working with groups in Microsoft Graph
Create, edit, or delete a security group in the Microsoft 365 admin center
Creating teams and managing members using Microsoft Graph
Overview of Office 365 groups in Microsoft Graph
Other ideas about what could be causing error...
Visibility is supported only for unified groups; it is not supported for security groups.
Source
Maybe the combination of securityEnabled: true and visibility:Hiddenmembership are not compatible?
I tried changing it to securityEnabled: false and got the same message.
Is there something wrong with the token?
I copied the Authorisation: Bearer value from Chrome dev tools console and pasted at https://jwt.io and there were two additional scopes provided in the token - but all other scopes are present:
from your graph call, I saw you try to create the dynamic group and assign a member to this group, the dynamic group did not support to add members, that's the reason why you delete "dynamicMemberShip" under group type can fix this issue, below is the example to create dynamic groups via graph API call:
{
"description": "test1",
"displayName": "test1",
"groupTypes": [
"Unified",
"DynamicMembership"
],
"mailEnabled": true,
"membershipRule": "user.displayname -contains A",
"membershipRuleProcessingState":"On",
"mailNickname": "library",
"securityEnabled": false,
}
I can see that you have not given Group.Create permission in your permission set.
Give this permission to create a group.
For Application permission type, these all permissions are required:
Group.Create
Group.ReadWrite.All
Directory.ReadWrite.All
And for Delegated user permission type:
Group.ReadWrite.All
Directory.ReadWrite.All
Directory.AccessAsUser.All
I was able to create a Group by changing the following in graph.js:
groupTypes: ["Unified","DynamicMembership"]
to
groupTypes: ["Unified"]`
I have no idea why DynamicMembership causes an error.
It is a valid property value of Group > groupTypes listed at the link below with no caveats:
https://learn.microsoft.com/en-us/graph/api/resources/group?view=graph-rest-1.0#properties
For reference, the code above creates:
Group
SharePoint 'Team Site'
Outlook email group
It does not automatically create a corresponding:
Team
Plan
You can see the new group listed in the following locations:
https://admin.microsoft.com/AdminPortal/Home#/groups
https://outlook.office365.com/people
https://portal.azure.com/#blade/Microsoft_AAD_IAM/GroupsManagementMenuBlade/AllGroups
And you can see the group 'Team Site' listed at:
https://your_account-admin.sharepoint.com/_layouts/15/online/AdminHome.aspx#/siteManagement

Get user list with whom OneDrive Document is shared using Graph API

I am using Graph API to read OneDrive document successfully.
I have a requirement to get the list of users with whom the document is shared with.
The DriveItem does have the Shared property but it only tells whether is shared with user\organsization\anonymous, however it does give me list of users with whom I have shared the document.
As per documentation
The Shared resource indicates a DriveItem has been shared with others.
{
"owner": { "#odata.type": "microsoft.graph.identitySet" },
"scope": "anonymous | organization | users",
"sharedBy": { "#odata.type": "microsoft.graph.identitySet" },
"sharedDateTime": "datetime"
}
Is there a way to get shared with "users list" for a given document?
The API supports this via the Permissions resource. Permissions define the level of access provided (read,write,sp.owner, orsp.member`).
You can return a list of Permissions using the //permissions endpoint of a given file. For example, to /me/drive/items/{item-id}/permissions will return the permissions for the {item-id} in the current user's default Drive.

maxAgeSeconds in sw-toolbox not working

I'm using sw-toolbox for building progressive web app. All resources are getting cached as expected but they are not expiring on putting maxAgeSeconds.
toolbox.router.get("/test/(.*)", toolbox.cacheFirst, {
cache: {
maxEntries: 5, name: "list", maxAgeSeconds: 300, debug: !0
}
});
I have used CacheFirst Strategy, and verified that resources are kicking in through service worker cache.

Resources