Skip to content

Commit 9cfd37b

Browse files
committed
Fixed oauth. Now checks restlet version for compatibility.
1 parent b7324f8 commit 9cfd37b

File tree

6 files changed

+247
-134
lines changed

6 files changed

+247
-134
lines changed

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.0.2] - 2019-02-07
9+
10+
### Fixed
11+
12+
- Fixed OAuth support. NetSuite OAuth is weird.
13+
14+
### Added
15+
16+
- Assigned a version to the RESTlet, and created a GET request that will pull down the version number of the RESTlet. This allows the extension to detect when the RESTlet version is not up-to-date, and to warn the user. There's also a new palette command, `Get NSUpload RESTlet version` which will fetch the value and display it in a notification.
17+
- Continued improving error handling. Now can detect bad authentication and warn the user.
18+
819
## [1.0.1] - 2019-02-05
920

1021
### Added

README.md

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
# netsuite-upload VS Code plugin
22

3-
**netsuite-upload** is a Visual Studio Code extension that allows you to manage your SuiteScript files directly from the IDE & helps you with defining of new modules & module dependecies
3+
**netsuite-upload** is a Visual Studio Code extension that allows you to manage your SuiteScript files directly from VS Code. It also helps you with defining new modules and adding server-side module dependecies.
44

55
## Under New Management
6-
In February 2019, original author original author Tomáš Tvrdý transferred ownership of this project to silverl. I'm releasing an updated version 1.0.X line with many fixes. I'm looking for volunteers to help test before I publish the extension to the VS Code Extensions site. Please reach out in a new Issues item.
6+
7+
In February 2019, original author original author Tomáš Tvrdý [@tvrdytom](https://github.com/tvrdytom) transferred ownership of this project to me [@silver](https://github.com/silverl). The last version under this line of development was `0.1.3`.
8+
9+
New releases will be versioned `1.0.0+`.
710

811
## Features
912

1013
### 1. Push and Pull Files and Folders between VS Code and the NetSuite File Cabinet
1114

12-
Right-click the file/folder in the navigation panel to see the options:
15+
Right-click a file or folder in the navigation panel to see the context menu options:
1316

14-
- `Pull file from NetSuite` - downloads file from NetSuite
15-
- `Push file to NetSuite` - uploads file to NetSuite
16-
- `Delete file in NetSuite` - deletes file in NetSuite
17-
- `Compare file with NetSuite` - compares your local version with the NetSuite one
18-
- `Pull folder from NetSuite` - Download the folder content from NetSuite
17+
- `Pull file from NetSuite` - downloads a file from NetSuite
18+
- `Push file to NetSuite` - uploads a file to NetSuite
19+
- `Delete file in NetSuite` - deletes a file in NetSuite
20+
- `Compare file with NetSuite` - diff your local version with the NetSuite version
21+
- `Pull folder from NetSuite` - Download the folder and all contents from NetSuite
1922

2023
![Snippet & commands](img/netsuite_upload.gif)
2124

@@ -32,8 +35,9 @@ Right-click the file/folder in the navigation panel to see the options:
3235

3336
By changing the `netSuiteUpload.rootDirectory` setting in `settings.json`, you can push and pull files and folders to/from a different base subfolder.
3437

35-
## Very Important Fact!
36-
Your VS Code project **MUST MUST MUST** be rooted at the folder that maps to the SuiteScript NetSuite file cabinet folder. This extension assumes it is being used inside that folder.
38+
## Very Important Fact
39+
40+
Your VS Code project **MUST MUST MUST** be rooted at the folder that maps to the SuiteScript NetSuite file cabinet folder. This extension assumes it is being used inside that folder.
3741

3842
If you look in the left pane of VS Code and your top-level folder is "SuiteScript", you've done it wrong. Instead, do a `File...Open Folder` in VS Code and choose that SuiteScript folder to open.
3943

@@ -46,6 +50,7 @@ Since this extension is under new leadership, I'm waiting until I get some beta
4650
Therefore, I'm asking everyone to manually install from the Github Releases tab.
4751

4852
The procedure:
53+
4954
- Uninstall Old: If you have a previous version from the Marketplace installed, uninstall it. (Command palette -> Extensions: Show Installed Extensions)
5055
- Uninstall `netsuite-upload`
5156
- Download the `netsuite-upload-1.0.0.vsix` file from the [Github Releases tab](https://github.com/netsuite-upload-org/netsuite-upload/releases)
@@ -70,22 +75,11 @@ _Future versions of this VS Code Extension may require that you upgrade the REST
7075

7176
### Authentication
7277

73-
#### Role Permissions
74-
75-
This extension is going to be calling a NetSuite RESTlet that will be manipulating files and folders in the SuiteScripts folder of the File Cabinet. Therefore, that user must have sufficient permissions assigned to their Role to allow these file changes, and to call the RESTlet.
76-
77-
At a minimum, the Role must have the following **Setup** permissions (please let me know if I have any of these wrong):
78-
79-
- Access Token Management - Full
80-
- Allow JS / HTML Uploads - Full
81-
- Log in using Access Tokens - Full
82-
- SuiteScript - Full
83-
- User Access Tokens - Full
84-
- Web Services - Full
78+
This extension supports accessing the RESTlet script deployment using either NLAuth authorization or OAuth 1.0 authorization.
8579

8680
NLAuth is supported. OAuth is attempted in the code, but I couldn't make it work locally. I could use some testers.
8781

88-
#### For regular NLAuth
82+
#### Authentication Option 1: NLAuth Authorization
8983

9084
Place the following in either Workspace settings or general User settings:
9185

@@ -102,14 +96,28 @@ Place the following in either Workspace settings or general User settings:
10296
- PASSWORD - Your password
10397
- ROLE - The NetSuite RoleID for which you have web service/API permissions.
10498

105-
#### OAuth
99+
#### Authentication Option 2: OAuth
106100

107-
To experiment with OAuth, leave the setting for `netSuiteUpload.authentication` unset or commented out.
101+
Generating the necessary tokens for OAuth is covered in the NetSuite help site. It's not fun.
108102

109103
- If you wish to use OAuth authentication instead of basic authentication you can leave the authentication header blank and use the OAuth settings properties.
110-
- First generate an Integration record in NetSuite, make sure the 'token based authentication' scheme is checked, and save the token and secret
111-
- Second log into a role you wish to use for authentication and from the manage tokens center generate a new token and secret using the Integration from the previous step
112-
- Input the 4 values from above in the corresponding settings options along with the account number in the realm property
104+
- First, generate an Integration record in NetSuite, make sure the 'token based authentication' scheme is checked, and hang on to the token and secret details.
105+
- Second, log into a role you wish to use for authentication. From the "manage tokens center", generate a new token and secret using the Integration from the previous step.
106+
- Input the 4 values from above (NetSuite key and token and Consumer key and token) in the corresponding settings options.
107+
- Set the `realm` setting equal to your numeric NetSuite account number.
108+
109+
### Authorization - User Role Permissions
110+
111+
This extension is going to be calling a NetSuite RESTlet that will be manipulating files and folders in the SuiteScripts folder of the File Cabinet. Therefore, the user being authenticated must have sufficient permissions assigned to their Role to allow these file changes, and to call the RESTlet script deployment.
112+
113+
At a minimum, the Role must have the following **Setup** permissions (please let me know if I have any of these wrong):
114+
115+
- Access Token Management - Full
116+
- Allow JS / HTML Uploads - Full
117+
- Log in using Access Tokens - Full
118+
- SuiteScript - Full
119+
- User Access Tokens - Full
120+
- Web Services - Full
113121

114122
### settings.json
115123

@@ -136,9 +144,8 @@ To experiment with OAuth, leave the setting for `netSuiteUpload.authentication`
136144
// Account number
137145
"netSuiteUpload.realm": "<NETSUITE ACCOUNT NUMBER>",
138146

139-
// Base NetSuite folder path to upload script to (e.g. "SuiteScripts/Developer")
147+
// Base NetSuite folder path to upload script to (e.g. "SuiteScripts/Developer"). Default if unset is "SuiteScripts".
140148
"netSuiteUpload.rootDirectory": "<BASE FOLDER PATH>"
141-
142149
}
143150
```
144151

@@ -154,4 +161,4 @@ Add the following to your `keybindings.json` file:
154161

155162
## Limitations
156163

157-
The plugin is using RESTlet for the communication with the NetSuite which is having some governance limitation. Current implementation does not deal with this problem, so there could be a problem to pull folders containing a lot of items from NetSuite.
164+
The plugin is using a RESTlet for the communication with NetSuite. RESTlets have some governance limitations. Current implementation does not deal with this, so there could be problems pulling folders containing a lot of items from NetSuite.

bl/netSuiteBl.js

Lines changed: 82 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ function getRestletVersion() {
1818

1919
function downloadFileFromNetSuite(file) {
2020
nsRestClient.getFile(file, function (err, res) {
21-
if (hasNetSuiteError("ERROR downloading file", err, res)) {
22-
return;
23-
}
21+
if (hasNetSuiteError('ERROR downloading file.', err, res)) return;
2422

2523
var relativeFileName = nsRestClient.getRelativePath(file.fsPath);
26-
fs.writeFile(file.fsPath, res.body[0].content);
24+
fs.writeFile(file.fsPath, res.body[0].content, (err) => {
25+
if (err) throw err;
26+
});
27+
2728
vscode.window.showInformationMessage('SUCCESS! File "' + relativeFileName + '" downloaded.');
2829
});
2930
}
@@ -32,9 +33,7 @@ function uploadFileToNetSuite(file) {
3233
var fileContent = fs.readFileSync(file.fsPath, 'utf8');
3334

3435
nsRestClient.postFile(file, fileContent, function (err, res) {
35-
if (hasNetSuiteError("ERROR uploading file", err, res)) {
36-
return;
37-
}
36+
if (hasNetSuiteError('ERROR uploading file.', err, res)) return;
3837

3938
var relativeFileName = nsRestClient.getRelativePath(file.fsPath);
4039
vscode.window.showInformationMessage('SUCCESS! File "' + relativeFileName + '" uploaded.');
@@ -43,68 +42,103 @@ function uploadFileToNetSuite(file) {
4342

4443
function hasNetSuiteError(custommessage, err, response) {
4544
if (err) {
46-
var get = function(obj, key) {
47-
return key.split(".").reduce(function(o, x) {
48-
return (typeof o == "undefined" || o === null) ? o : o[x];
45+
var get = function (obj, key) {
46+
return key.split('.').reduce(function (o, x) {
47+
return (typeof o == 'undefined' || o === null) ? o : o[x];
4948
}, obj);
5049
};
5150

52-
var msg = custommessage;
53-
var items = [];
54-
if (response && response.body && response.body.error) {
51+
var errorDetails = [];
52+
if (response && get(response, 'status') === 403) { // Forbidden. Bad Auth.
53+
errorDetails = [
54+
'AUTHENTICATION FAILED!',
55+
'HTTP Status: 403',
56+
'HTTP Error: ' + get(response, 'message'),
57+
'Local Stack:',
58+
get(response, 'stack')
59+
];
60+
} else if (err.shortmessage) {
61+
// We passed in a simple, short message which is all we need to display.
62+
errorDetails = [err.shortmessage];
63+
64+
} else if (response && response.body && response.body.error) {
5565
// The body of the response may contain a JSON object containing a NetSuite-specific
5666
// message. We'll parse and display that in addition to the HTTP message.
57-
//if (response.headers.)
58-
items = [
59-
"NetSuite Error:",
60-
"HTTP Status: " + get(err, 'status'),
61-
"HTTP Error: " + get(err, 'message'),
62-
"NS Error: " + get(response.body.error, 'code'),
63-
"NS Message: " + get(response.body.error, 'message'),
64-
"NS Type: " + get(response.body.error, 'message.type'),
65-
"NS Name: " + get(response.body.error, 'message.name'),
66-
"NS Stack: " + get(response.body.error, 'message.stack')
67-
];
68-
} else if (err.status && err.stack) {
69-
items = [
70-
"Other Error:",
71-
"HTTP Status: " + get(err, 'status'),
72-
"HTTP Error: " + get(err, 'message'),
73-
"HTTP Stack:" + get(err, 'stack')
67+
try {
68+
69+
var nsErrorObj = JSON.parse(response.body.error.message);
70+
71+
if (nsErrorObj.name === 'SSS_MISSING_REQD_ARGUMENT') {
72+
custommessage += ' NetSuite N/file module does not allow storing an empty file.';
73+
}
74+
75+
errorDetails = [
76+
'NetSuite Error Details:',
77+
get(nsErrorObj, 'type'),
78+
get(nsErrorObj, 'name'),
79+
get(nsErrorObj, 'message'),
80+
get(nsErrorObj, 'code'),
81+
'Remote Stack:',
82+
get(nsErrorObj, 'stack'),
83+
'HTTP Status: ' + get(err, 'status'),
84+
'HTTP Error: ' + get(err, 'message'),
85+
'Local Stack:',
86+
get(err, 'stack')
87+
];
88+
} catch (e) {
89+
// Response body error does not contain a JSON message.
90+
errorDetails = [
91+
'NetSuite Error Details:',
92+
'NS Error: ' + get(response.body.error, 'code'),
93+
'NS Message: ' + get(response.body.error, 'message'),
94+
'HTTP Status: ' + get(err, 'status'),
95+
'HTTP Error: ' + get(err, 'message'),
96+
'Local Stack:',
97+
get(err, 'stack')
98+
];
99+
}
100+
} else {
101+
errorDetails = [
102+
'Unknown Error:',
103+
'HTTP Status: ' + get(err, 'status'),
104+
'HTTP Error: ' + get(err, 'message'),
105+
'Local Stack:',
106+
get(err, 'stack')
74107
];
75108
}
76-
var errormessage = msg + " " + items.join(" ");
77-
console.log(err);
78-
console.log(errormessage);
79-
vscode.window.showErrorMessage(errormessage);
109+
110+
// Pre-pend the custommessage and our own message.
111+
errorDetails.unshift(custommessage);
112+
errorDetails.push('Use Help…Toggle Developer Tools and choose the Console tab for a better formatted error message.');
113+
console.log(errorDetails.join('\n'));
114+
// vscode window doesn't support newlines.
115+
vscode.window.showErrorMessage(errorDetails.join(' | '));
116+
80117
return true;
81118
}
82119
return false;
83120
}
84121

85122
function deleteFileInNetSuite(file) {
86123
nsRestClient.deleteFile(file, function (err, res) {
87-
if (hasNetSuiteError("ERROR deleting file", err, res)) {
88-
return;
89-
}
90-
124+
if (hasNetSuiteError('ERROR deleting file.', err, res)) return;
91125
var relativeFileName = nsRestClient.getRelativePath(file.fsPath);
92126
vscode.window.showInformationMessage('SUCCESS! Deleted file "' + relativeFileName + '".');
93127
});
94128
}
95129

96130
function previewFileFromNetSuite(file) {
97131
nsRestClient.getFile(file, function (err, res) {
98-
if (hasNetSuiteError("ERROR downloading file!", err, res)) {
99-
return;
100-
}
132+
if (hasNetSuiteError('ERROR downloading file.', err, res)) return;
101133

102134
var relativeFileName = nsRestClient.getRelativePath(file.fsPath);
103135
var tempFolder = vscode.workspace.getConfiguration('netSuiteUpload').tempFolder;
104136
var filePathArray = (relativeFileName.split('.')[0] + '.preview.' + relativeFileName.split('.')[1]).split(path.sep);
105137
var newPreviewFile = path.join(tempFolder, filePathArray[filePathArray.length - 1]);
106138

107-
fs.writeFile(newPreviewFile, res.body[0].content);
139+
fs.writeFile(newPreviewFile, res.body[0].content, (err) => {
140+
if (err) throw err;
141+
});
108142

109143
var nsFile = vscode.Uri.file(newPreviewFile);
110144
vscode.commands.executeCommand('vscode.diff', file, nsFile, 'Local <--> NetSuite');
@@ -113,17 +147,17 @@ function previewFileFromNetSuite(file) {
113147

114148
function downloadDirectoryFromNetSuite(directory) {
115149
nsRestClient.getDirectory(directory, function (err, res) {
116-
if (hasNetSuiteError("ERROR downloading directory!", err, res)) {
117-
return;
118-
}
150+
if (hasNetSuiteError('ERROR downloading directory.', err, res)) return;
119151

120152
res.body.forEach(function (file) {
121-
var fullFilePath = path.join(vscode.workspace.rootPath, file.fullPath.split('/').join(path.sep));
153+
var fullFilePath = path.join(vscode.workspace.rootPath, file.fullPath.replace(/^SuiteScripts\//, '').split('/').join(path.sep));
122154

123155
createDirectoryIfNotExist(fullFilePath + (file.type == 'folder' ? path.sep + '_' : ''));
124156

125-
if (file.type == 'file') {
126-
fs.writeFile(fullFilePath, file.content);
157+
if (file.type === 'file') {
158+
fs.writeFile(fullFilePath, file.content, (err) => {
159+
if (err) throw err;
160+
});
127161
}
128162
});
129163

0 commit comments

Comments
 (0)