Repository: graphql/graphql-playground
Branch: main
Commit: 91ed7d8b1c1b
Files: 316
Total size: 869.0 KB
Directory structure:
gitextract_3l4bavka/
├── .dependabot/
│ └── config.yml
├── .github/
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .nvmrc
├── .vscode/
│ └── settings.json
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── SECURITY.md
├── docs/
│ └── security/
│ ├── 2020-xss-template-injection.md
│ └── 2021-schema-xss-phishing-attack.md
├── lerna.json
├── package.json
├── packages/
│ ├── graphql-playground-electron/
│ │ ├── .babelrc
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── postcss.config.js
│ │ ├── src/
│ │ │ ├── main/
│ │ │ │ ├── createWindow.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── menu.ts
│ │ │ │ ├── notify.ts
│ │ │ │ ├── types.ts
│ │ │ │ └── updates.ts
│ │ │ ├── renderer/
│ │ │ │ ├── components/
│ │ │ │ │ ├── App.tsx
│ │ │ │ │ ├── InitialView/
│ │ │ │ │ │ ├── InitialView.tsx
│ │ │ │ │ │ ├── Toggle.tsx
│ │ │ │ │ │ └── data.ts
│ │ │ │ │ ├── Loading.tsx
│ │ │ │ │ └── Root.tsx
│ │ │ │ ├── index.css
│ │ │ │ ├── index.html
│ │ │ │ ├── index.tsx
│ │ │ │ ├── redux/
│ │ │ │ │ ├── actions/
│ │ │ │ │ │ └── history.ts
│ │ │ │ │ ├── createStore.ts
│ │ │ │ │ └── reducers/
│ │ │ │ │ ├── history.ts
│ │ │ │ │ └── index.ts
│ │ │ │ └── utils/
│ │ │ │ └── errify.ts
│ │ │ └── shared/
│ │ │ └── utils.ts
│ │ ├── static/
│ │ │ └── icons/
│ │ │ └── icon.icns
│ │ ├── tsconfig.json
│ │ ├── tslint.json
│ │ ├── typings/
│ │ │ ├── custom.d.ts
│ │ │ └── styled-jsx.d.ts
│ │ ├── webpack.config.build.js
│ │ └── webpack.config.js
│ ├── graphql-playground-html/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── examples/
│ │ │ └── xss-attack/
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── minimal.html
│ │ ├── minimalWithoutCDN.html
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── get-loading-markup.ts
│ │ │ ├── index.ts
│ │ │ └── render-playground-page.ts
│ │ ├── tsconfig.json
│ │ └── withAnimation.html
│ ├── graphql-playground-middleware-express/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── examples/
│ │ │ ├── basic/
│ │ │ │ ├── .envrc
│ │ │ │ ├── .graphqlconfig.yml
│ │ │ │ ├── README.md
│ │ │ │ ├── index.js
│ │ │ │ └── package.json
│ │ │ └── graphcool/
│ │ │ ├── .envrc
│ │ │ ├── .graphqlconfig.yml
│ │ │ ├── README.md
│ │ │ ├── datamodel.graphql
│ │ │ ├── graphcool.yml
│ │ │ ├── index.js
│ │ │ ├── package.json
│ │ │ ├── prisma.yml
│ │ │ └── schema.graphql
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── graphql-playground-middleware-hapi/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── examples/
│ │ │ └── basic/
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── graphql-playground-middleware-koa/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── README.md
│ │ ├── examples/
│ │ │ └── basic/
│ │ │ ├── .graphqlconfig.yml
│ │ │ ├── README.md
│ │ │ ├── index.js
│ │ │ └── package.json
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ ├── graphql-playground-middleware-lambda/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── examples/
│ │ │ └── basic/
│ │ │ ├── .gitignore
│ │ │ ├── README.md
│ │ │ ├── handler.js
│ │ │ ├── package.json
│ │ │ └── serverless.yml
│ │ ├── package.json
│ │ ├── src/
│ │ │ └── index.ts
│ │ └── tsconfig.json
│ └── graphql-playground-react/
│ ├── .babelrc
│ ├── .gitignore
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ ├── config/
│ │ ├── env.js
│ │ ├── jest/
│ │ │ ├── cssTransform.js
│ │ │ ├── fileTransform.js
│ │ │ └── typescriptTransform.js
│ │ ├── paths.js
│ │ ├── polyfills.js
│ │ ├── webpack.config.dev.js
│ │ ├── webpack.config.prod.js
│ │ └── webpackDevServer.config.js
│ ├── jest.config.js
│ ├── package.json
│ ├── public/
│ │ ├── _headers
│ │ ├── _redirects
│ │ ├── affiliates.graphql
│ │ ├── auth.graphql
│ │ ├── blogging.graphql
│ │ ├── chat.graphql
│ │ ├── conferences.graphql
│ │ ├── empty.graphql
│ │ ├── freecom.graphql
│ │ ├── hn-relay-full.graphql
│ │ ├── hn-relay.graphql
│ │ ├── hn-starter.graphql
│ │ ├── hn.graphql
│ │ ├── index.html
│ │ ├── insta-auth0.graphql
│ │ ├── insta-email.graphql
│ │ ├── insta-expo-auth.graphql
│ │ ├── insta-files.graphql
│ │ ├── instagram-full.graphql
│ │ ├── instagram.graphql
│ │ ├── movies-import.graphql
│ │ ├── pokedex.graphql
│ │ ├── simple-pokedex.graphql
│ │ ├── todo.graphql
│ │ ├── twitter.graphql
│ │ ├── welcome-rp.graphql
│ │ ├── welcome.graphql
│ │ └── worldchat.graphql
│ ├── release.sh
│ ├── scripts/
│ │ ├── build.js
│ │ ├── start.js
│ │ └── test.js
│ ├── src/
│ │ ├── __snapshots__/
│ │ │ └── index.test.tsx.snap
│ │ ├── components/
│ │ │ ├── Button.tsx
│ │ │ ├── Copy.tsx
│ │ │ ├── EndpointPopup.tsx
│ │ │ ├── FileEditor.tsx
│ │ │ ├── GraphQLBinApp.tsx
│ │ │ ├── HistoryPopup/
│ │ │ │ ├── HistoryChooser.tsx
│ │ │ │ ├── HistoryHeader.tsx
│ │ │ │ └── HistoryItems.tsx
│ │ │ ├── HistoryPopup.tsx
│ │ │ ├── Icons/
│ │ │ │ └── index.tsx
│ │ │ ├── MiddlewareApp.tsx
│ │ │ ├── Modal.tsx
│ │ │ ├── Playground/
│ │ │ │ ├── ConfigEditor.tsx
│ │ │ │ ├── DocExplorer/
│ │ │ │ │ ├── Argument.tsx
│ │ │ │ │ ├── ArgumentInline.tsx
│ │ │ │ │ ├── ColumnDoc.tsx
│ │ │ │ │ ├── DocTypeSchema.tsx
│ │ │ │ │ ├── DocsStyles.tsx
│ │ │ │ │ ├── DocsTypes/
│ │ │ │ │ │ ├── DocType.tsx
│ │ │ │ │ │ ├── EnumTypeSchema.tsx
│ │ │ │ │ │ ├── ScalarType.tsx
│ │ │ │ │ │ └── UnionTypeSchema.tsx
│ │ │ │ │ ├── ErrorContainer.tsx
│ │ │ │ │ ├── FieldDoc.tsx
│ │ │ │ │ ├── GraphDocs.tsx
│ │ │ │ │ ├── GraphDocsRoot.tsx
│ │ │ │ │ ├── RootColumn.tsx
│ │ │ │ │ ├── SchemaDoc.tsx
│ │ │ │ │ ├── SearchBox.tsx
│ │ │ │ │ ├── SearchResults.tsx
│ │ │ │ │ └── TypeLink.tsx
│ │ │ │ ├── EditorWrapper.tsx
│ │ │ │ ├── ExecuteButton.tsx
│ │ │ │ ├── ExecuteButtonOperation.tsx
│ │ │ │ ├── ExplorerTabs/
│ │ │ │ │ ├── SideTab.tsx
│ │ │ │ │ └── SideTabs.tsx
│ │ │ │ ├── GraphQLEditor.tsx
│ │ │ │ ├── QueryEditor.tsx
│ │ │ │ ├── ResponseTracing.tsx
│ │ │ │ ├── ResultViewer.tsx
│ │ │ │ ├── Results.tsx
│ │ │ │ ├── SchemaExplorer/
│ │ │ │ │ ├── SDLEditor.tsx
│ │ │ │ │ ├── SDLHeader.tsx
│ │ │ │ │ ├── SDLTypes/
│ │ │ │ │ │ ├── SDLDocType.tsx
│ │ │ │ │ │ ├── SDLFieldDoc.tsx
│ │ │ │ │ │ ├── SDLStyles.tsx
│ │ │ │ │ │ ├── SDLType.tsx
│ │ │ │ │ │ └── SDLUnionType.tsx
│ │ │ │ │ └── SDLView.tsx
│ │ │ │ ├── SchemaFetcher.ts
│ │ │ │ ├── Tab.tsx
│ │ │ │ ├── TabBar.tsx
│ │ │ │ ├── TopBar/
│ │ │ │ │ ├── Polling.tsx
│ │ │ │ │ ├── PollingIcon.tsx
│ │ │ │ │ ├── Positioner.tsx
│ │ │ │ │ ├── Reload.tsx
│ │ │ │ │ ├── ReloadIcon.tsx
│ │ │ │ │ ├── SchemaReload.tsx
│ │ │ │ │ └── TopBar.tsx
│ │ │ │ ├── TracingRow.tsx
│ │ │ │ ├── VariableEditor.tsx
│ │ │ │ ├── onHasCompletion.tsx
│ │ │ │ └── util/
│ │ │ │ ├── InvalidSchemaError.ts
│ │ │ │ ├── NoSchemaError.ts
│ │ │ │ ├── ageOfDate.ts
│ │ │ │ ├── createSDL.ts
│ │ │ │ ├── fibonacci-backoff.ts
│ │ │ │ ├── getQueryFacts.ts
│ │ │ │ ├── getQueryTypes.ts
│ │ │ │ ├── getSelectedOperationName.ts
│ │ │ │ ├── getWorkspaceId.ts
│ │ │ │ ├── hasSubscription.ts
│ │ │ │ ├── immutableMemoize.ts
│ │ │ │ ├── makeOperation.ts
│ │ │ │ ├── parseHeaders.ts
│ │ │ │ ├── session.ts
│ │ │ │ ├── shallowEqual.ts
│ │ │ │ ├── shouldUpdate.ts
│ │ │ │ ├── stack.ts
│ │ │ │ ├── toJS.tsx
│ │ │ │ └── whatChanged.ts
│ │ │ ├── Playground.tsx
│ │ │ ├── PlaygroundWrapper.tsx
│ │ │ ├── Popup.tsx
│ │ │ ├── ProjectsSideNav.tsx
│ │ │ ├── ProjectsSideNavItem.tsx
│ │ │ ├── Root.tsx
│ │ │ ├── Settings.tsx
│ │ │ ├── SettingsEditor.tsx
│ │ │ ├── Share.tsx
│ │ │ ├── Spinner.tsx
│ │ │ ├── Toggle.tsx
│ │ │ ├── ToggleButton.tsx
│ │ │ ├── Tooltip.tsx
│ │ │ ├── asyncComponent.tsx
│ │ │ ├── resolveRefStrings.ts
│ │ │ └── util.ts
│ │ ├── constants.ts
│ │ ├── fixtures/
│ │ │ ├── exampleSchema.ts
│ │ │ └── sdl.ts
│ │ ├── graphqlConfig.ts
│ │ ├── index.css
│ │ ├── index.test.tsx
│ │ ├── index.tsx
│ │ ├── lib.tsx
│ │ ├── localDevIndex.tsx
│ │ ├── middlewareIndex.tsx
│ │ ├── setupEnzyme.ts
│ │ ├── state/
│ │ │ ├── appHistory/
│ │ │ │ ├── actions.ts
│ │ │ │ └── reducers.ts
│ │ │ ├── createStore.ts
│ │ │ ├── docs/
│ │ │ │ ├── actions.ts
│ │ │ │ ├── reducers.ts
│ │ │ │ └── selectors.ts
│ │ │ ├── general/
│ │ │ │ ├── actions.ts
│ │ │ │ ├── reducers.ts
│ │ │ │ └── selectors.ts
│ │ │ ├── history/
│ │ │ │ ├── actions.ts
│ │ │ │ ├── reducers.ts
│ │ │ │ └── selectors.ts
│ │ │ ├── localStorage.ts
│ │ │ ├── rootSaga.ts
│ │ │ ├── sessions/
│ │ │ │ ├── WebSocketLink.ts
│ │ │ │ ├── actions.ts
│ │ │ │ ├── fetchingSagas.ts
│ │ │ │ ├── reducers.ts
│ │ │ │ ├── sagas.ts
│ │ │ │ └── selectors.ts
│ │ │ ├── sharing/
│ │ │ │ ├── actions.ts
│ │ │ │ ├── reducers.ts
│ │ │ │ ├── selectors.ts
│ │ │ │ └── sharingSaga.ts
│ │ │ └── workspace/
│ │ │ ├── actions.ts
│ │ │ ├── deserialize.ts
│ │ │ └── reducers.ts
│ │ ├── styled/
│ │ │ ├── index.ts
│ │ │ ├── styled.ts
│ │ │ └── theme.ts
│ │ ├── types.ts
│ │ ├── utils/
│ │ │ └── performance.ts
│ │ └── utils.ts
│ ├── tests/
│ │ └── schema.faker.graphql
│ ├── tsconfig.build.json
│ ├── tsconfig.jest.json
│ ├── tsconfig.json
│ ├── tslint.json
│ └── typings/
│ ├── custom.d.ts
│ └── styled-jsx.d.ts
├── renovate.json
└── scripts/
├── build.sh
├── release-html.sh
├── release-react.sh
└── versions.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .dependabot/config.yml
================================================
version: 1
update_configs:
- package_manager: 'javascript'
directory: '/'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'chore'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-react'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'chore'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-html'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'chore'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-electron'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-hapi'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-express'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-koa'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-lambda'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-hapi/examples/basic'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-express/examples/basic'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-express/examples/graphcool'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-lambda/examples/basic'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
- package_manager: 'javascript'
directory: '/packages/graphql-playground-middleware-koa/examples/basic'
update_schedule: 'live'
allowed_updates:
- match:
update_type: 'security'
commit_message:
prefix: 'fix'
prefix_development: 'fix'
include_scope: true
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing to this project
[fork]: https://github.com/graphcool/graphql-playground/fork
[pr]: https://github.com/graphcool/graphql-playground/compare
[code-of-conduct]: ../CODE_OF_CONDUCT.md
Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great.
Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms.
## Contribution Agreement
As a contributor, you represent that the code you submit is your original work or that of your employer (in which case you represent you have the right to bind your employer). By submitting code, you (and, if applicable, your employer) are licensing the submitted code to the open source community subject to the MIT license.
## Submitting a pull request
0. [Fork][fork] and clone the repository
0. Create a new branch: `git checkout -b feature/my-new-feature-name`
0. Run `npm install` or `yarn install` to make sure you've got the latest dependencies.
0. Make your change
0. Run the unit tests and make sure they pass and have 100% coverage. (`npm test`)
0. Push to your fork and [submit a pull request][pr]
0. Pat your self on the back and wait for your pull request to be reviewed and merged.
Here are a few things you can do that will increase the likelihood of your pull request being accepted:
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
- In your pull request description, provide as much detail as possible. This context helps the reviewer to understand the motivation for and impact of the change.
- Make sure that all the unit tests still pass. PRs with failing tests won't be merged.
## Resources
- [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/)
- [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/)
- [Using Pull Requests](https://help.github.com/articles/about-pull-requests/)
- [GitHub Help](https://help.github.com)
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
#### This issue pertains to the following package(s):
- [ ] GraphQL Playground - Electron App
- [ ] GraphQL Playground HTML
- [ ] GraphQL Playground
- [ ] GraphQL Playground Express Middleware
- [ ] GraphQL Playground Hapi Middleware
- [ ] GraphQL Playground Koa Middleware
- [ ] GraphQL Playground Lambda Middleware
#### What OS and OS version are you experiencing the issue(s) on?
#### What version of graphql-playground(-electron/-middleware) are you experiencing the issue(s) on?
#### What is the expected behavior?
#### What is the actual behavior?
#### What steps may we take to reproduce the behavior?
_Please provide a gif or image of the issue for a quicker response/fix._
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
Fixes #.
Changes proposed in this pull request:
-
-
-
================================================
FILE: .gitignore
================================================
# See http://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
example/node_modules
example/yarn.lock
# testing
/coverage
# production
/build
# misc
.DS_Store
.env
npm-debug.log
.idea
lib
/middleware
playground.css
*.log
**/examples/*/yarn.lock
================================================
FILE: .nvmrc
================================================
lts/dubnium
================================================
FILE: .vscode/settings.json
================================================
{
"prettier.singleQuote": true,
"prettier.trailingComma": "all",
"prettier.semi": false,
}
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at hello@graph.cool. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
[homepage]: http://contributor-covenant.org
[version]: http://contributor-covenant.org/version/1/4/
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Graphcool
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
[](https://badge.fury.io/js/graphql-playground-react)
[](https://circleci.com/gh/graphql/graphql-playground)
> **SECURITY WARNING:** both `graphql-playground-html` and [all four (4) of it's middleware dependents](#impacted-packages) until `graphql-playground-html@1.6.22` were subject to an **XSS Reflection attack vulnerability only to unsanitized user input strings** to the functions therein. This was resolved in `graphql-playground-html@^1.6.22`. [More Information](#security-details) [CVE-2020-4038](https://github.com/graphql/graphql-playground/security/advisories/GHSA-4852-vrh7-28rf)
**Future of this repository**: See [this issue](https://github.com/graphql/graphql-playground/issues/1366#issuecomment-1062088978) for details.
---
GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration).
[](https://graphqlbin.com/v2/6RQ6TM)
## Installation
```sh
$ brew install --cask graphql-playground
```
## Features
- ✨ Context-aware autocompletion & error highlighting
- 📚 Interactive, multi-column docs (keyboard support)
- ⚡️ Supports real-time GraphQL Subscriptions
- ⚙ GraphQL Config support with multiple Projects & Endpoints
- 🚥 Apollo Tracing support
## Security Details
> **NOTE: only _unsanitized user input_ to the functions in these packages is vulnerable** to the recently reported XSS Reflection attack.
### Impact
> Impacted are any and all unsanitized **user-defined** input to:
-`renderPlaygroundPage()`
-`koaPlayground()`
-`expressPlayground()`
-`koaPlayground()`
-`lambdaPlayground()
> If you used static values, such as `graphql-playground-electron` does in [it's webpack config](https://github.com/prisma-labs/graphql-playground/blob/main/packages/graphql-playground-electron/webpack.config.build.js#L16), as well as the most common middleware implementations out there, they were not vulnerable to the attack.
The only reason this vulnerability exists is because we are using template strings in `renderPlaygroundPage()` with potentially unsanitized user defined variables. This allows an attacker to inject html and javascript into the page.
- [Read more about preventing XSS in react](https://pragmaticwebsecurity.com/files/cheatsheets/reactxss.pdf)
Common examples may be user-defined path parameters, query string, unsanitized UI provided values in database, etc., that are used to build template strings or passed directly to a `renderPlaygroundPage()` or the matching middleware function equivalent listed above.
### Impacted Packages
**All versions of these packages are impacted until the ones specified below**, which are now safe for user defined input:
- `graphql-playground-html`: **☔ safe** @ `1.6.22`
- `graphql-playground-express` **☔ safe** @ `1.7.16`
- `graphql-playground-koa` **☔ safe** @ `1.6.15`
- `graphql-playground-hapi` **☔ safe** @ `1.6.13`
- `graphql-playground-lambda` **☔ safe** @ `1.7.17`
- `graphql-playground-electron` has always been **☔ safe** from XSS attacks! This is because configuration is statically defined [it's webpack config](https://github.com/prisma-labs/graphql-playground/blob/main/packages/graphql-playground-electron/webpack.config.build.js#L16)
- `graphql-playground-react` is safe because it does not use `renderPlaygroundPage()` anywhere, and thus is not susceptible to template string XSS reflection attacks.
### More Information
See the [security docs](./SECURITY.md) for more details on how your implementation might be impacted by this vulnerability. It contains safe examples, unsafe examples, workarounds, and more details.
We've also provided ['an example of the xss using the express middleware]('https://github.com/prisma-labs/graphql-playground/tree/main/packages/graphql-playground-html/examples/xss-attack')
## FAQ
### How is this different from [GraphiQL](https://github.com/graphql/graphiql)?
GraphQL Playground uses components of GraphiQL under the hood but is meant as a more powerful GraphQL IDE enabling better (local) development workflows. Compared to GraphiQL, the GraphQL Playground ships with the following additional features:
- Interactive, multi-column schema documentation
- Automatic schema reloading
- Support for GraphQL Subscriptions
- Query history
- Configuration of HTTP headers
- Tabs
See the following question for more additonal features.
### What's the difference between the desktop app and the web version?
The desktop app is the same as the web version but includes these additional features:
- Partial support for [graphql-config](https://github.com/prismagraphql/graphql-config) enabling features like multi-environment setups (no support for sending HTTP headers).
- Double click on `*.graphql` files.
### How does GraphQL Bin work?
You can easily share your Playgrounds with others by clicking on the "Share" button and sharing the generated link. You can think about GraphQL Bin like Pastebin for your GraphQL queries including the context (endpoint, HTTP headers, open tabs etc).
> You can also find the announcement blog post [here](https://blog.graph.cool/introducing-graphql-playground-f1e0a018f05d).
## Settings
In the top right corner of the Playground window you can click on the settings icon.
These are the settings currently available:
```js
{
'editor.cursorShape': 'line', // possible values: 'line', 'block', 'underline'
'editor.fontFamily': `'Source Code Pro', 'Consolas', 'Inconsolata', 'Droid Sans Mono', 'Monaco', monospace`,
'editor.fontSize': 14,
'editor.reuseHeaders': true, // new tab reuses headers from last tab
'editor.theme': 'dark', // possible values: 'dark', 'light'
'general.betaUpdates': false,
'prettier.printWidth': 80,
'prettier.tabWidth': 2,
'prettier.useTabs': false,
'request.credentials': 'omit', // possible values: 'omit', 'include', 'same-origin'
'schema.polling.enable': true, // enables automatic schema polling
'schema.polling.endpointFilter': '*localhost*', // endpoint filter for schema polling
'schema.polling.interval': 2000, // schema polling interval in ms
'schema.disableComments': boolean,
'tracing.hideTracingResponse': true,
'tracing.tracingSupported': true, // set false to remove x-apollo-tracing header from Schema fetch requests
}
```
## Usage
### Properties
The React component `` and all middlewares expose the following options:
- `props` (Middlewares & React Component)
- `endpoint` [`string`](optional) - the GraphQL endpoint url.
- `subscriptionEndpoint` [`string`](optional) - the GraphQL subscriptions endpoint url.
- `workspaceName` [`string`](optional) - in case you provide a GraphQL Config, you can name your workspace here
- `config` [`string`](optional) - the JSON of a GraphQL Config. See an example [here](https://github.com/prismagraphql/graphql-playground/blob/main/packages/graphql-playground-react/src/localDevIndex.tsx#L47)
- `settings` [`ISettings`](optional) - Editor settings in json format as [described here](https://github.com/prismagraphql/graphql-playground#settings)
```ts
interface ISettings {
'editor.cursorShape': 'line' | 'block' | 'underline'
'editor.fontFamily': string
'editor.fontSize': number
'editor.reuseHeaders': boolean
'editor.theme': 'dark' | 'light'
'general.betaUpdates': boolean
'prettier.printWidth': number
'prettier.tabWidth': number
'prettier.useTabs': boolean
'request.credentials': 'omit' | 'include' | 'same-origin'
'request.globalHeaders': { [key: string]: string }
'schema.polling.enable': boolean
'schema.polling.endpointFilter': string
'schema.polling.interval': number
'schema.disableComments': boolean
'tracing.hideTracingResponse': boolean
'tracing.tracingSupported': boolean
}
```
- `schema` [`IntrospectionResult`](optional) - The result of an introspection query (an object of this form: `{__schema: {...}}`) The playground automatically fetches the schema from the endpoint. This is only needed when you want to override the schema.
- `tabs` [`Tab[]`](optional) - An array of tabs to inject. **Note: When using this feature, tabs will be resetted each time the page is reloaded**
```ts
interface Tab {
endpoint: string
query: string
name?: string
variables?: string
responses?: string[]
headers?: { [key: string]: string }
}
```
In addition to this, the React app provides some more properties:
- `props` (React Component)
- `createApolloLink` [`(session: Session, subscriptionEndpoint?: string) => ApolloLink`] - this is the equivalent to the `fetcher` of GraphiQL. For each query that is being executed, this function will be called
`createApolloLink` is only available in the React Component and not the middlewares, because the content must be serializable as it is being printed into a HTML template.
### As HTML Page
If you simply want to render the Playground HTML on your own, for example when implementing a GraphQL Server, there are 2 options for you:
1. [The bare minimum HTML needed to render the Playground](https://github.com/prismagraphql/graphql-playground/blob/main/packages/graphql-playground-html/minimal.html)
2. [The Playground HTML with full loading animation](https://github.com/prismagraphql/graphql-playground/blob/main/packages/graphql-playground-html/withAnimation.html)
Note: In case you do not want to serve assets from a CDN (like jsDelivr) and instead use a local copy, you will need to install `graphql-playground-react` from npm, and then replace all instances of `//cdn.jsdelivr.net/npm` with `./node_modules`. An example can be found [here](https://github.com/prismagraphql/graphql-playground/blob/main/packages/graphql-playground-html/minimalWithoutCDN.html)
### As React Component
#### Install
```sh
yarn add graphql-playground-react
```
#### Use
GraphQL Playground provides a React component responsible for rendering the UI and Session management.
There are **3 dependencies** needed in order to run the `graphql-playground-react` React component.
1. _Open Sans_ and _Source Code Pro_ fonts
2. Rendering the `` component
The GraphQL Playground requires **React 16**.
Including Fonts (`1.`)
```html
```
Including stylesheet and the component (`2., 3.`)
```js
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { Playground, store } from 'graphql-playground-react'
ReactDOM.render(
,
document.body,
)
```
### As Server Middleware
#### Install
```sh
# Pick the one that matches your server framework
yarn add graphql-playground-middleware-express # for Express or Connect
yarn add graphql-playground-middleware-hapi
yarn add graphql-playground-middleware-koa
yarn add graphql-playground-middleware-lambda
```
#### Usage with example
We have a full example for each of the frameworks below:
- **Express:** See [packages/graphql-playground-middleware-express/examples/basic](https://github.com/prismagraphql/graphql-playground/tree/main/packages/graphql-playground-middleware-express/examples/basic)
- **Hapi:** See [packages/graphql-playground-middleware-hapi](https://github.com/prismagraphql/graphql-playground/tree/main/packages/graphql-playground-middleware-hapi)
- **Koa:** See [packages/graphql-playground-middleware-koa](https://github.com/prismagraphql/graphql-playground/tree/main/packages/graphql-playground-middleware-koa)
- **Lambda (as serverless handler):** See [serverless-graphql-apollo](https://github.com/serverless/serverless-graphql-apollo) or a quick example below.
### As serverless handler
#### Install
```sh
yarn add graphql-playground-middleware-lambda
```
#### Usage
`handler.js`
```js
import lambdaPlayground from 'graphql-playground-middleware-lambda'
// or using require()
// const lambdaPlayground = require('graphql-playground-middleware-lambda').default
exports.graphqlHandler = function graphqlHandler(event, context, callback) {
function callbackFilter(error, output) {
// eslint-disable-next-line no-param-reassign
output.headers['Access-Control-Allow-Origin'] = '*'
callback(error, output)
}
const handler = graphqlLambda({ schema: myGraphQLSchema })
return handler(event, context, callbackFilter)
}
exports.playgroundHandler = lambdaPlayground({
endpoint: '/dev/graphql',
})
```
`serverless.yml`
```yaml
functions:
graphql:
handler: handler.graphqlHandler
events:
- http:
path: graphql
method: post
cors: true
playground:
handler: handler.playgroundHandler
events:
- http:
path: playground
method: get
cors: true
```
#### Security Issue
There is an [XSS Reflection Vulnerability](./SECURITY.md) when using these middlewares with unsanitized user input before
## Development
```sh
$ cd packages/graphql-playground-react
$ yarn
$ yarn start
```
Open
[localhost:3000/localDev.html?endpoint=https://api.graph.cool/simple/v1/swapi](http://localhost:3000/localDev.html?endpoint=https://api.graph.cool/simple/v1/swapi) for local development!
### Contributing to this project
This repository is managed by EasyCLA. Project participants must sign the free ([GraphQL Specification Membership agreement](https://preview-spec-membership.graphql.org) before making a contribution. You only need to do this one time, and it can be signed by [individual contributors](http://individual-spec-membership.graphql.org/) or their [employers](http://corporate-spec-membership.graphql.org/).
To initiate the signature process please open a PR against this repo. The EasyCLA bot will block the merge if we still need a membership agreement from you.
You can find [detailed information here](https://github.com/graphql/graphql-wg/tree/main/membership). If you have issues, please email [operations@graphql.org](mailto:operations@graphql.org).
If your company benefits from GraphQL and you would like to provide essential financial support for the systems and people that power our community, please also consider membership in the [GraphQL Foundation](https://foundation.graphql.org/join).
## Custom Theme
From `graphql-playground-react@1.7.0` on you can provide a `codeTheme` property to the React Component to customize your color theme.
These are the available options:
```ts
export interface EditorColours {
property: string
comment: string
punctuation: string
keyword: string
def: string
qualifier: string
attribute: string
number: string
string: string
builtin: string
string2: string
variable: string
meta: string
atom: string
ws: string
selection: string
cursorColor: string
editorBackground: string
resultBackground: string
leftDrawerBackground: string
rightDrawerBackground: string
}
```
### Versions
This is repository is a "mono repo" and contains multiple packages using [Yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/). Please be aware that versions are **not** synchronised between packages. The versions of the [release page](https://github.com/graphcool/graphql-playground/releases) refer to the electron app.
### Packages
In the folder `packages` you'll find the following packages:
- `graphql-playground-electron`: Cross-platform electron app which uses `graphql-playground-react`
- `graphql-playground-html`: Simple HTML page rendering a version of `graphql-playground-react` hosted on JSDeliver
- `graphql-playground-middleware-express`: Express middleware using `graphql-playground-html`
- `graphql-playground-middleware-hapi`: Hapi middleware using `graphql-playground-html`
- `graphql-playground-middleware-koa`: Koa middleware using `graphql-playground-html`
- `graphql-playground-middleware-lambda`: AWS Lambda middleware using `graphql-playground-html`
- `graphql-playground-react`: Core of GraphQL Playground built with ReactJS
## Help & Community [](https://discord.gg/EXUYPaY)
Join our [Discord Server](https://discord.gg/EXUYPaY) if you run into issues or have questions. We love talking to you!
================================================
FILE: SECURITY.md
================================================
# Known Vulnerabilities
- [2021: Introspection Schema Phishing Attack XSS Vulnerability](docs/security/2021-schema-xss-phishing-attack.md)
- [2020: XSS Reflection Vulnerability](docs/security/2020-xss-template-injection.md)
================================================
FILE: docs/security/2020-xss-template-injection.md
================================================
## XSS Reflection Vulnerability
the origin of the vulnerability is in `renderPlaygroundPage`, found in `graphql-playground-html`
### Impact
When using
- `renderPlaygroundPage()`,
- `koaPlayground()`
- `expressPlayground()`
- `koaPlayground()`
- `lambdaPlayground()`
- any downstream dependent packages that use these functions
without sanitization of user input, your application is vulnerable to an XSS Reflection Attack. This is a serious vulnerability that could allow for exfiltration of data or user credentials, or to disrupt systems.
We've provided ['an example of the xss using the express middleware]('https://github.com/prisma-labs/graphql-playground/tree/main/packages/graphql-playground-middleware-express/examples/xss-attack')
### Impacted Packages
**All versions of these packages are impacted until those specified below**, which are now safe for user defined input:
- `graphql-playground-html`: **☔ safe** @ `1.6.22`
- `graphql-playground-express` **☔ safe** @ `1.7.16`
- `graphql-playground-koa` **☔ safe** @ `1.6.15`
- `graphql-playground-hapi` **☔ safe** @ `1.6.13`
- `graphql-playground-lambda` **☔ safe** @ `1.7.17`
### Static input was always safe
These examples are safe for _all versions_ **because input is static**
with `express` and `renderPlaygroundPage`:
```js
app.get('/playground', (req) => {
res.html(
renderPlaygroundPage({
endpoint: `/our/graphql`,
}),
)
next()
})
```
with `expressPlayground`:
```js
// params
app.get('/playground', (req) =>
expressPlayground({
endpoint: `/our/graphql`,
settings: { 'editor.theme': req.query.darkMode ? 'dark' : 'light' },
}),
)
```
with `koaPlayground`:
```js
const koa = require('koa')
const koaRouter = require('koa-router')
const koaPlayground = require('graphql-playground-middleware-koa')
const app = new koa()
const router = new koaRouter()
router.all('/playground', koaPlayground({ endpoint: '/graphql' }))
```
### Vulnerable Examples
Here are some examples where the vulnerability would be present before the patch, because of unfiltered user input
```js
const express = require('express')
const expressPlayground = require('graphql-playground-middleware-express')
.default
const app = express()
app.use(express.json())
// params
app.get('/playground/:id', (req) =>
expressPlayground({
endpoint: `/our/graphql/${req.params.id}`,
}),
)
// params
app.get('/playground', (req) =>
expressPlayground({
endpoint: `/our/graphql`,
// any settings that are unsanitized user input, not just `endpoint`
settings: { 'editor.fontFamily': req.query.font },
}),
)
```
[See a proof of concept](packages/graphql-playground-html/examples/xss-attack) to understand the vulnerability better
### Workaround
To fix this issue without the update, you can sanitize however you want.
We suggest using [`xss`](https://www.npmjs.com/package/xss) (what we use for our own fix)
For example, with `graphql-playground-middleware-express`:
```js
const express = require('express')
const { filterXSS } = require('xss')
const expressPlayground = require('graphql-playground-middleware-express')
.default
const app = express()
const filter = (val) => filterXSS(val, {
whitelist: [],
stripIgnoreTag: true,
stripIgnoreTagBody: ['script']
})
// simple example
app.get('/playground/:id', (req) =>
expressPlayground({ endpoint: `/graphql/${filter(req.params.id)}` })
// advanced params
app.get('/playground', (req) =>
expressPlayground(JSON.parse(filter(JSON.stringify(req.query))))
```
[See a proof of concept workaround](packages/graphql-playground-html/examples/xss-attack), example #3
================================================
FILE: docs/security/2021-schema-xss-phishing-attack.md
================================================
## GraphQL Playground introspection schema template injection attack: Advisory Statement
This is a security advisory for an XSS vulnerability in `graphql-playground`.
A similar vulnerability affects `graphiql`, the package from which `graphql-playground` was forked. There is a corresponding `graphiql` [advisory](https://github.com/graphql/graphiql/security/advisories/GHSA-x4r7-m2q9-69c8).
- [1. Impact](#1-impact)
- [2. Scope](#2-scope)
- [3. Patches](#3-patches)
- [4. Reproducing the exploit](#4-reproducing-the-exploit)
- [5. Credit](#5-credit)
- [6. For more information](#6-for-more-information)
### 1. Impact
All versions of `graphql-playground-react` older than `graphql-playground-react@1.7.28` are vulnerable to compromised HTTP schema introspection responses or `schema` prop values with malicious GraphQL type names, exposing a dynamic XSS attack surface that can allow code injection on operation autocomplete.
In order for the attack to take place, the user must load a malicious schema in `graphql-playground`. There are several ways this can occur, including by specifying the URL to a malicious schema in the `endpoint` query parameter. If a user clicks on a link to a GraphQL Playground installation that specifies a malicious server, arbitrary JavaScript can run in the user's browser, which can be used to exfiltrate user credentials or other harmful goals.
### 2. Scope
This advisory describes the impact on the `graphql-playground-react` package. The vulnerability also affects `graphiql`, the package from which `graphql-playground` was forked, with a less severe impact; see the [`graphiql` advisory](https://github.com/graphql/graphiql/security/advisories/GHSA-x4r7-m2q9-69c8) for details. It affects all versions of `graphql-playground-react` older than `v1.7.28`.
This vulnerability was introduced with the first public release of `graphql-playground`, so it impacts both the original legacy `graphql-playground` and the contemporary `graphql-playground-react` npm package. It is most easily exploited on `graphql-playground-react@1.7.0` and newer, as that release added functionality which made it possible to override the endpoint URL via query parameter even if it is explicitly specified in the code.
`graphql-playground-react` is commonly loaded via the `graphql-playground-html` package or a middleware package that wraps it (`graphql-playground-express`, `graphql-playground-middleware-koa`, `graphql-playground-middleware-hapi`, or `graphql-playground-middleware-lambda`). By default, these packages render an HTML page which loads the *latest* version of `graphql-playground-react` through a CDN. If you are using one of these packages to install GraphQL Playground on your domain *and you do not explicitly pass the `version` option to `renderPlaygroundPage` or the middleware function*, then you do not need to take any action to resolve this vulnerability, as the latest version of the React app will automatically be loaded.
`graphql-playground-react` is also commonly loaded via HTML served by Apollo Server. Apollo Server always pins a specific version of `graphql-playground-react`, so if you are using Apollo Server you do need to take action to resolve this vulnerability. See the [Apollo Server advisory](https://github.com/apollographql/apollo-server/security/advisories/GHSA-qm7x-rc44-rrqw) for details.
### 3. Patches
`graphql-playground-react@1.7.28` addresses this issue via defense in depth:
- **HTML-escaping text** that should be treated as text rather than HTML. In most of the app, this happens automatically because React escapes all interpolated text by default. However, one vulnerable component uses the unsafe `innerHTML` API and interpolated type names directly into HTML. We now properly escape that type name, which fixes the known vulnerability.
- **Validates the schema** upon receiving the introspection response or schema changes. Schemas with names that violate the GraphQL spec will no longer be loaded. (This includes preventing the Doc Explorer from loading.) This change is also sufficient to fix the known vulnerability.
- **Ensuring that user-generated HTML is safe**. Schemas can contain Markdown in `description` and `deprecationReason` fields, and the web app renders them to HTML using the `markdown-it` library. Prior to `graphql-playground-react@1.7.28`, GraphQL Playground used two separate libraries to render Markdown: `markdown-it` and `marked`. As part of the development of `graphql-playground-react@1.7.28`, we verified that our use of `markdown-it` prevents the inclusion of arbitrary HTML. We use `markdown-it` without setting `html: true`, so we are comfortable relying on [`markdown-it`'s HTML escaping](https://github.com/markdown-it/markdown-it/blob/master/docs/security.md) here. We considered running a second level of sanitization over all rendered Markdown using a library such as `dompurify` but believe that is unnecessary as `markdown-it`'s sanitization appears to be adequate. `graphiql@1.4.3` does update to the latest version of `markdown-it` (v12, from v10) so that any security fixes in v11 and v12 will take effect. On the other hand, [`marked`](https://github.com/markedjs/marked) recommends the use of a separate HTML sanitizer if its input is untrusted. In this release, we switch the one component which uses `marked` to use `markdown-it` like the rest of the app.
**If you are using `graphql-playground-react` directly in your client app**, upgrade to version 1.7.28 or later.
**If you are using `graphql-playground-html` or a package which starts with `graphql-playground-middleware-` in your server** and you are passing the `version` option to a function imported from that package, change that `version` option to be at least `"1.7.28"`.
**If you are using `graphql-playground-html` or a package which starts with `graphql-playground-middleware-` in your server** and you are **NOT** passing the `version` option to a function imported from that package, no action is necessary; your app automatically loads the latest version of `graphql-playground-react` from CDN.
### 4. Reproducing the exploit
We are hosting a "malicious" server at https://graphql-xss-schema.netlify.app/graphql . This server has a hard-coded introspection result that includes unsafe HTML in type names.
If you manually change a GraphQL Playground installation to use that endpoint, clear the operation pane, and type `{x` into the operation pane, an alert will pop up; this demonstrates execution of code provided by the malicious server.
An URL like https://YOUR-PLAYGROUND-SERVER/?endpoint=https%3A%2F%2Fgraphql-xss-schema.netlify.app%2Fgraphql&query=%7B will load already configured with the endpoint in question. (This URL-based exploit works on `graphql-playground-react@1.7.0` and newer; older versions may be protected from this particular URL-based exploit depending on their configuration.)
### 5. Credit
This vulnerability was discovered by [@Ry0taK](https://github.com/Ry0taK), thank you! :1st_place_medal:
Others who contributed:
- extensive help from [@glasser](https://github.com/glasser) at [Apollo](https://github.com/apollographql)
- [@acao](https://github.com/acao)
- [@imolorhe](https://github.com/imolorhe)
- [@divyenduz](https://github.com/divyenduz)
- [@dotansimha](https://github.com/dotansimha)
- [@timsuchanek](http://github.com/timsuchanek)
- [@benjie](https://github.com/Ry0taK) and many others who provided morale support
### 6. For more information
If you have any questions or comments about this advisory:
- The `graphiql` advisory document contains [more information](https://github.com/graphql/graphiql/blob/main/docs/security/2021-introspection-schema-xss.md#2-more-details-on-the-vulnerability) about how both the client-side and server-side vulnerabilities work
- Open an issue in the [graphql-playground repo](https://github.com/graphql/graphql-playground/new/issues)
================================================
FILE: lerna.json
================================================
{
"version": "independent",
"packages": ["packages/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"command": {
"publish": {
"allowBranch": ["main"],
"preDistTag": "next",
"distTag": "latest",
"conventionalCommits": true
},
"version": {
"ignoreChanges": [
"'__tests__' '**/*.spec.js' '**/*.spec.js' '**/*.spec.ts'",
"**/examples/**",
"**/public/**",
"public/**",
"packages/*/yarn.lock",
"**/*.md"
],
"message": "chore(release): publish"
}
}
}
================================================
FILE: package.json
================================================
{
"private": true,
"workspaces": [
"packages/*"
],
"scripts": {
"build": "bash scripts/build.sh",
"show-versions": "bash scripts/versions.sh",
"release-html": "bash scripts/release-html.sh",
"release-react": "bash scripts/release-react.sh",
"prepublishOnly": "yarn build"
},
"devDependencies": {
"@hapi/hapi": "19.1.1",
"@types/node": "12.12.34",
"aws-lambda": "1.0.5",
"express": "4.17.1",
"koa": "2.11.0",
"lerna": "^3.22.0",
"rimraf": "3.0.2"
},
"dependencies": {
"dotenv": "8.2.0"
}
}
================================================
FILE: packages/graphql-playground-electron/.babelrc
================================================
{
"presets": [
"@babel/preset-env", "@babel/preset-react"
],
"plugins": [
"styled-jsx-postcss/babel",
"@babel/plugin-proposal-object-rest-spread",
"babel-plugin-styled-components"
]
}
================================================
FILE: packages/graphql-playground-electron/.gitignore
================================================
.DS_STORE
*.log
.vscode
node_modules
dist
.tmp
lib
.idea
*.iml
*.swp
*.swo
.envrc
.netlify
.tags
.yarnclean
.awcache
dll
.happypack
build-electron
================================================
FILE: packages/graphql-playground-electron/CHANGELOG.md
================================================
# Change Log
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.8.15](https://github.com/graphcool/graphql-playground/compare/graphql-playground-electron@1.8.14...graphql-playground-electron@1.8.15) (2020-10-20)
**Note:** Version bump only for package graphql-playground-electron
## [1.8.14](https://github.com/graphcool/graphql-playground/compare/graphql-playground-electron@1.8.13...graphql-playground-electron@1.8.14) (2020-09-15)
**Note:** Version bump only for package graphql-playground-electron
## [1.8.13](https://github.com/graphcool/graphql-playground/compare/graphql-playground-electron@1.8.12...graphql-playground-electron@1.8.13) (2020-08-30)
**Note:** Version bump only for package graphql-playground-electron
## [1.8.12](https://github.com/graphcool/graphql-playground/compare/graphql-playground-electron@1.8.11...graphql-playground-electron@1.8.12) (2020-08-30)
### Bug Fixes
* upgrades, schema viewer display & width adjustment ([2bb34bb](https://github.com/graphcool/graphql-playground/commit/2bb34bb8fb8c356e10435727a3f82cd23464b6b6))
## 1.8.11 (2020-06-07)
### Bug Fixes
* hapi and koa mws for next release ([#1217](https://github.com/graphcool/graphql-playground/issues/1217)) ([40c35fc](https://github.com/graphcool/graphql-playground/commit/40c35fc4c73b939d002c9d2dff51eed5dd0b6aa9))
* **deps:** [security] bump lodash.merge ([2e6b2e5](https://github.com/graphcool/graphql-playground/commit/2e6b2e5fc59c9f7fbbad5398d0defc1a0b3bd849))
* **deps:** [security] bump minimist ([a77ca5d](https://github.com/graphcool/graphql-playground/commit/a77ca5d64d9fe7b209e8a732d189570ba750eb3e))
* **deps:** update deps and toolchain, move back to using yarn… ([#1191](https://github.com/graphcool/graphql-playground/issues/1191)) ([824c7a5](https://github.com/graphcool/graphql-playground/commit/824c7a57f0284f022726a8b8840aafc3e8720ccd))
* **deps-dev:** [security] bump electron ([98ffd72](https://github.com/graphcool/graphql-playground/commit/98ffd722362820a867a9eef9ef51e5098f45afde))
* **deps-dev:** [security] bump webpack-dev-server ([a0a784d](https://github.com/graphcool/graphql-playground/commit/a0a784dd57af53799e781b876c9c80354e37c6a0))
## 1.8.10 (2019-02-23)
## 1.8.9 (2019-02-01)
## 1.8.7 (2019-01-28)
## 1.8.2 (2018-11-14)
# 1.8.0 (2018-10-16)
### Features
* update graphql-config dependency ([bb6baa1](https://github.com/graphcool/graphql-playground/commit/bb6baa1c26a0f72af73018acbe722c979f892322))
## 1.6.2 (2018-07-06)
## 1.6.1 (2018-06-26)
# 1.6.0 (2018-05-31)
### Bug Fixes
* **deps:** update dependency styled-jsx to v2.2.6 ([#543](https://github.com/graphcool/graphql-playground/issues/543)) ([b110cc9](https://github.com/graphcool/graphql-playground/commit/b110cc95c3d68cb7fae136b5d1dbbeb49b8a603d))
## 1.5.9 (2018-05-25)
### Bug Fixes
* update react dep ([9588758](https://github.com/graphcool/graphql-playground/commit/95887589a7d56570ce6007cd7d164bfc89c8a690))
## 1.5.7 (2018-05-08)
### Bug Fixes
* bump version ([2fdab26](https://github.com/graphcool/graphql-playground/commit/2fdab263f904fc38e3373f5cc3c8dcaa9342d42e))
## 1.5.6 (2018-04-24)
### Bug Fixes
* **electron:** update graphql-config-extension-prisma ([8fd45a1](https://github.com/graphcool/graphql-playground/commit/8fd45a1c27172722c335b4c84b793813c2d52b7e))
## 1.5.4 (2018-04-12)
## 1.5.2 (2018-04-02)
## 1.5.1 (2018-04-02)
# 1.5.0 (2018-04-01)
# 1.5.0-rc.5 (2018-03-28)
### Bug Fixes
* **electron:** endpoint injection ([39a1110](https://github.com/graphcool/graphql-playground/commit/39a1110a0284ef05c73ada3892c585c85e6e14a2))
# 1.5.0-rc.4 (2018-03-26)
# 1.5.0-rc.2 (2018-03-26)
# 1.5.0-rc.1 (2018-03-23)
## 1.4.5 (2018-03-15)
## 1.4.4 (2018-03-02)
### Bug Fixes
* **deps:** rm not needed graphql-playground dep ([79b3ef1](https://github.com/graphcool/graphql-playground/commit/79b3ef1f987484923c272dbbc18890433e5f3340))
* **deps:** update dependency electron-updater to v2.20.1 ([#528](https://github.com/graphcool/graphql-playground/issues/528)) ([ab2e9c3](https://github.com/graphcool/graphql-playground/commit/ab2e9c3d43f2225b546d93473bbde47535365f8e))
* **deps:** update dependency graphql-config-extension-graphcool to v1.0.6 ([#545](https://github.com/graphcool/graphql-playground/issues/545)) ([9ef55be](https://github.com/graphcool/graphql-playground/commit/9ef55bec0fd3359121ab65fa2a5ff55231feeaec))
* **deps:** update deps ([c17a8d2](https://github.com/graphcool/graphql-playground/commit/c17a8d25779ba7d3e6e0b18e9b45df86c410c4f5))
* **deps:** update deps in electron app ([772a04c](https://github.com/graphcool/graphql-playground/commit/772a04cfc684c2e38c376a9ef3abe311090c96d4))
* **settings:** Expose settings programmatically, update deps ([0356557](https://github.com/graphcool/graphql-playground/commit/03565573869f240675aaa5399bb5f0ac097455c5))
## 1.4.2 (2018-01-22)
## 1.4.1 (2018-01-22)
### Bug Fixes
* **electron:** Add zoom on CMD + and CMD - ([cc2c4ed](https://github.com/graphcool/graphql-playground/commit/cc2c4edc48c986aa55a76c56362e5109eb393b1f))
* **electron:** postcss config ([55187dc](https://github.com/graphcool/graphql-playground/commit/55187dcbed2be9c48f24424e6d3ee5f2d60e11e5))
## 1.3.24 (2018-01-13)
### Bug Fixes
* **build:** Fix build, temporarily disable yarn workspace, update graphcool-styles & graphcool-ui de ([af501d7](https://github.com/graphcool/graphql-playground/commit/af501d7a754a14dbacc76439a77434f892828482))
* **deps:** Updated graphql-config-extension-graphcool ([ef83c09](https://github.com/graphcool/graphql-playground/commit/ef83c097c018a42f7ee65529d6af4ea3928a4281))
## 1.3.21 (2018-01-04)
## 1.3.20 (2018-01-04)
## 1.3.19 (2018-01-03)
### Bug Fixes
* electron without graphql config ([bf5a722](https://github.com/graphcool/graphql-playground/commit/bf5a722f484a644a1a64e536ba6bd375dbfce928))
## 1.3.17 (2018-01-03)
## 1.3.16 (2017-12-27)
## 1.3.14 (2017-12-26)
## 1.3.12 (2017-12-24)
## 1.3.10 (2017-12-20)
### Bug Fixes
* should suggest `graphql-cli` instead of `graphql` ([bbfa0ca](https://github.com/graphcool/graphql-playground/commit/bbfa0ca935dea618208f76c58b86363f27ddf595))
## 1.3.9 (2017-12-14)
## 1.3.8-beta.1 (2017-12-12)
### Bug Fixes
* Capture keyboard event so it's always called (regardless of any stopPropagation by children) ([56634d0](https://github.com/graphcool/graphql-playground/commit/56634d0c7079854888a7d2d5ec0936d8fd0b06a5))
* change package.json > author > url to match URI schema ([0a84b89](https://github.com/graphcool/graphql-playground/commit/0a84b89f6a94aeecdf48217b1de661181e0b5741))
### Features
* Add Settings to the menu with the shortcut to open on CMD + , (Fixes [#290](https://github.com/graphcool/graphql-playground/issues/290)) ([ead0cae](https://github.com/graphcool/graphql-playground/commit/ead0cae5c4d069f87b61a6cd17d5855f5e215b81))
# 1.3.0 (2017-12-01)
# 1.2.0 (2017-11-24)
## 1.1.1 (2017-11-03)
### Bug Fixes
* **desktop:** allow urls without tld - fixes [#138](https://github.com/graphcool/graphql-playground/issues/138) ([be3d2cf](https://github.com/graphcool/graphql-playground/commit/be3d2cfe219211393b6438332b553e6e3d9b7493))
================================================
FILE: packages/graphql-playground-electron/README.md
================================================
# Playground
Repository for the electron playground app.
## Development
```sh
yarn install
yarn start
```
## Production build
```sh
yarn release
```
This will create a `build-electron` folder where you will have the compiled electron app. (The assets will automatically be uploaded to Github if the `GH_TOKEN` env variable is set. See [here](https://www.electron.build/publishing-artifacts) for more.)
================================================
FILE: packages/graphql-playground-electron/package.json
================================================
{
"name": "graphql-playground-electron",
"productName": "GraphQL Playground",
"homepage": "https://github.com/graphcool/graphql-playground",
"repository": "graphcool/graphql-playground",
"description": "GraphQL IDE for better development workflows (GraphQL Subscriptions, interactive docs & collaboration)",
"version": "1.8.15",
"private": true,
"author": {
"name": "Graphcool",
"email": "hello@graph.cool",
"url": "https://www.graph.cool"
},
"main": "lib/main",
"build": {
"appId": "cool.graph.playground",
"mac": {
"category": "public.app-category.developer-tools",
"icon": "static/icons/icon.icns"
},
"win": {
"icon": "static/icons/icon.ico"
},
"linux": {
"category": "Development",
"target": [
"AppImage",
"snap",
"deb"
]
},
"nsis": {
"oneClick": false,
"perMachine": true,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"runAfterFinish": false
},
"files": [
"lib/**/!(*.map)"
],
"directories": {
"output": "build-electron"
},
"fileAssociations": {
"ext": "graphql",
"name": "GraphQL",
"role": "Editor"
},
"protocols": {
"name": "graphql-playground-protocol",
"schemes": [
"graphql-playground"
]
}
},
"scripts": {
"build:ts": "rimraf lib && tsc",
"build:webpack": "rimraf ./dist && NODE_ENV=production GRAPHQL_ENDPOINT=$BACKEND_ADDR/system webpack --config webpack.config.build.js && cp -r static/* dist",
"build": "npm run build:ts && npm run build:webpack && cp -r dist lib && rimraf ./build:electron",
"release": "npm run build && electron-builder -lmw",
"release:mac": "npm run build && electron-builder -m",
"release:win": "npm run build && electron-builder -w",
"release:linux": "npm run build && electron-builder -l",
"library": "rimraf .happypack && NODE_ENV=production webpack --config webpack.library.js -p",
"lint": "tslint \"src/**/*.ts{,x}\" && lint-staged",
"start": "yarn build:ts && concurrently \"yarn start:react\" \"wait-on http://localhost:4040/ && yarn start:electron\"",
"start:react": "webpack-dev-server --hot --profile --history-api-fallback --host 0.0.0.0 --port 4040",
"start:electron": "electron lib/main",
"stats": "NODE_ENV=production webpack --config webpack.library.js --profile --json > stats.json",
"test": "npm run lint",
"precommit": "lint-staged",
"prettier": "prettier --single-quote --no-semi --trailing-comma all --write '*.{js,ts,tsx}' 'src/**/*.{js,ts,tsx}'"
},
"lint-staged": {
"*.{ts,tsx}": [
"prettier --single-quote --no-semi --trailing-comma all --write",
"tslint",
"git add"
]
},
"pre-push": [
"test-quick"
],
"dependencies": {
"@types/ms": "0.7.30",
"classnames": "2.2.5",
"core-js": "^3.6.5",
"date-fns": "1.29.0",
"electron-is-dev": "0.3.0",
"electron-localshortcut": "3.1.0",
"electron-log": "2.2.14",
"electron-updater": "^4.0.0",
"find-up": "^2.1.0",
"graphcool-styles": "0.2.7",
"graphcool-ui": "^0.0.14",
"graphql": "^15.3.0",
"graphql-config": "^2.2.2",
"graphql-config-extension-graphcool": "^1.0.11",
"graphql-config-extension-prisma": "^0.3.0",
"graphql-playground-html": "^1.6.29",
"graphql-playground-react": "^1.7.27",
"immutable": "4.0.0-rc.9",
"js-yaml": "^3.11.0",
"lodash.merge": "^4.6.2",
"minimist": "^1.2.3",
"ms": "^2.1.1",
"query-string": "^5.0.1",
"raven": "^2.4.2",
"react": "16.13.1",
"react-dom": "^16.4.0",
"react-redux": "^7.2.1",
"redux": "^4.0.5",
"redux-localstorage": "^1.0.0-rc5",
"redux-localstorage-filter": "^0.1.1",
"regenerator-runtime": "^0.13.7",
"reselect": "^3.0.1",
"semver": "^5.5.0",
"styled-jsx": "2.2.6",
"styled-jsx-postcss": "git+https://github.com/timsuchanek/styled-jsx-postcss#build3",
"sweetalert2": "^7.21.0",
"validator": "^9.2.0"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
"@babel/core": "^7.0.0",
"@babel/plugin-proposal-class-properties": "^7.0.0",
"@babel/plugin-proposal-json-strings": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
"@babel/plugin-syntax-import-meta": "^7.0.0",
"@babel/plugin-transform-runtime": "^7.12.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@types/classnames": "2.2.3",
"@types/deasync": "0.1.0",
"@types/jest": "22.2.3",
"@types/node": "12.12.34",
"@types/react": "16.9.32",
"@types/react-addons-test-utils": "0.14.20",
"@types/react-dom": "~16.9.6",
"@types/react-redux": "7.1.9",
"@types/zen-observable": "^0.5.3",
"awesome-typescript-loader": "5.0.0",
"babel-core": "^7.0.0-bridge.0",
"babel-jest": "^23.4.2",
"babel-loader": "^8.0.0",
"babel-plugin-styled-components": "^1.8.0",
"babili-webpack-plugin": "0.1.2",
"concurrently": "3.5.1",
"css-loader": "0.28.11",
"electron": "2.0.18",
"electron-builder": "^22.8.1",
"electron-devtools-installer": "3.1.1",
"extract-text-webpack-plugin": "3.0.2",
"file-loader": "1.1.11",
"fork-ts-checker-webpack-plugin": "0.4.1",
"happypack": "5.0.0",
"html-loader": "^1.3.1",
"html-webpack-plugin": "3.2.0",
"identity-obj-proxy": "3.0.0",
"json-loader": "0.5.7",
"lint-staged": "7.1.2",
"node-noop": "1.0.0",
"polished": "1.9.2",
"postcss-inherit": "git+https://github.com/timsuchanek/postcss-inherit#build3",
"postcss-loader": "0.9.1",
"postcss-simple-vars": "3.1.0",
"prettier": "2.0.2",
"raw-loader": "0.5.1",
"react-addons-test-utils": "15.6.2",
"react-test-renderer": "16.4.0",
"redux-mock-store": "1.5.1",
"rimraf": "3.0.2",
"style-loader": "0.20.1",
"svgo-loader": "1.2.1",
"ts-loader": "3.4.0",
"tslint": "5.10.0",
"tslint-graphcool-frontend": "0.0.3",
"tslint-loader": "3.6.0",
"typescript": "3.8.3",
"url-loader": "0.6.2",
"wait-on": "2.1.0",
"webpack": "4.44.1",
"webpack-bundle-analyzer": "3.3.2",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "3.11.0",
"webpack-uglify-parallel": "0.1.4"
},
"resolutions": {
"**/graphql": "0.13.2",
"**/**/graphql": "0.13.2",
"**/**/**/graphql": "0.13.2",
"**/**/**/**/graphql": "0.13.2",
"**/**/**/**/**/graphql": "0.13.2",
"**/**/**/**/**/**/graphql": "0.13.2"
}
}
================================================
FILE: packages/graphql-playground-electron/postcss.config.js
================================================
module.exports = ctx => ({
plugins: [
require('postcss-simple-vars')({
variables: () => require('graphcool-styles/dist/variables/variables.js'),
}),
require('postcss-inherit')({
globalStyles: 'node_modules/graphcool-styles/dist/styles.css',
propertyRegExp: /^(inherit|extend|p)s?:?$/i,
}),
],
})
================================================
FILE: packages/graphql-playground-electron/src/main/createWindow.ts
================================================
import { BrowserWindow, app, ipcMain } from 'electron'
import * as path from 'path'
import dev = require('electron-is-dev')
import { newWindowConfig } from '../shared/utils'
import * as log from 'electron-log'
import { WindowContext } from './types'
export function createWindow(windowContext: WindowContext) {
// Create the browser window.
const newWindow = new BrowserWindow(newWindowConfig)
newWindow.loadURL(
dev
? 'http://localhost:4040' // Dev server ran by react-scripts
: `file://${path.join(__dirname, '..', '/dist/index.html')}`, // Bundled application
)
if (dev) {
// If dev mode install react and redux extension
// Also open the devtools
const {
default: installExtension,
REACT_DEVELOPER_TOOLS,
REDUX_DEVTOOLS,
} = require('electron-devtools-installer')
installExtension(REACT_DEVELOPER_TOOLS)
.then(name => log.info(`Added Extension: ${name}`))
.catch(err => log.info('An error occurred: ', err))
installExtension(REDUX_DEVTOOLS)
.then(name => log.info(`Added Extension: ${name}`))
.catch(err => log.info('An error occurred: ', err))
// newWindow.webContents.openDevTools()
}
windowContext.windows.add(newWindow)
windowContext.windowById.set(newWindow.id, newWindow)
// Emitted when the window is closed.
const id = newWindow.id
newWindow.on('closed', () => {
if (process.platform !== 'darwin' && windowContext.windows.size === 0) {
app.quit()
}
windowContext.windows.delete(newWindow)
windowContext.windowById.delete(id)
windowContext.windowByPath.forEach((window, cwd) => {
if (window === newWindow) {
windowContext.windowByPath.delete(cwd)
}
})
})
// electronLocalShortcut.register(newWindow, 'Cmd+Shift+]', () => {
// send('Tab', 'Next')
// })
// electronLocalShortcut.register(newWindow, 'Cmd+Shift+[', () => {
// send('Tab', 'Prev')
// })
windowContext.readyWindowsPromises[newWindow.id] = new Promise(resolve =>
ipcMain.once('ready', resolve),
)
return newWindow
}
================================================
FILE: packages/graphql-playground-electron/src/main/index.ts
================================================
// TODO enable tslint
// /* tslint:disable */
import {
app,
BrowserWindow,
globalShortcut,
ipcMain,
protocol,
Menu,
} from 'electron'
import * as queryString from 'query-string'
import * as fs from 'fs'
import * as log from 'electron-log'
import { buildTemplate } from './menu'
import { createWindow } from './createWindow'
import { WindowContext } from './types'
import { startUpdates } from './updates'
import * as Raven from 'raven'
Raven.config(
'https://cce868d3730e473e9350f1436da7d908:ff5d65389e404b28b5af1d97d8024414@sentry.io/297194',
).install()
log.transports.file.level = 'info'
log.transports.console.level = 'debug'
const windowContext: WindowContext = {
readyWindowsPromises: {},
windows: new Set(),
windowById: new Map(),
windowByPath: new Map(),
}
let appResolve
const appPromise = new Promise(resolve => (appResolve = resolve))
app.setAsDefaultProtocolClient('graphql-playground')
app.on('open-url', (event, url) => {
event.preventDefault()
const cutIndex = url.indexOf('//')
const query = url.slice(cutIndex + 2)
const input = queryString.parse(query)
let env
if (input.env) {
try {
env = JSON.parse(input.env)
} catch (e) {
// could
console.log('could not get env')
}
}
if (input.envPath) {
try {
env = JSON.parse(fs.readFileSync(input.envPath, 'utf-8'))
fs.unlinkSync(input.envPath)
} catch (e) {
//
}
}
const msg = JSON.stringify({
...input,
env,
})
forceSend('OpenUrl', msg, input.cwd)
})
app.on('open-file', (event, filePath) => {
event.preventDefault()
forceSend('OpenSelectedFile', filePath, filePath)
})
ipcMain.on('online-status-changed', (event, status) => {
process.env.CONNECTION = status
})
ipcMain.on('async', (event, arg) => {
const focusedWindow = BrowserWindow.getFocusedWindow()
if (focusedWindow) {
focusedWindow.close()
}
})
ipcMain.on('cwd', (event, msg) => {
const { cwd, id } = JSON.parse(msg)
const window = windowContext.windowById.get(id)
windowContext.windowByPath.set(cwd, window)
})
ipcMain.on('CloseWindow', (event, msg) => {
const { id } = JSON.parse(msg)
const window = windowContext.windowById.get(id)
window.close()
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', () => {
createWindow(windowContext)
startUpdates()
const menu = Menu.buildFromTemplate(buildTemplate(windowContext))
Menu.setApplicationMenu(menu)
ipcMain.on('get-file-data', event => {
log.info('get-file-data', event)
// this.fileAdded(event)
})
ipcMain.on('load-file-content', (event, filePath) => {
log.info('load-file-content', event, filePath)
})
protocol.registerFileProtocol('file:', (request, filePath) => {
log.info('file:', request, filePath)
})
if (appResolve) {
appResolve()
}
})
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow(windowContext)
}
})
app.on('will-quit', () => {
// Unregister all shortcuts.
globalShortcut.unregisterAll()
})
async function forceSend(channel: string, arg: string, byPath?: string) {
await appPromise
const currentWindows = BrowserWindow.getAllWindows()
let window
if (byPath) {
window = windowContext.windowByPath.get(byPath)
if (
!window &&
currentWindows.length === 1 &&
windowContext.windowByPath.size === 0
) {
window = currentWindows[0]
}
} else {
window = currentWindows[0]
}
let destroyed = null
try {
destroyed = window ? window.isDestroyed() : null
} catch (e) {
//
}
if (!window || destroyed) {
window = createWindow(windowContext)
}
await windowContext.readyWindowsPromises[window.id]
window.webContents.send(channel, arg)
}
================================================
FILE: packages/graphql-playground-electron/src/main/menu.ts
================================================
import {
MenuItemConstructorOptions,
BrowserWindow,
app,
autoUpdater,
} from 'electron'
import * as log from 'electron-log'
import { WindowContext } from './types'
import { createWindow } from './createWindow'
import { notify } from './notify'
export const buildTemplate = (
windowContext: WindowContext,
): MenuItemConstructorOptions[] => [
{
label: 'Application',
submenu: [
{
label: 'About GraphQL Playground',
selector: 'orderFrontStandardAboutPanel:',
} as MenuItemConstructorOptions,
{
label: 'Check For Updates',
click: () => {
autoUpdater.once('update-not-available', () => {
notify({
title: 'GraphQL Playground Updates',
body: 'Already up to date.',
})
})
autoUpdater.checkForUpdates()
},
},
{ type: 'separator' },
{
label: 'Settings',
accelerator: 'CmdOrCtrl+,',
click: () => send('Tab', 'Settings'),
},
{ type: 'separator' },
{
label: 'Hide GraphQL Playground',
accelerator: 'Cmd+H',
role: 'hide',
},
{
label: 'Hide Others',
accelerator: 'Option+Cmd+H',
role: 'hideOthers',
},
{
label: 'Show All',
role: 'unhide',
},
{ type: 'separator', visible: process.platform === 'darwin' },
{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click: () => app.quit(),
},
],
},
{
label: 'Workspace',
submenu: [
{
label: 'New Workspace',
accelerator: 'CmdOrCtrl+N',
click: () => {
createWindow(windowContext)
},
},
{
label: 'New Tab',
accelerator: 'CmdOrCtrl+T',
click: () => send('Tab', 'New'),
},
{ type: 'separator' },
{
label: 'Close Workspace',
accelerator: 'CmdOrCtrl+Shift+W',
role: 'close',
},
{
label: 'Close Tab',
accelerator: 'CmdOrCtrl+W',
click: () => send('Tab', 'Close'),
},
{
label: 'Open File',
accelerator: 'CmdOrCtrl+O',
click: () => send('File', 'Open'),
},
{
label: 'Save File',
accelerator: 'CmdOrCtrl+S',
click: () => send('File', 'Save'),
},
{
label: 'Reload Schema',
accelerator: 'CmdOrCtrl+R',
click: () => send('Tab', 'ReloadSchema'),
},
],
},
{
label: 'Edit',
submenu: [
{
label: 'Undo',
accelerator: 'CmdOrCtrl+Z',
selector: 'undo:',
} as MenuItemConstructorOptions,
{
label: 'Redo',
accelerator: 'Shift+CmdOrCtrl+Z',
selector: 'redo:',
} as MenuItemConstructorOptions,
{ type: 'separator' },
{
label: 'Cut',
accelerator: 'CmdOrCtrl+X',
selector: 'cut:',
} as MenuItemConstructorOptions,
{
label: 'Copy',
accelerator: 'CmdOrCtrl+C',
selector: 'copy:',
} as MenuItemConstructorOptions,
{
label: 'Paste',
accelerator: 'CmdOrCtrl+V',
selector: 'paste:',
} as MenuItemConstructorOptions,
{
label: 'Select All',
accelerator: 'CmdOrCtrl+A',
selector: 'selectAll:',
} as MenuItemConstructorOptions,
],
},
{
label: 'Window',
submenu: [
{
label: 'Next Tab',
accelerator: 'CmdOrCtrl+Alt+Right',
click: () => send('Tab', 'Next'),
},
{
label: 'Previous Tab',
accelerator: 'CmdOrCtrl+Alt+Left',
click: () => send('Tab', 'Next'),
},
{
label: 'Minimize',
accelerator: 'CmdOrCtrl+M',
click: () => {
const focusedWindow = BrowserWindow.getFocusedWindow()
if (focusedWindow) {
focusedWindow.minimize()
}
},
},
{ type: 'separator' },
{ label: 'Toggle Developer Tools', role: 'toggledevtools' },
],
},
]
function send(channel: string, arg: string) {
const focusedWindow = BrowserWindow.getFocusedWindow()
if (focusedWindow) {
log.info('sending to focused window', channel, arg)
focusedWindow.webContents.send(channel, arg)
} else {
log.info('no focused window')
}
}
================================================
FILE: packages/graphql-playground-electron/src/main/notify.ts
================================================
import { shell, Notification } from 'electron'
interface NotificationOptions {
title: string
body: string
url?: string
onClick?: () => void
}
export const notify = ({ title, body, url, onClick }: NotificationOptions) => {
const notification = new Notification({
title,
body,
silent: true,
})
if (url || onClick) {
notification.on('click', () => {
if (onClick) {
return onClick()
}
shell.openExternal(url)
})
}
notification.show()
console.log(`[Notification] ${title}: ${body}`)
}
================================================
FILE: packages/graphql-playground-electron/src/main/types.ts
================================================
import { BrowserWindow } from 'electron'
export interface WindowContext {
readyWindowsPromises: { [windowId: number]: Promise }
windows: Set
windowById: Map
windowByPath: Map
}
================================================
FILE: packages/graphql-playground-electron/src/main/updates.ts
================================================
import { app, autoUpdater, ipcMain, BrowserWindow, dialog } from 'electron'
import ms = require('ms')
import dev = require('electron-is-dev')
import * as log from 'electron-log'
import { notify } from './notify'
export const startUpdates = () => {
if (!dev) {
startAppUpdates()
}
}
const setUpdateURL = async () => {
let betaUpdates = false
await new Promise(resolve => {
ipcMain.once('SettingsResponse', (event, settingsString) => {
log.info('settings', settingsString)
betaUpdates = getBetaUpdates(settingsString)
resolve()
})
send('SettingsRequest', '')
})
const channel = betaUpdates ? 'kygnjrcroc' : 'ppbimurjwk'
const server = `https://hazel-server-${channel}.now.sh/update`
autoUpdater.setFeedURL({url: `${server}/${process.platform}/${app.getVersion()}`})
}
const checkForUpdates = async () => {
if (process.env.CONNECTION === 'offline') {
// Try again after half an hour
setTimeout(checkForUpdates, ms('30m'))
return
}
// Ensure we're pulling from the correct channel
try {
await setUpdateURL()
} catch (err) {
log.error(err)
// Retry later if setting the update URL failed
return
}
// Then ask the server for updates
autoUpdater.checkForUpdates()
}
const startAppUpdates = () => {
autoUpdater.on('error', error => {
log.error(error)
setTimeout(checkForUpdates, ms('15m'))
})
autoUpdater.on('checking-for-update', () => {
log.info('Checking for app updates...')
})
autoUpdater.on('update-downloaded', () => {
log.info('Update downloaded')
const buttonIndex = dialog.showMessageBox({
message: `Update downloaded. Install now?`,
buttons: ['Install Update & Restart', 'Later'],
})
if (buttonIndex === 0) {
autoUpdater.quitAndInstall()
app.quit()
}
})
autoUpdater.on('update-available', () => {
log.info('Found update for the app! Downloading...')
notify({
title: 'GraphQL Playground Updates',
body: 'Update available. Downloading...',
})
})
autoUpdater.on('update-not-available', () => {
log.info('No updates found. Checking again in 5 minutes...')
setTimeout(checkForUpdates, ms('5m'))
})
setTimeout(checkForUpdates, ms('10s'))
}
function send(channel: string, arg: string) {
const window = BrowserWindow.getAllWindows()[0]
if (window) {
log.info('sending to open window', channel, arg)
window.webContents.send(channel, arg)
} else {
log.info('no opened window')
}
}
function getBetaUpdates(settingsString: string | undefined): boolean {
try {
const settings = JSON.parse(settingsString)
return !!settings['general.betaUpdates']
} catch (e) {
//
}
return false
}
================================================
FILE: packages/graphql-playground-electron/src/renderer/components/App.tsx
================================================
import * as React from 'react'
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import { remote, ipcRenderer, webFrame } from 'electron'
import * as cx from 'classnames'
import { Playground as IPlayground } from 'graphql-playground-react/lib/components/Playground'
import { merge, set } from 'immutable'
import Playground, {
openSettingsTab,
selectNextTab,
selectPrevTab,
closeSelectedTab,
refetchSchema,
newSession,
store,
getSessionsState,
saveFile,
newFileTab,
getEndpoint,
selectAppHistoryItem,
AppHistoryItem,
} from 'graphql-playground-react'
import {
getGraphQLConfig,
findGraphQLConfigFile,
GraphQLConfigData,
resolveEnvsInValues,
} from 'graphql-config'
import { createRemoteWindow } from '../../shared/utils'
import InitialView from './InitialView/InitialView'
import * as minimist from 'minimist'
import * as fs from 'fs'
import * as path from 'path'
import * as os from 'os'
import * as yaml from 'js-yaml'
import * as findUp from 'find-up'
import {
patchEndpointsToConfigData as patchPrismaEndpointsToConfigData,
makeConfigFromPath,
} from 'graphql-config-extension-prisma'
import { patchEndpointsToConfigData } from 'graphql-config-extension-graphcool'
import { connect } from 'react-redux'
import { errify } from '../utils/errify'
import { createStructuredSelector } from 'reselect'
import * as dotenv from 'dotenv'
// import { PermissionSession } from 'graphql-playground/lib/types'
const { dialog } = remote
// declare var p: IPlayground
interface State {
endpoint?: string
openTooltipTheme: boolean
theme: string
shareUrl?: string
loading: boolean
session?: any
platformToken?: string
configString?: string
configPath?: string
folderName?: string
env?: any
config?: GraphQLConfigData
}
ipcRenderer.on('SettingsRequest', () => {
ipcRenderer.send('SettingsResponse', localStorage.getItem('settings'))
})
const events: any[] = []
ipcRenderer.on('OpenSelectedFile', pushSelectedFile)
ipcRenderer.on('OpenUrl', pushOpenUrl)
function pushSelectedFile() {
events.push({
type: 'OpenSelectedFile',
args: arguments,
})
}
function pushOpenUrl() {
events.push({
type: 'OpenUrl',
args: arguments,
})
}
interface ReduxProps {
openSettingsTab: () => void
selectNextTab: () => void
selectPrevTab: () => void
closeSelectedTab: () => void
refetchSchema: () => void
newSession: (endpoint: string) => void
saveFile: () => void
newFileTab: (fileName: string, filePath: string, file: string) => void
selectAppHistoryItem: (item: AppHistoryItem) => void
endpoint: string
}
class App extends React.Component {
private playground: IPlayground
constructor(props) {
super(props)
const { endpoint, platformToken } = this.getArgs()
this.state = {
openTooltipTheme: false,
theme: 'dark',
endpoint,
platformToken,
loading: false,
}
; (global as any).a = this
; (global as any).r = remote
}
fileAdded = event => {
// console.log(event)
}
getArgs(): any {
const argv = remote.process.argv
const args = minimist(argv.slice(1))
return {
endpoint: args.endpoint,
subscriptionsEndpoint: args['subscriptions-endpoint'],
platformToken:
args['platform-token'] || localStorage.getItem('platformToken'),
env: args.env,
}
}
handleSelectEndpoint = (endpoint: string) => {
this.setState({ endpoint } as State)
}
handleSelectFolder = async (folderPath: string) => {
try {
// Get config from folderPath
const envPath = path.join(folderPath, '.env')
let env = process.env
if (fs.existsSync(envPath)) {
const envString = fs.readFileSync(envPath)
const localEnv = dotenv.parse(envString)
if (localEnv) {
env = merge(env, localEnv)
}
}
const configPath = findGraphQLConfigFile(folderPath)
const configString = fs.readFileSync(configPath, 'utf-8')
/* tslint:disable-next-line */
// if (configString.includes('${env:')) {
// errify(`You opened a .graphqlconfig file that includes environment variables.
// In order to use environment variables in the Playground, please start it from the graphql cli. Install with
// npm install -g graphql-cli
// Then open the graphql config with:
// cd ${folderPath}; graphql playground`)
// }
const configDir = path.dirname(configPath)
let config
try {
config = await patchEndpointsToConfigData(
resolveEnvsInValues(getGraphQLConfig(configDir).config, env),
configDir,
env,
)
config = await patchPrismaEndpointsToConfigData(
resolveEnvsInValues(getGraphQLConfig(configDir).config, env),
configDir,
env,
)
} catch (e) {
const ymlPath = path.join(configDir, 'prisma.yml')
if (!fs.existsSync(ymlPath)) {
throw e
}
config = await makeConfigFromPath(configDir)
}
ipcRenderer.send(
'cwd',
JSON.stringify({ cwd: configDir, id: remote.getCurrentWindow().id }),
)
const state = {
configString,
configPath,
config,
folderName: path.basename(folderPath),
env,
}
this.setState(state as State)
this.props.selectAppHistoryItem(merge(state, {
type: 'local',
path: configPath,
}) as any)
} catch (error) {
errify(error)
}
}
handleOpenNewWindow = () => {
createRemoteWindow()
}
openSettingsTab = () => {
this.props.openSettingsTab()
}
nextTab = () => {
this.props.selectNextTab()
}
prevTab = () => {
this.props.selectPrevTab()
}
newTab = () => {
this.props.newSession(this.props.endpoint)
}
closeTab = () => {
this.props.closeSelectedTab()
}
reloadSchema = () => {
this.props.refetchSchema()
}
componentDidMount() {
ipcRenderer.removeListener('OpenUrl', pushOpenUrl)
ipcRenderer.removeListener('OpenSelectedFile', pushSelectedFile)
ipcRenderer.on('Tab', this.readTabMessage)
ipcRenderer.on('File', this.readFileMessage)
ipcRenderer.on('OpenSelectedFile', this.readOpenSelectedFileMessage)
ipcRenderer.on('OpenUrl', this.handleUrl)
window.addEventListener('keydown', this.handleKeyDown, true)
this.consumeEvents()
ipcRenderer.send('ready', '')
// if (
// !this.state.endpoint &&
// !this.state.config &&
// !this.state.configPath &&
// !this.state.configString
// ) {
// const workspace = this.deserializeWorkspace()
// if (workspace) {
// this.setState(workspace)
// }
// }
}
consumeEvents() {
while (events.length > 0) {
const event = events.shift()
switch (event.type) {
case 'OpenSelectedFile':
return this.readOpenSelectedFileMessage.call(this, ...event.args)
case 'OpenUrl':
return this.handleUrl.call(this, ...event.args)
}
}
}
componentWillUnmount() {
ipcRenderer.removeListener('Tab', this.readTabMessage)
ipcRenderer.removeListener('File', this.readFileMessage)
ipcRenderer.removeListener(
'OpenSelectedFile',
this.readOpenSelectedFileMessage,
)
ipcRenderer.removeListener('OpenUrl', this.handleUrl)
window.removeEventListener('keydown', this.handleKeyDown, true)
}
handleKeyDown = e => {
if (e.key === '{' && e.metaKey) {
this.prevTab()
} else if (e.key === '}' && e.metaKey) {
this.nextTab()
} else if (e.key >= 1 && e.key <= 9 && e.metaKey) {
this.playground.switchTab(e.key)
} else if (e.key === '=' && e.metaKey) {
const zoom = webFrame.getZoomFactor()
webFrame.setZoomFactor(zoom + 0.1)
} else if (e.key === '-' && e.metaKey) {
const zoom = webFrame.getZoomFactor()
webFrame.setZoomFactor(zoom - 0.1)
}
}
handleUrl = async (event, msg) => {
const input = JSON.parse(msg)
const endpoint = input.endpoint
let configString
let folderName
let configPath
const platformToken = input.platformToken
let config
if (input.cwd) {
// use the endpoint as an alternative, only log the error
try {
configPath = findUp.sync(['.graphqlconfig', '.graphqlconfig.yml'], {
cwd: input.cwd,
})
configString = configPath
? fs.readFileSync(configPath, 'utf-8')
: undefined
folderName = configPath
? path.basename(path.dirname(configPath))
: undefined
try {
const rawConfig = getGraphQLConfig(input.cwd).config
const resolvedConfig = resolveEnvsInValues(rawConfig, input.env)
config = await patchEndpointsToConfigData(
resolvedConfig,
input.cwd,
input.env,
)
config = await patchPrismaEndpointsToConfigData(
resolvedConfig,
input.cwd,
input.env,
)
} catch (e) {
const ymlPath = path.join(input.cwd, 'prisma.yml')
if (!fs.existsSync(ymlPath)) {
throw e
}
config = (await makeConfigFromPath(input.cwd, input.env)).config
configPath = ymlPath
folderName = path.basename(input.cwd)
configString = JSON.stringify(config)
}
if (!this.configContainsEndpoints(config)) {
const graphcoolNote = configString.includes('graphcool')
? 'Please make sure to add stages to your graphcool.yml'
: ''
errify(
`${configPath} does not include any endpoints. ${graphcoolNote}`,
)
return
}
} catch (e) {
console.error(e)
errify(e)
}
}
ipcRenderer.send(
'cwd',
JSON.stringify({ cwd: input.cwd, id: remote.getCurrentWindow().id }),
)
const state = {
configString,
folderName,
configPath,
env: input.env,
endpoint,
config,
platformToken,
}
this.props.selectAppHistoryItem(merge(state, {
type: 'endpoint',
path: configPath,
}) as any)
this.setState(state)
}
configContainsEndpoints(config: GraphQLConfigData): boolean {
if (
Object.keys((config.extensions && config.extensions.endpoints) || {})
.length > 0
) {
return true
}
return Object.keys(config.projects).reduce((acc, curr) => {
const project = config.projects[curr]
if (
project.extensions &&
project.extensions.endpoints &&
Object.keys(project.extensions.endpoints).length > 0
) {
return true
}
return acc
}, false)
}
readFileMessage = (event, message) => {
switch (message) {
case 'Open':
this.showOpenDialog()
break
case 'Save':
this.getSaveFileName()
break
}
}
readOpenSelectedFileMessage = (event, selectedFile) => {
if (selectedFile) {
this.openFile(selectedFile)
}
}
async openFile(fileName: string) {
const file = fs.readFileSync(fileName, 'utf-8')
if (!this.playground) {
this.handleSelectFolder(path.dirname(fileName))
}
while (!this.playground) {
await new Promise(r => setTimeout(r, 200))
}
await new Promise(r => setTimeout(r, 200))
this.props.newFileTab(path.basename(fileName), fileName, file)
}
showOpenDialog() {
dialog.showOpenDialog(
{
title: 'Choose a .graphql file to edit',
properties: ['openFile'],
// filters: [{
// name: '*',
// extensions: ['graphql']
// }]
},
fileNames => {
if (fileNames && fileNames.length > 0) {
const file = fileNames[0]
this.openFile(file)
}
},
)
}
getSaveFileName(): Promise {
return new Promise((resolve, reject) => {
// save current tab
if (this.playground) {
const session = getSessionsState(store.getState())
if (session.isConfigTab) {
this.playground.handleSaveConfig()
}
if (session.isSettingsTab) {
this.playground.handleSaveSettings()
}
if (session.isFile && session.filePath) {
// TODO
// dialog.showSaveDialog(
// {
// title: 'Save Permission Query',
// filters: [
// {
// name: 'Permission File',
// extensions: ['graphql'],
// },
// ],
// },
// (fileName: any) => {
// resolve(fileName)
// },
// )
this.props.saveFile()
fs.writeFileSync(session.filePath, session.file)
}
this.playground.handleSaveConfig()
}
})
}
saveConfig = (configString: string) => {
fs.writeFileSync(this.state.configPath, configString)
this.setState({ configString })
}
// async saveFile() {
// const session = this.playground.state.sessions[
// this.playground.state.selectedSessionIndex
// ]
// ;(this.playground as any).setValueInSession(session.id, 'hasChanged', false)
// const fileName =
// (session as any).absolutePath || (await this.getSaveFileName())
// // if (!(session as any).absolutePath) {
// ;(this.playground as any).setValueInSession(
// session.id,
// 'name',
// path.basename(fileName),
// )
// ;(this.playground as any).setValueInSession(
// session.id,
// 'absolutePath',
// fileName,
// )
// // }
// const query = session.query
// fs.writeFileSync(fileName, query)
// }
getGraphcoolYml(from: string): { ymlPath: string; yml: any } | null {
const ymlPath = findUp.sync('graphcool.yml', { cwd: from })
if (ymlPath) {
const file = fs.readFileSync(ymlPath)
try {
return {
yml: yaml.safeLoad(file),
ymlPath,
}
} catch (e) {
// console.error(e)
}
}
return null
}
getGraphcoolRc(): any | null {
const graphcoolRc = path.join(os.homedir(), '.graphcoolrc')
if (fs.existsSync(graphcoolRc)) {
const file = fs.readFileSync(graphcoolRc)
try {
return yaml.safeLoad(file)
} catch (e) {
// console.error(e)
}
}
return null
}
readTabMessage = (error, message) => {
switch (message) {
case 'Next':
this.nextTab()
break
case 'Prev':
this.prevTab()
break
case 'New':
this.newTab()
break
case 'Close':
if (!this.state.endpoint && !this.state.config) {
ipcRenderer.send(
'CloseWindow',
JSON.stringify({ id: remote.getCurrentWindow().id }),
)
} else {
this.closeTab()
}
break
case 'Settings':
this.openSettingsTab()
break
case 'ReloadSchema':
this.reloadSchema()
break
}
}
render() {
const { theme, endpoint, platformToken, configString, config } = this.state
return (
{(endpoint || configString) && (
)}
)
}
private handleSelectItem = ({ type, ...item }) => {
this.setState(item as any)
this.props.selectAppHistoryItem(set(item, 'lastOpened', new Date()) as any)
}
private setRef = ref => {
this.playground = ref
}
}
const mapStateToProps = createStructuredSelector({
endpoint: getEndpoint,
})
export default connect(
mapStateToProps,
{
openSettingsTab,
selectNextTab,
selectPrevTab,
closeSelectedTab,
refetchSchema,
newSession,
saveFile,
newFileTab,
selectAppHistoryItem,
},
)(App)
================================================
FILE: packages/graphql-playground-electron/src/renderer/components/InitialView/InitialView.tsx
================================================
import * as React from 'react'
import * as isURL from 'validator/lib/isURL'
import { remote } from 'electron'
import { existsSync } from 'fs'
import { resolve } from 'path'
import { Icon, $v } from 'graphcool-styles'
import Modal from 'graphcool-ui/lib/Modal'
import { connect } from 'react-redux'
import * as format from 'date-fns/format'
import Toggle from './Toggle'
import { examples } from './data'
import { getAppHistory, AppHistoryItem } from 'graphql-playground-react'
import { createStructuredSelector, createSelector } from 'reselect'
import { OrderedMap } from 'immutable'
interface StateFromProps {
history: OrderedMap
}
interface State {
endpoint: string
selectedMode: string
}
export interface Props {
isOpen: boolean
onSelectEndpoint: (endpoint: string) => void
onSelectFolder: (config: string) => void
selectHistory: (history: AppHistoryItem) => any
}
const modalStyle = {
overlay: {
zIndex: 20,
backgroundColor: 'rgba(15,32,46,.9)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
'-webkit-app-region': 'drag',
},
content: {
position: 'relative',
width: 976,
height: 'auto',
top: 'initial',
left: 'initial',
right: 'initial',
bottom: 'initial',
borderRadius: 2,
padding: 0,
border: 'none',
background: 'none',
boxShadow: '0 1px 7px rgba(0,0,0,.2)',
},
}
class InitialView extends React.Component {
state = {
endpoint: '',
selectedMode: 'local',
}
handleRequestClose = () => null
handleSubmit = e => {
e.preventDefault()
if (isURL(this.state.endpoint, { require_tld: false })) {
this.props.selectHistory(
new AppHistoryItem({
type: 'endpoint',
path: this.state.endpoint,
}),
)
this.props.onSelectEndpoint(this.state.endpoint)
} else {
alert('Endpoint is not a valid url')
}
}
handleClickLocal = () => {
const paths = remote.dialog.showOpenDialog({
properties: ['openDirectory'],
})
if (paths && paths[0]) {
const path = paths[0]
// Check if there is a .graphqlconfig file in the folder
if (
!existsSync(resolve(path, '.graphqlconfig')) &&
!existsSync(resolve(path, '.graphqlconfig.yml')) &&
!existsSync(resolve(path, '.graphqlconfig.yaml'))
) {
alert('No .graphqlconfig found in this folder')
return
}
this.setState({ endpoint: path } as State)
this.props.onSelectFolder(path)
}
}
handleChangeEndpoint = e => {
this.setState({ endpoint: e.target.value } as State)
}
handleChangeMode = selectedMode => {
this.setState({ selectedMode } as State)
}
handleClickHistory = (history: AppHistoryItem) => {
this.props.selectHistory(history)
}
render() {
const { isOpen, history } = this.props
const { endpoint, selectedMode } = this.state
const choicesMode = ['local', 'url endpoint']
const items = history.toJS()
return (
{history.size > 0 ? (
RECENT
{Object.keys(items)
.reverse()
.map((key) => {
const data = items[key]
const name = data.folderName || data.endpoint || data.path
return (
this.handleClickHistory(data)}
>
{name}
Last opened{' '}
{format(data.lastOpened, 'DD.MM.YYYY')}
)
})}
) : (
EXAMPLES
{examples.map((example) => (
this.props.onSelectEndpoint(example.endpoint)
}
>
{example.name}
{example.endpoint}
))}
)}
New Workspace
Either load a local repository with a .graphqlconfig file, or
just open a HTTP endpoint