Skip to content

Commit

Permalink
feature: fix types in @formspree/core (#44)
Browse files Browse the repository at this point in the history
`@formspree/core`\
- fix `SubmissionData` has a type of `any` causing everything after it to opt-out typechecking
- remove a no-op `teardown` method on `Client` and `Session`
- remove `utils.now` and use `Date.now` instead
- remove unused functions from `utils` module: `append`, `toCamel`, `camelizeTopKeys`
- add tests for `utils.appendExtraData` and convert the test file to typescript
- add tests for `session.data()`
- no longer export `Session` type

`@formspree/react`
- update types as a result of `SubmissionData` is no longer `any`
- fix `createPaymentMethod` does not properly map payload when the submission data is a type of `FormData`
- fix the `Client` is not updated when project changes
  • Loading branch information
bhongy authored Jul 6, 2023
1 parent bbd3982 commit 4c40e1b
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 143 deletions.
22 changes: 22 additions & 0 deletions .changeset/great-mayflies-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@formspree/react': minor
'@formspree/core': minor
---

# Fix types in @formspree/core

## `@formspree/core`

- fix `SubmissionData` has a type of `any` causing everything after it to opt-out typechecking
- remove a no-op `teardown` method on `Client` and `Session`
- remove `utils.now` and use `Date.now` instead
- remove unused functions from `utils` module: `append`, `toCamel`, `camelizeTopKeys`
- add tests for `utils.appendExtraData` and convert the test file to typescript
- add tests for `session.data()`
- no longer export `Session` type

## `@formspree/react`

- update types as a result of `SubmissionData` is no longer `any`
- fix `createPaymentMethod` does not properly map payload when the submission data is a type of `FormData`
- fix the `Client` is not updated when project changes
9 changes: 1 addition & 8 deletions packages/formspree-core/src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@ export class Client {
}
}

/**
* Teardown the client session.
*/
teardown(): void {
if (this.session) this.session.teardown();
}

/**
* Submit a form.
*
Expand Down Expand Up @@ -116,7 +109,7 @@ export class Client {
// Send a request to Formspree server to handle the payment method
const response = await fetchImpl(url, {
...request,
body: data,
body: serializeBody(data),
});
const responseData = await response.json();

Expand Down
5 changes: 5 additions & 0 deletions packages/formspree-core/src/env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// declare PhantomJS properties that we check in session webdriver.
declare interface Window {
_phantom?: unknown;
callPhantom?: unknown;
}
2 changes: 1 addition & 1 deletion packages/formspree-core/src/forms.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { PaymentMethodResult } from '@stripe/stripe-js';

export type SubmissionData = FormData | any;
export type SubmissionData = FormData | Record<string, string | Blob>;

export interface SubmissionOptions {
endpoint?: string;
Expand Down
1 change: 0 additions & 1 deletion packages/formspree-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,3 @@ export {
type ErrorBody,
type FieldError,
} from './forms';
export { type Session } from './session';
14 changes: 6 additions & 8 deletions packages/formspree-core/src/session.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
import { atob } from './base64';
import { now } from './utils';

/**
* Check whether the user agent is controlled by an automation.
*/
const webdriver = (): boolean => {
return (
navigator.webdriver ||
!!document.documentElement.getAttribute(atob('d2ViZHJpdmVy')) ||
// @ts-ignore
!!window.callPhantom ||
// @ts-ignore
!!window._phantom
);
};

export class Session {
loadedAt: number;
webdriver: boolean;
private readonly loadedAt: number;
private readonly webdriver: boolean;

constructor() {
this.loadedAt = now();
this.loadedAt = Date.now();
this.webdriver = webdriver();
}

teardown(): void {}

data(): {
loadedAt: number;
webdriver: boolean;
Expand Down
62 changes: 3 additions & 59 deletions packages/formspree-core/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { btoa } from './base64';
import { version } from '../package.json';
import { hasErrors } from './forms';
import type { SubmissionResponse } from './forms';
import type { SubmissionData, SubmissionResponse } from './forms';
import type { PaymentMethod, Stripe } from '@stripe/stripe-js';

/**
Expand All @@ -13,54 +13,6 @@ export const encode64 = (obj: object): string => {
return btoa(JSON.stringify(obj));
};

/**
* Appends a key-value pair to a target.
*
* @param target - An object or FormData instance to mutate.
* @param key - The key to append.
* @param value - The value to append.
*/
export const append = (
target: { [key: string]: any } | FormData,
key: string,
value: string
): void => {
if (target instanceof FormData) {
target.append(key, value);
} else {
target[key] = value;
}
};

/**
* Converts a snake case string to camel case.
*
* @param str - A string to convert to camel case.
*/
export const toCamel = (str: string): string => {
return str.replace(/([-_][a-z])/gi, ($1) => {
return $1.toUpperCase().replace('-', '').replace('_', '');
});
};

/**
* Converts the top-level keys of an object to camel case.
* This function returns a new object (instead of mutating in place).
*
* @param obj - An object with string keys.
*/
export const camelizeTopKeys = (obj: {
[key: string]: any;
}): { [key: string]: any } => {
const newObject: { [key: string]: any } = {};

for (const [key, value] of Object.entries(obj)) {
newObject[toCamel(key)] = value;
}

return newObject;
};

/**
* Generates a client header.
*
Expand All @@ -72,16 +24,8 @@ export const clientHeader = (givenLabel: string | undefined): string => {
return `${givenLabel} ${label}`;
};

/**
* The current timestamp.
*/
export const now = (): number => {
// @ts-ignore
return 1 * new Date();
};

export const appendExtraData = (
formData: FormData | object,
formData: SubmissionData,
prop: string,
value: string
) => {
Expand All @@ -100,7 +44,7 @@ type HandleSCAargs = {
paymentMethod: PaymentMethod;
error?: undefined;
};
data: FormData | object;
data: SubmissionData;
fetchImpl: (
input: RequestInfo,
init?: RequestInit | undefined
Expand Down
42 changes: 42 additions & 0 deletions packages/formspree-core/test/session.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Session } from '../src/session';

describe('Session', () => {
const now = Date.now();

beforeEach(() => {
jest.useFakeTimers({ now });
});

afterEach(() => {
jest.useRealTimers();
});

describe('with webdriver', () => {
beforeEach(() => {
// pretend running in PhantomJS
window._phantom = {};
});

afterEach(() => {
window._phantom = undefined;
});

it('returns the correct data', () => {
const sess = new Session();
expect(sess.data()).toEqual({
loadedAt: now,
webdriver: true,
});
});
});

describe('without webdriver', () => {
it('returns the correct data', () => {
const sess = new Session();
expect(sess.data()).toEqual({
loadedAt: now,
webdriver: false,
});
});
});
});
5 changes: 3 additions & 2 deletions packages/formspree-core/test/submitForm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { version } from '../package.json';
// return success;
// };
//
const success = new Promise((resolve, _reject) => {
const success = new Promise((resolve) => {
const response = {
status: 200,
json: () => {
Expand All @@ -22,7 +22,7 @@ const success = new Promise((resolve, _reject) => {
resolve(response);
});

const failure = new Promise((resolve, _reject) => {
const failure = new Promise((resolve) => {
const response = {
status: 400,
json: () => {
Expand Down Expand Up @@ -168,6 +168,7 @@ it('sends telemetry data if session is started', () => {
expect(props.headers['Formspree-Session-Data']).toBeDefined();

const parsedBody = JSON.parse(props.body);
expect(parsedBody).toEqual({});
return success;
};

Expand Down
25 changes: 0 additions & 25 deletions packages/formspree-core/test/utils.test.js

This file was deleted.

Loading

1 comment on commit 4c40e1b

@vercel
Copy link

@vercel vercel bot commented on 4c40e1b Jul 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.