Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,31 @@ async bugsplat.post(error, options); // Posts an arbitrary Error object to BugSp
// Returns a promise that resolves with properties: error (if there was an error posting to BugSplat), response (the response from the BugSplat crash post API), and original (the error passed by bugsplat.post)
```

### User Feedback

You can also submit non-crashing user feedback (e.g. bug reports, feature requests) using `postFeedback`. Feedback reports appear in BugSplat with the "User Feedback" crash type, grouped by `title`.

```ts
const response = await bugsplat.postFeedback('Login button does not respond', {
description: 'Tapping login on iPhone does nothing.',
user: 'jane@example.com',
email: 'jane@example.com',
});
```

You can attach files such as screenshots:

```ts
const screenshot = document.querySelector('input[type="file"]').files[0];

await bugsplat.postFeedback('UI rendering issue', {
description: 'The sidebar overlaps the main content.',
attachments: [
{ filename: 'screenshot.png', data: screenshot },
],
});
```

## 📢 Upgrading

If you are developing a Node.js application and were using bugsplat-js <= 5.0.0 please upgrade to [bugsplat-node](https://www.npmjs.com/package/bugsplat-node). BugSplat-node has the same consumer APIs as bugsplat-js <= 5.0.0. Additionally, support for file attachments and exiting the Node process in the error handler have been moved to [bugsplat-node](https://www.npmjs.com/package/bugsplat-node) so that bugsplat-js can be run in browsers as well as Node.js environments.
Expand Down
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,8 @@
"typescript": "^5.9.3",
"typescript-eslint": "^8.33.1",
"vitest": "^4.0.16"
},
"dependencies": {
"fflate": "^0.8.2"
}
}
72 changes: 36 additions & 36 deletions spec/bugsplat-response.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,30 @@ describe('validateResponseBody', () => {
const responseBodies = [
{
status: 'success',
current_server_time: 12,
message: 'message 1',
url: 'osaiujdhfihfju',
crash_id: 9,
crashId: 9,
stackKeyId: 1,
messageId: 1,
infoUrl: 'https://app.bugsplat.com/browse/crashInfo.php',
},
{
status: 'fail',
current_server_time: 24,
message: 'message 2',
url: 'http://example.com',
crash_id: 100,
crashId: 100,
stackKeyId: 55,
messageId: 200,
infoUrl: 'https://app.bugsplat.com/browse/crashInfo.php?id=100',
},
{
status: 'success',
current_server_time: -100,
message: 'message 3',
url: 'http://bugsplat.com/rocks',
crash_id: 333,
crashId: 333,
stackKeyId: 0,
messageId: 0,
infoUrl: 'https://app.bugsplat.com/browse/crashInfo.php?id=333',
},
{
status: 'success',
crashId: 21417,
stackKeyId: -1,
messageId: -1,
},
];

Expand All @@ -41,41 +47,35 @@ describe('validateResponseBody', () => {
{},
{
status: 'succes',
current_server_time: 12,
message: 'message 1',
url: 'osaiujdhfihfju',
crash_id: 9,
crashId: 9,
stackKeyId: 1,
messageId: 1,
infoUrl: 'https://app.bugsplat.com',
},
{
status: 'fail',
current_server_time: 24,
message: 'message 2',
url: new Date(),
crash_id: 100,
},
{
current_server_time: -100,
message: 'message 3',
url: 'http://bugsplat.com/rocks',
crash_id: 333,
status: 'success',
stackKeyId: 1,
messageId: 1,
infoUrl: 'https://app.bugsplat.com',
},
{
status: 'success',
message: 'message 3',
url: 'http://bugsplat.com/rocks',
crash_id: 333,
crashId: 9,
messageId: 1,
infoUrl: 'https://app.bugsplat.com',
},
{
status: 'success',
current_server_time: -100,
url: 'http://bugsplat.com/rocks',
crash_id: 333,
crashId: 9,
stackKeyId: 1,
infoUrl: 'https://app.bugsplat.com',
},
{
status: 'success',
current_server_time: -100,
message: 'message 3',
url: 'http://bugsplat.com/rocks',
crashId: 9,
stackKeyId: 1,
messageId: 1,
infoUrl: 42,
},
];

Expand Down
63 changes: 51 additions & 12 deletions spec/bugsplat.e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ describe('BugSplat', () => {
throw new Error('Please set FRED_PASSWORD environment variable');
}

const api = await BugSplatApiClient.createAuthenticatedClientForNode(email, password);
const api = await BugSplatApiClient.createAuthenticatedClientForNode(
email,
password,
);
client = new CrashApiClient(api);

// Posting too frequently results in 400s from the web server
Expand All @@ -36,26 +39,62 @@ describe('BugSplat', () => {
const additionalFile = './spec/files/additionalFile.txt';
const fileName = path.basename(additionalFile);
const fileContents = await readFile(additionalFile);
const additionalFormDataParams = [
{
key: fileName,
value: new Blob([new Uint8Array(fileContents)]),
filename: fileName,
},
{ key: 'attributes', value: '{"foo": "bar"}' },
];
const bugsplat = new BugSplat(database, appName, appVersion);
bugsplat.setDefaultAppKey(appKey);
bugsplat.setDefaultUser(user);
bugsplat.setDefaultEmail(email);
bugsplat.setDefaultDescription(description);

const result = await bugsplat.post(error, { additionalFormDataParams });
const result = await bugsplat.post(error, {
attachments: [
{ data: new Uint8Array(fileContents), filename: fileName },
],
attributes: { foo: 'bar' },
});
if (result.error) {
throw new Error(result.error.message);
}

const expectedCrashId = result.response.crash_id;
const expectedCrashId = result.response.crashId;
const crashData = await client.getCrashById(database, expectedCrashId);
expect(crashData.appName).toEqual(appName);
expect(crashData.appVersion).toEqual(appVersion);
expect(crashData.appKey).toEqual(appKey);
expect(crashData.description).toEqual(description);
expect(crashData.user).toBeTruthy(); // Fred has PII obfuscated so the best we can do here is to check if truthy
expect(crashData.email).toBeTruthy(); // Fred has PII obfuscated so the best we can do here is to check if truthy
}, 30000);

it('should post a feedback report with all provided information', async () => {
const database = 'fred';
const appName = 'my-node-crasher';
const appVersion = '1.2.3.4';
const appKey = 'Key!';
const user = 'User!';
const email = 'fred@bedrock.com';
const title = 'Symbol Store Size Limit';
const description =
'I hit the 8 GB symbol size limit and can no longer upload symbols';
const additionalFile = './spec/files/additionalFile.txt';
const fileName = path.basename(additionalFile);
const fileContents = await readFile(additionalFile);
const bugsplat = new BugSplat(database, appName, appVersion);
bugsplat.setDefaultAppKey(appKey);
bugsplat.setDefaultUser(user);
bugsplat.setDefaultEmail(email);
bugsplat.setDefaultDescription(description);

const result = await bugsplat.postFeedback(title, {
attachments: [
{ data: new Uint8Array(fileContents), filename: fileName },
],
attributes: { foo: 'bar' },
});
if (result.error) {
throw new Error(result.error.message);
}

const expectedCrashId = result.response.crashId;
const crashData = await client.getCrashById(database, expectedCrashId);
expect(crashData.appName).toEqual(appName);
expect(crashData.appVersion).toEqual(appVersion);
Expand All @@ -76,7 +115,7 @@ describe('BugSplat', () => {
if (result.error) {
throw new Error(result.error.message);
}
const expectedCrashId = result.response.crash_id;
const expectedCrashId = result.response.crashId;
const crashData = await client.getCrashById(database, expectedCrashId);

expect(crashData.appName).toEqual(appName);
Expand Down
Loading
Loading