Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support sandbox instances of salesforce #6

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 24 additions & 0 deletions documentation/getting-started.md
Expand Up @@ -40,6 +40,20 @@ When starting a new project or adding glide to an existing project, you may exec
glide init https://my-salesforce-instance.salesforce.com
```

### Sandbox Setup

If you wish to use glide with a sandbox environment, you may generate an additional JSON configuration file by running the `init` command again with the `--sandbox` option. When `--sandbox` is passed to the `init` command, a boolean `"sandbox": true` property is added to the resulting JSON configuration file. This option instructs the authentication layer of glide to use the correct login url (https://test.salesforce.com).

```sh
glide init https://my-salesforce-sandbox.salesforce.com --sandbox
```

In order to prevent name collisions with the default JSON configuration file generated from the `init` command, `glide.sandbox.json` will be used as the file name if the optional `path` argument is not provided. If you wish to use another file name or path, you may do so by providing the `path` argument.

```sh
glide init https://my-salesforce-sandbox.salesforce.com sandbox.json --sandbox
```

**Note:**

If you have not used Glide with your Salesforce instance before, you may be prompted to login. More information about how authentication works in Glide can be found in the [_Advanced Topics_](./advanced-topics.md) section of the guides.
Expand All @@ -48,6 +62,16 @@ If you have not used Glide with your Salesforce instance before, you may be prom

Once you have a JSON configuration file in the root directory of your project, you may spawn a GraphQL server by executing the `serve` command. When executing the `serve` command in a development environment, your default browser will open a [GraphQL Playground](https://github.com/prisma/graphql-playground) playground page to start exploring your data.

```sh
glide serve
```

To use a different JSON configuration file then the default `glide.json` generated by the `init` command, you may do so by providing a the path to the configuration file as a positional argument to the `serve` command.

```sh
glide serve glide.sandbox.json
```

You manually edit your JSON configuration file if you wish to modify the mutations, queries, and/or data types exposed by your GraphQL server. Information about how to configure the generated GraphQL schema can be found in the [_Advanced Topics_](./advanced-topics.md) section of the guides.

<img alt="GraphQL Playground" src="../public/demo.gif" width="980" />
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -21,6 +21,7 @@
"@glide/runtime": "^0.0.1",
"@zakgolba/jsonptr": "^1.1.0",
"body-parser": "^1.19.0",
"boxen": "^4.1.0",
"chalk": "^2.4.2",
"commander": "^3.0.1",
"cross-fetch": "^3.0.4",
Expand Down
3 changes: 3 additions & 0 deletions packages/glide-authenticate/package.json
Expand Up @@ -5,17 +5,20 @@
"main": "dist/lib.js",
"types": "dist/lib.d.ts",
"scripts": {
"deploy": "serverless deploy",
"lint": "tslint --project tsconfig.json"
},
"dependencies": {
"aws-sdk": "^2.534.0",
"jsforce": "^1.9.2",
"lodash": "^4.17.15",
"nconf": "^0.10.0"
},
"devDependencies": {
"@types/aws-lambda": "8.10.32",
"@types/aws-sdk": "2.7.0",
"@types/jsforce": "1.9.9",
"@types/lodash": "4.14.139",
"@types/nconf": "0.10.0",
"@types/node": "12.7.5",
"serverless": "^1.52.2"
Expand Down
10 changes: 5 additions & 5 deletions packages/glide-authenticate/serverless.yml
@@ -1,4 +1,4 @@
service: glide-authenticate
service: glide-oauth
provider:
name: aws
runtime: nodejs8.10
Expand All @@ -7,23 +7,23 @@ package:
- dist/**
functions:
callback:
handler: dist/lib.callback
handler: dist/events/oauth/callback.default
events:
- http:
path: oauth/callback
method: get
connect:
handler: dist/lib.connect
handler: dist/events/connect.default
events:
- websocket:
route: $connect
message:
handler: dist/lib.message
handler: dist/events/message.default
events:
- websocket:
route: $default
refresh:
handler: dist/lib.refresh
handler: dist/events/oauth/refresh.default
events:
- http:
path: oauth/refresh
Expand Down
5 changes: 5 additions & 0 deletions packages/glide-authenticate/src/events/connect.ts
@@ -0,0 +1,5 @@
import { handler } from "../utilities";

export default handler(async () => {
// noop
});
17 changes: 17 additions & 0 deletions packages/glide-authenticate/src/events/message.ts
@@ -0,0 +1,17 @@
import { get } from "lodash";

import { MessageType } from "../types";
import { handler, oauth2, send } from "../utilities";

export default handler(async request => {
const { connectionId, domainName, stage } = request.requestContext;
const environment = get(JSON.parse(request.body || "{}"), "data.environment");
const result = oauth2.configure(environment).getAuthorizationUrl({
state: JSON.stringify({ connectionId, domainName, stage }),
});

await send(request.requestContext, {
data: result,
type: MessageType.Initialize,
});
});
26 changes: 26 additions & 0 deletions packages/glide-authenticate/src/events/oauth/callback.ts
@@ -0,0 +1,26 @@
import { Connection } from "jsforce";

import { MessageType } from "../../types";
import { handler, params, oauth2, send } from "../../utilities";

export default handler(async request => {
const environment = params.get(request, "environment");
const connection = new Connection({
oauth2: oauth2.configure(environment),
});

await connection.authorize(params.require(request, "code"));
await send(JSON.parse(params.require(request, "state")), {
data: {
accessToken: connection.accessToken,
oauth2: oauth2.options(environment),
// @ts-ignore
refreshToken: connection.refreshToken,
},
type: MessageType.Authenticated,
});

return {
message: "You have been successfully logged in",
};
});
12 changes: 12 additions & 0 deletions packages/glide-authenticate/src/events/oauth/refresh.ts
@@ -0,0 +1,12 @@
import { handler, oauth2 } from "../../utilities";

export default handler(async request => {
const { environment, token } = JSON.parse(request.body || "{}");
const result = await oauth2.configure(environment).refreshToken(token);

return {
oauth2: oauth2.options(environment),
accessToken: result.access_token,
refreshToken: result.refresh_token,
};
});
60 changes: 0 additions & 60 deletions packages/glide-authenticate/src/lib.ts
@@ -1,60 +0,0 @@
import { Connection, OAuth2 } from "jsforce";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was split up into multiple files to make it easier to maintain in the future. Each file in the events directory now represents a single lambda event handler.

import nconf from "nconf";

import { handler, params, send } from "./utilities";

const oauth2 = new OAuth2(nconf.env("__").get("oauth"));

const enum MessageType {
Authenticated = "AUTHENTICATED",
Initialize = "INITIALIZE",
}

export const callback = handler(async request => {
const client = JSON.parse(params.require(request, "state"));
const connection = new Connection({ oauth2 });

await connection.authorize(params.require(request, "code"));

await send(client, {
data: {
accessToken: connection.accessToken,
oauth2: nconf.get("oauth"),
// @ts-ignore
refreshToken: connection.refreshToken,
},
type: MessageType.Authenticated,
});

return {
message: "You have been successfully logged in",
};
});

export const connect = handler(async () => {
// noop
});

export const message = handler(async request => {
const state = JSON.stringify({
connectionId: request.requestContext.connectionId,
domainName: request.requestContext.domainName,
stage: request.requestContext.stage,
});

await send(request.requestContext, {
data: oauth2.getAuthorizationUrl({ state }),
type: MessageType.Initialize,
});
});

export const refresh = handler(async request => {
const { token } = JSON.parse(request.body || "{}");
const results = await oauth2.refreshToken(token);

return {
oauth2: nconf.get("oauth"),
accessToken: results.access_token,
refreshToken: results.refresh_token,
};
});
22 changes: 22 additions & 0 deletions packages/glide-authenticate/src/types.ts
@@ -0,0 +1,22 @@
export enum Environment {
Default = "default",
Sandbox = "sandbox",
}

export namespace Environment {
export function from(input?: string): Environment {
switch (input) {
case Environment.Sandbox: {
return Environment.Sandbox;
}
default: {
return Environment.Default;
}
}
}
}

export const enum MessageType {
Authenticated = "AUTHENTICATED",
Initialize = "INITIALIZE",
}
30 changes: 28 additions & 2 deletions packages/glide-authenticate/src/utilities.ts
@@ -1,5 +1,9 @@
import lambda, { Context } from "aws-lambda";
import { ApiGatewayManagementApi } from "aws-sdk";
import { OAuth2, OAuth2Options } from "jsforce";
import nconf from "nconf";

import { Environment } from "./types";

let apiGateway: ApiGatewayManagementApi | null = null;

Expand All @@ -11,6 +15,8 @@ export type Response = lambda.APIGatewayProxyResult;
export type Responder = (request: Request, context: Context) => Promise<object | null | void>;

export function handler(responder: Responder): lambda.APIGatewayProxyHandler {
nconf.env("__");

return (request, context, callback) => {
responder(request, context).then(
data => {
Expand Down Expand Up @@ -52,9 +58,29 @@ export async function send(client: Client, data: object): Promise<void> {
await apiGateway.postToConnection(message).promise();
}

export namespace oauth2 {
const url = {
[Environment.Default]: "https://login.salesforce.com",
[Environment.Sandbox]: "https://test.salesforce.com",
};

export function configure(environment?: string): OAuth2 {
return new OAuth2(options(environment));
}

export function options(environment?: string): OAuth2Options {
return {
...nconf.get("oauth"),
loginUrl: url[Environment.from(environment)],
};
}
}

export namespace params {
export function get(request: Request, param: string): string | undefined {
return (request.queryStringParameters || {})[param];
export function get(request: Request, param: string): string | undefined;
export function get(request: Request, param: string, value: string): string;
export function get(request: Request, param: string, value?: string): string | undefined {
return (request.queryStringParameters || {})[param] || value;
}

export function require(request: Request, param: string): string {
Expand Down
14 changes: 9 additions & 5 deletions src/commands/init.ts
@@ -1,10 +1,11 @@
// tslint:disable: object-shorthand-properties-first

import { Definition, Operator } from "@glide/runtime";
import { Command } from "commander";
import jsforce from "jsforce";

import { Options } from "../lib";
import { login } from "../oauth";
import { Environment, login } from "../oauth";
import { json, string } from "../utilities";

interface Identifiers {
Expand All @@ -18,9 +19,10 @@ interface Inflection {
singular: string;
}

export default async function init(origin: string, path: string = "glide.json"): Promise<void> {
const { instance, schema } = createOptions(origin);
const connection = await login(instance);
export default async function init(origin: string, path: string, flags: Command): Promise<void> {
const { instance, sandbox, schema } = createOptions(origin, flags);
const environment = sandbox ? Environment.Sandbox : Environment.Default;
const connection = await login(instance, environment);
const sobjects = await connection.describeGlobal().then(({ sobjects }) => {
return Promise.all(sobjects.map(({ name }) => connection.describe(name)));
});
Expand Down Expand Up @@ -55,13 +57,15 @@ export default async function init(origin: string, path: string = "glide.json"):

await json.write(path, {
instance,
sandbox,
schema,
});
}

function createOptions(instance: string): Options {
function createOptions(instance: string, flags: Command): Options {
return {
instance,
sandbox: Boolean(flags.sandbox),
schema: {
mutations: {},
queries: {},
Expand Down
33 changes: 26 additions & 7 deletions src/commands/serve.ts
@@ -1,7 +1,11 @@
import { EOL } from "os";

import boxen from "boxen";
import chalk from "chalk";
import open from "open";

import glide, { Options } from "../lib";
import { login } from "../oauth";
import { Environment, login } from "../oauth";
import { display, json, isDevEnv } from "../utilities";

export interface Flags {
Expand All @@ -15,8 +19,19 @@ export default async function serve(path: string = "glide.json", flags: Flags):
const options = await configure(path);
const listener = glide(options).listen(flags.port, () => {
const address = display.address(listener);

console.log(`server listening on ${address}`);
const message = [
chalk`Config File: {underline ${path}}`,
chalk`GraphQL Server: {underline ${address}}`,
chalk`Salesforce Instance: {underline ${options.instance}}`,
];

console.log(
boxen(message.join(EOL), {
float: "center",
margin: 3,
padding: 1,
}),
);

if (isDevEnv()) {
open(address).then(browser => browser.unref());
Expand All @@ -36,8 +51,12 @@ export default async function serve(path: string = "glide.json", flags: Flags):
async function configure(path: string): Promise<Options> {
const options = await json.read<Options>(path);

return {
connection: isDevEnv() ? await login(options.instance) : null,
...options,
};
if (isDevEnv()) {
options.connection = await login(
options.instance,
options.sandbox ? Environment.Sandbox : Environment.Default,
);
}

return options;
}