├── .circleci └── config.yml ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── LICENSE.md ├── README.md ├── docs ├── examples │ ├── anonymous.js │ ├── remixProject.js │ ├── search.js │ ├── starter.js │ └── websockets.js └── theme │ └── assets │ └── css │ └── constants.sass ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── index.ts ├── interfaces │ ├── MemberOptions.ts │ └── Requests.ts ├── models │ ├── API.ts │ ├── Editor.ts │ ├── Glitch.ts │ ├── Logs.ts │ ├── Projects.ts │ ├── Request.ts │ ├── Teams.ts │ ├── Terminal.ts │ └── Users.ts ├── structures │ ├── Context.ts │ ├── Feature.ts │ ├── Member.ts │ ├── Project.ts │ ├── SearchCreds.ts │ ├── Team.ts │ └── User.ts └── utils │ └── constants.ts ├── tests ├── api.test.ts ├── glitch.test.ts ├── projects.test.ts ├── teams.test.ts └── users.test.ts ├── tsconfig.json ├── watch.json └── webpack.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/node:latest 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/mongo:3.4.4 16 | 17 | working_directory: ~/repo 18 | 19 | steps: 20 | - checkout 21 | 22 | # Download and cache dependencies 23 | - restore_cache: 24 | keys: 25 | - v1-dependencies-{{ checksum "package.json" }} 26 | # fallback to using the latest cache if no exact match is found 27 | - v1-dependencies- 28 | 29 | - run: npm install 30 | 31 | - save_cache: 32 | paths: 33 | - node_modules 34 | key: v1-dependencies-{{ checksum "package.json" }} 35 | 36 | # run tests! 37 | - run: npm test -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | browser: false 3 | commonjs: true 4 | es6: true 5 | node: true 6 | extends: 'eslint:recommended' 7 | globals: 8 | Atomics: 'readonly' 9 | SharedArrayBuffer: 'readonly' 10 | parserOptions: 11 | sourceType: module 12 | ecmaVersion: 2018 13 | rules: 14 | no-console: 'off' 15 | no-inner-declarations: 'off' 16 | no-mixed-spaces-and-tabs: ['error', 'smart-tabs'] 17 | indent: ['error', 2] 18 | linebreak-style: ['error', 'windows'] 19 | quotes: ['warn', 'single'] 20 | semi: ['error', 'never'] 21 | no-unused-vars: 'off' 22 | ignorePatterns: ['node_modules/', 'lib/', 'docs/', '*.test*'] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment globals file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | shrinkwrap.yaml 64 | temp/ 65 | .vscode/ 66 | /data/ 67 | *.sqlite 68 | 69 | # Exclude dist/ folder 70 | dist/ 71 | 72 | lib/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | .env 7 | 8 | shrinkwrap.yaml 9 | temp/ 10 | .vscode/ 11 | /data/ 12 | 13 | # Any .dot files should be ignored 14 | .* -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | trailingComma: "es5" 2 | tabWidth: 2 3 | semi: false 4 | singleQuote: true -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Citizen Code of Conduct 2 | 3 | ## 1. Purpose 4 | 5 | A primary goal of gl-api is to be inclusive to the largest number of contributors, with the most varied and diverse backgrounds possible. As such, we are committed to providing a friendly, safe and welcoming environment for all, regardless of gender, sexual orientation, ability, ethnicity, socioeconomic status, and religion (or lack thereof). 6 | 7 | This code of conduct outlines our expectations for all those who participate in our community, as well as the consequences for unacceptable behavior. 8 | 9 | We invite all those who participate in gl-api to help us create safe and positive experiences for everyone. 10 | 11 | ## 2. Open [Source/Culture/Tech] Citizenship 12 | 13 | A supplemental goal of this Code of Conduct is to increase open [source/culture/tech] citizenship by encouraging participants to recognize and strengthen the relationships between our actions and their effects on our community. 14 | 15 | Communities mirror the societies in which they exist and positive action is essential to counteract the many forms of inequality and abuses of power that exist in society. 16 | 17 | If you see someone who is making an extra effort to ensure our community is welcoming, friendly, and encourages all participants to contribute to the fullest extent, we want to know. 18 | 19 | ## 3. Expected Behavior 20 | 21 | The following behaviors are expected and requested of all community members: 22 | 23 | * Participate in an authentic and active way. In doing so, you contribute to the health and longevity of this community. 24 | * Exercise consideration and respect in your speech and actions. 25 | * Attempt collaboration before conflict. 26 | * Refrain from demeaning, discriminatory, or harassing behavior and speech. 27 | * Be mindful of your surroundings and of your fellow participants. Alert community leaders if you notice a dangerous situation, someone in distress, or violations of this Code of Conduct, even if they seem inconsequential. 28 | * Remember that community event venues may be shared with members of the public; please be respectful to all patrons of these locations. 29 | 30 | ## 4. Unacceptable Behavior 31 | 32 | The following behaviors are considered harassment and are unacceptable within our community: 33 | 34 | * Violence, threats of violence or violent language directed against another person. 35 | * Sexist, racist, homophobic, transphobic, ableist or otherwise discriminatory jokes and language. 36 | * Posting or displaying sexually explicit or violent material. 37 | * Posting or threatening to post other people's personally identifying information ("doxing"). 38 | * Personal insults, particularly those related to gender, sexual orientation, race, religion, or disability. 39 | * Inappropriate photography or recording. 40 | * Inappropriate physical contact. You should have someone's consent before touching them. 41 | * Unwelcome sexual attention. This includes, sexualized comments or jokes; inappropriate touching, groping, and unwelcomed sexual advances. 42 | * Deliberate intimidation, stalking or following (online or in person). 43 | * Advocating for, or encouraging, any of the above behavior. 44 | * Sustained disruption of community events, including talks and presentations. 45 | 46 | ## 5. Weapons Policy 47 | 48 | No weapons will be allowed at gl-api events, community spaces, or in other spaces covered by the scope of this Code of Conduct. Weapons include but are not limited to guns, explosives (including fireworks), and large knives such as those used for hunting or display, as well as any other item used for the purpose of causing injury or harm to others. Anyone seen in possession of one of these items will be asked to leave immediately, and will only be allowed to return without the weapon. Community members are further expected to comply with all state and local laws on this matter. 49 | 50 | ## 6. Consequences of Unacceptable Behavior 51 | 52 | Unacceptable behavior from any community member, including sponsors and those with decision-making authority, will not be tolerated. 53 | 54 | Anyone asked to stop unacceptable behavior is expected to comply immediately. 55 | 56 | If a community member engages in unacceptable behavior, the community organizers may take any action they deem appropriate, up to and including a temporary ban or permanent expulsion from the community without warning (and without refund in the case of a paid event). 57 | 58 | ## 7. Reporting Guidelines 59 | 60 | If you are subject to or witness unacceptable behavior, or have any other concerns, please notify a community organizer as soon as possible. [CONTACT_INFO_HERE]. 61 | 62 | [LINK_TO_REPORTING_GUIDELINES] 63 | 64 | Additionally, community organizers are available to help community members engage with local law enforcement or to otherwise help those experiencing unacceptable behavior feel safe. In the context of in-person events, organizers will also provide escorts as desired by the person experiencing distress. 65 | 66 | ## 8. Addressing Grievances 67 | 68 | If you feel you have been falsely or unfairly accused of violating this Code of Conduct, you should notify vlad.ekushev@mail.ru with a concise description of your grievance. Your grievance will be handled in accordance with our existing governing policies. [LINK_TO_POLICY] 69 | 70 | [NOTE: Every organization's governing policies should dictate how you handle warnings and expulsions of community members. It is strongly recommended that you mention those policies here or in Section 7 and that you include a mechanism for addressing grievances.] 71 | 72 | ## 9. Scope 73 | 74 | We expect all community participants (contributors, paid or otherwise; sponsors; and other guests) to abide by this Code of Conduct in all community venues--online and in-person--as well as in all one-on-one communications pertaining to community business. 75 | 76 | This code of conduct and its related procedures also applies to unacceptable behavior occurring outside the scope of community activities when such behavior has the potential to adversely affect the safety and well-being of community members. 77 | 78 | ## 10. Contact info 79 | 80 | [YOUR_CONTACT_INFO_HERE -- this should be a single person or a small team who can respond to issues directly] 81 | 82 | ## 11. License and attribution 83 | 84 | The Citizen Code of Conduct is distributed by [Stumptown Syndicate](http://stumptownsyndicate.org) under a [Creative Commons Attribution-ShareAlike license](http://creativecommons.org/licenses/by-sa/3.0/). 85 | 86 | Portions of text derived from the [Django Code of Conduct](https://www.djangoproject.com/conduct/) and the [Geek Feminism Anti-Harassment Policy](http://geekfeminism.wikia.com/wiki/Conference_anti-harassment/Policy). 87 | 88 | _Revision 2.3. Posted 6 March 2017._ 89 | 90 | _Revision 2.2. Posted 4 February 2016._ 91 | 92 | _Revision 2.1. Posted 23 June 2014._ 93 | 94 | _Revision 2.0, adopted by the [Stumptown Syndicate](http://stumptownsyndicate.org) board on 10 January 2013. Posted 17 March 2013._ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 jarvis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## glitch-api 2 | 3 | [![GitHub package.json dynamic](https://img.shields.io/github/package-json/version/jarvis394/glitch-api)](https://www.npmjs.com/package/glitch-api) 4 | [![CircleCI](https://circleci.com/gh/jarvis394/glitch-api/tree/master.svg?style=shield)](https://circleci.com/gh/jarvis394/glitch-api/tree/master) 5 | [![npm bundle size](https://img.shields.io/bundlephobia/min/glitch-api?label=size)](https://www.npmjs.com/package/glitch-api) 6 | 7 | A Node.js module that allows you to easily interact with the Glitch API 8 | 9 | | 📖 [Documentation](https://glapi.ml/globals) | ✨ [Examples](https://github.com/jarvis394/glitch-api/tree/master/docs/examples/) | 10 | | ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | 11 | 12 | ## Features 13 | 14 | - 99% coverage of the **known** Glitch API 15 | - Uses **TypeScript** that provides hints in editor, type checking, etc. 16 | - **Supports WebSocket connection to the Glitch editor** 17 | - Support for authorization 18 | - Only two dependencies: `node-fetch` and `algoliasearch` 19 | - Class abstraction 20 | - Works with both API versions 21 | 22 | ###### _Warning: this module uses unstable API that hasn't been officially released yet. Described only world-open methods from [this unofficial site](https://glitchapi.glitch.me) and my researches_ 23 | 24 | ## Installation 25 | 26 | > **[Node.js](https://nodejs.org/) 8.0.0 or newer is required** 27 | 28 | ##### NPM 29 | 30 | ``` 31 | npm i glitch-api -s 32 | ``` 33 | 34 | ## Example usage 35 | 36 | ```javascript 37 | // Require using ES6 syntax 38 | import { Glitch } from 'glitch-api' 39 | 40 | // Or using old fancy style 41 | const { Glitch } = require('glitch-api') 42 | 43 | // Init main class 44 | const glitch = new Glitch({ token: 'xxx' }) // Put here your Glitch token or use glitch.setAnonToken() 45 | const { api } = glitch 46 | 47 | // Get a user profile 48 | api.users.get({ id: 1 }).then(user => console.log) // → User 49 | ``` 50 | 51 | ## ❗ Migration from 2.x to 3.x 52 | 53 | - Any search method now follows the same syntax: 54 | ``` 55 | .search(s: string) → Algolia result 56 | ``` 57 | - Remixing a project doesn't return `joinLink`. Now it is a `Project` instance. Consider getting project data with token via `api.projects.get()` 58 | - Token is absolutely required. If you don't have a token, consider using `Glitch.setAnonToken()`. For more info, see [examples/anonymous.js](https://github.com/jarvis394/glitch-api/blob/master/docs/examples/anonymous.js) 59 | 60 | ## Q&A 61 | 62 | ### How do I get Glitch token? 63 | 64 | Paste the following code to the browser's console on the Glitch editor page: 65 | 66 | ```javascript 67 | (JSON.parse(localStorage.getItem('cachedUser'))).persistentToken 68 | ``` 69 | 70 | Also you can use `Glitch.setAnonToken() → Promise` method to login as an anonymous user and use its token. 71 | 72 | ___ 73 | ### Why *\*api-method\** is not implemented? 74 | 75 | Because I'm also human and I might not have seen the recent changes in Glitch's API. 76 | Anyway, PRs are open for anyone :) 77 | 78 | ___ 79 | ### The *\*api-method\** is not working. 80 | 81 | It could be that Glitch devs removed the support for that method. Or it's just a my fault. 82 | Open a new issue and describe what's happend. 83 | 84 | ## Contribution 85 | 86 | Feel free to open new Pull request or an issue! 87 | 88 | ## Credits 89 | 90 | Made by jarvis394 with ♥️ 91 | 92 | - VK: **[@tarnatovski](https://vk.com/tarnatovski)** 93 | - git: **[@jarvis394](https://github.com/jarvis394)** 94 | -------------------------------------------------------------------------------- /docs/examples/anonymous.js: -------------------------------------------------------------------------------- 1 | const { Glitch } = require('../../src') 2 | const glitch = new Glitch() // Let's not put a token here 3 | const { api } = glitch 4 | 5 | // Magically get a token as an anonymous user 6 | glitch.setAnonToken().then(() => { 7 | // Searching now works! 8 | api.users.search('jarvis394').then(users => console.log(users.hits[0])) 9 | }) 10 | -------------------------------------------------------------------------------- /docs/examples/remixProject.js: -------------------------------------------------------------------------------- 1 | const { Glitch } = require('../../src') 2 | const glitch = new Glitch({ token: process.env.TOKEN }) 3 | const { api } = glitch 4 | 5 | // hello-express 6 | const ID = 'a0fcd798-9ddf-42e5-8205-17158d4bf5bb' 7 | 8 | // Remix project 9 | api.projects.remix(ID).then(remix => { 10 | console.log(remix) 11 | }) 12 | -------------------------------------------------------------------------------- /docs/examples/search.js: -------------------------------------------------------------------------------- 1 | const { Glitch } = require('../../src') 2 | const util = require('util') 3 | const glitch = new Glitch({ token: '3cb6d1bf-7802-4d5f-be3f-302f30d8de72' }) 4 | const { api } = glitch 5 | 6 | // Pretty logging to not to blow up your terminal with data ;) 7 | const log = res => 8 | console.log(util.inspect(res, { showHidden: false, depth: 1 })) 9 | 10 | // Search users (jarvis394 is a main developer of this lib - say hello!) 11 | api.users.search('jarvis394').then(result => log(result)) 12 | 13 | // Search projects 14 | api.projects.search('glapi').then(result => log(result)) 15 | 16 | // Search teams 17 | api.teams.search('hello-express').then(result => log(result)) 18 | -------------------------------------------------------------------------------- /docs/examples/starter.js: -------------------------------------------------------------------------------- 1 | const { Glitch } = require('../../src') 2 | const glitch = new Glitch({ token: process.env.TOKEN }) 3 | const { api } = glitch 4 | 5 | // 6 | // If you don't have a token, you can use glitch.setAnonToken() method 7 | // You won't able to do any operations without a token 8 | // For more information, check anonymous.js in docs/ folder: 9 | // 10 | 11 | // Get user profile 12 | api.users.get({ id: 1 }).then(user => console.log(user)) // → User 13 | 14 | // Get project data 15 | api.projects.get({ domain: 'glapi' }).then(project => console.log(project)) // → Project 16 | -------------------------------------------------------------------------------- /docs/examples/websockets.js: -------------------------------------------------------------------------------- 1 | const { Glitch } = require('../../src') 2 | const glitch = new Glitch({ token: process.env.TOKEN }) 3 | const { api } = glitch 4 | const DOMAIN = 'hello-express' 5 | 6 | ;(async () => { 7 | const editor = await api.projects.edit({ domain: DOMAIN }) 8 | 9 | editor.setMessageHandler((message) => console.log(JSON.parse(message.data))) 10 | editor.connect() 11 | })() 12 | -------------------------------------------------------------------------------- /docs/theme/assets/css/constants.sass: -------------------------------------------------------------------------------- 1 | // Fonts 2 | // 3 | $FONT_FAMILY: 'Segoe UI', sans-serif 4 | $FONT_FAMILY_MONO: Menlo, Monaco, Consolas, 'Courier New', monospace 5 | 6 | $FONT_SIZE: 16px 7 | $FONT_SIZE_MONO: 14px 8 | 9 | $LINE_HEIGHT: 1.333em 10 | 11 | 12 | // Colors 13 | // 14 | $COLOR_BACKGROUND: #222222 15 | $COLOR_TEXT: #fafafa 16 | $COLOR_TEXT_ASIDE: #aaaaaa 17 | $COLOR_LINK: #4da6ff 18 | 19 | $COLOR_MENU_DIVIDER: #eee 20 | $COLOR_MENU_DIVIDER_FOCUS: #000 21 | $COLOR_MENU_LABEL: #808080 22 | 23 | $COLOR_PANEL: #222 24 | $COLOR_PANEL_DIVIDER: #eee 25 | 26 | $COLOR_COMMENT_TAG: #808080 27 | $COLOR_COMMENT_TAG_TEXT: #fff 28 | 29 | $COLOR_CODE_BACKGROUND: rgba(#000, 0.04) 30 | 31 | $COLOR_TS: #9600ff 32 | $COLOR_TS_INTERFACE: #7da01f 33 | $COLOR_TS_ENUM: #cc9900 34 | $COLOR_TS_CLASS: #4da6ff 35 | $COLOR_TS_PRIVATE: #808080 36 | 37 | $TOOLBAR_COLOR: #fff 38 | $TOOLBAR_TEXT_COLOR: #333 39 | $TOOLBAR_HEIGHT: 40px -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: [''], 3 | transform: { 4 | '^.+\\.tsx?$': 'ts-jest', 5 | }, 6 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "glitch-api", 3 | "version": "3.1.0", 4 | "description": "Library for using Glitch API ⚙️", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "start": "serve lib/docs", 8 | "test": "jest test", 9 | "build": "webpack -c", 10 | "build:docs": "typedoc --out ./lib/docs --mode file --name glitch-api --theme ./docs/theme ./src", 11 | "lint": "npm run lint:eslint && npm run lint:prettier", 12 | "lint:eslint": "eslint . --fix", 13 | "lint:prettier": "prettier src/**/{.[^.],}* --config .prettierrc --write" 14 | }, 15 | "dependencies": { 16 | "algoliasearch": "^4.2.0", 17 | "node-fetch": "^2.6.0" 18 | }, 19 | "engines": { 20 | "node": ">=8.0.0" 21 | }, 22 | "homepage": "https://glapi.ml", 23 | "repository": { 24 | "url": "https://github.com/jarvis394/glitch-api/" 25 | }, 26 | "files": [ 27 | "lib/index.js" 28 | ], 29 | "license": "MIT", 30 | "keywords": [ 31 | "node", 32 | "glitch", 33 | "js", 34 | "api" 35 | ], 36 | "devDependencies": { 37 | "@types/jest": "^24.9.1", 38 | "@types/node": "^12.12.43", 39 | "@types/node-fetch": "^2.5.7", 40 | "@types/socket.io-client": "^1.4.33", 41 | "@types/ws": "^6.0.4", 42 | "awesome-typescript-loader": "^5.2.1", 43 | "bufferutil": "^4.0.1", 44 | "eslint": "^6.8.0", 45 | "express": "^4.17.1", 46 | "jest": "^26.0.1", 47 | "path": "^0.12.7", 48 | "prettier": "^1.19.1", 49 | "sass": "^1.26.7", 50 | "serve": "^11.3.1", 51 | "ts-jest": "^26.1.0", 52 | "typedoc": "^0.15.8", 53 | "typedoc-webpack-plugin": "^1.1.4", 54 | "typescript": "^3.9.3", 55 | "webpack": "^4.43.0", 56 | "webpack-cli": "^3.3.11" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Glitch } from './models/Glitch' 2 | export { default as API } from './models/API' 3 | export { default as Projects } from './models/Projects' 4 | export { default as Teams } from './models/Teams' 5 | export { default as Users } from './models/Users' 6 | export { default as Request } from './models/Request' 7 | export { default as Editor } from './models/Editor' 8 | export { default as Logs } from './models/Logs' 9 | export { default as Terminal } from './models/Terminal' 10 | 11 | export { default as Feature } from './structures/Feature' 12 | export { default as Member } from './structures/Member' 13 | export { default as Project } from './structures/Project' 14 | export { default as Team } from './structures/Team' 15 | export { default as User } from './structures/User' 16 | export { default as SearchCreds } from './structures/SearchCreds' 17 | 18 | export { default as MemberOptions } from './interfaces/MemberOptions' 19 | export * from './interfaces/Requests' 20 | 21 | export * from './utils/constants' 22 | -------------------------------------------------------------------------------- /src/interfaces/MemberOptions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Project member class options 3 | */ 4 | export default interface IMemberOptions { 5 | /** 6 | * Member ID 7 | */ 8 | userId?: number 9 | 10 | /** 11 | * @hidden 12 | */ 13 | id?: number 14 | 15 | /** 16 | * Member access level in the project 17 | */ 18 | accessLevel: number 19 | } 20 | -------------------------------------------------------------------------------- /src/interfaces/Requests.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Request options interface 3 | */ 4 | export interface IRequestOptions { 5 | /** 6 | * Glitch API method 7 | */ 8 | method: string 9 | /** 10 | * State that shows whether the packet should be compressed 11 | */ 12 | compress: boolean 13 | /** 14 | * Timeout for request 15 | */ 16 | timeout: number 17 | /** 18 | * Request body 19 | */ 20 | body: any | null 21 | /** 22 | * Request headers 23 | */ 24 | headers: Record 25 | } 26 | 27 | /** 28 | * Request parameters interface 29 | */ 30 | export interface IRequestParams { 31 | /** 32 | * Method for the request sending 33 | */ 34 | method: string 35 | /** 36 | * Shows whether to use old API URL 37 | */ 38 | oldApi: boolean 39 | /** 40 | * API base 41 | */ 42 | apiBaseURL?: string 43 | } 44 | -------------------------------------------------------------------------------- /src/models/API.ts: -------------------------------------------------------------------------------- 1 | import Request from './Request' 2 | import fetch, { Response } from 'node-fetch' 3 | import { URLSearchParams } from 'url' 4 | import { IRequestOptions, IRequestParams } from '../interfaces/Requests' 5 | 6 | import Users from './Users' 7 | import Projects from './Projects' 8 | import Teams from './Teams' 9 | import Glitch, { IGlitchOptions } from './Glitch' 10 | import Context from '../structures/Context' 11 | import SearchCreds from '../structures/SearchCreds' 12 | 13 | /** 14 | * API class 15 | * @class 16 | */ 17 | export default class API { 18 | /** 19 | * Glitch instance's options 20 | * @hidden 21 | */ 22 | _options: IGlitchOptions 23 | 24 | /** 25 | * Glitch instance 26 | * @hidden 27 | */ 28 | _glitch: Glitch 29 | 30 | /** 31 | * Requests queue 32 | */ 33 | protected queue: Request[] 34 | 35 | /** 36 | * Shows whether the instance is working on a request 37 | */ 38 | protected working: boolean 39 | 40 | /** 41 | * Users API 42 | */ 43 | users: Users 44 | 45 | /** 46 | * Projects API 47 | */ 48 | projects: Projects 49 | 50 | /** 51 | * Teams API 52 | */ 53 | teams: Teams 54 | 55 | /** 56 | * API constructor 57 | * @param glitchOptions - Glitch instance's options 58 | */ 59 | constructor(glitch: Glitch) { 60 | this._options = glitch.options 61 | this._glitch = glitch 62 | this.queue = [] 63 | this.working = false 64 | this.users = new Users(this) 65 | this.projects = new Projects(this) 66 | this.teams = new Teams(this) 67 | } 68 | 69 | /** 70 | * Adds request for queue 71 | * @param request - Request to call 72 | */ 73 | callWithRequest(request: Request): Promise { 74 | this.queue.push(request) 75 | 76 | this.worker() 77 | 78 | return request.promise 79 | } 80 | 81 | /** 82 | * Runs queue 83 | * @private 84 | */ 85 | private worker() { 86 | if (this.working) return 87 | else this.working = true 88 | 89 | const work = () => { 90 | if (this.queue.length === 0) { 91 | this.working = false 92 | 93 | return 94 | } 95 | 96 | this.callMethod(this.queue.shift()) 97 | 98 | setTimeout(work, this._options.apiInterval) 99 | } 100 | 101 | work() 102 | } 103 | 104 | /** 105 | * Adds method to queue 106 | * @param method - Method to execute 107 | * @param params - Parameters for method 108 | * @param requestParams - Parameters for request 109 | * @param requestParams.method - Method for request 110 | * @param requestParams.oldApi - Use old API url state 111 | */ 112 | enqueue( 113 | method: string, 114 | params: Record, 115 | requestParams: Partial 116 | ): Promise> { 117 | const request = new Request(method, params, requestParams) 118 | 119 | return this.callWithRequest(request) 120 | } 121 | 122 | /** 123 | * Calls the API method 124 | * @param request - Request to call 125 | */ 126 | async callMethod(request: Request) { 127 | const { 128 | apiBaseUrlOld, 129 | apiBaseUrl, 130 | compress, 131 | apiTimeout, 132 | apiHeaders, 133 | token, 134 | } = this._options 135 | const { method, params, requestParams } = request 136 | const search = new URLSearchParams(params) 137 | 138 | let url: string = `${ 139 | requestParams.oldApi ? apiBaseUrlOld : apiBaseUrl 140 | }/${method}` 141 | let requestOptions: IRequestOptions = { 142 | method: requestParams.method || 'GET', 143 | compress: compress, 144 | timeout: apiTimeout, 145 | body: null, 146 | headers: { 147 | ...apiHeaders, 148 | connection: 'keep-alive', 149 | }, 150 | } 151 | 152 | if (token) { 153 | search.append('authorization', token) 154 | requestOptions.headers.authorization = token 155 | } 156 | 157 | if (requestParams.method === 'GET') url += '?' + search.toString() 158 | else requestOptions.body = search 159 | 160 | if (this._options.debug) 161 | console.debug('GLAPI: Fetching', method, requestParams) 162 | 163 | try { 164 | const resFetch: Response = await fetch(url, requestOptions) 165 | const resText = await resFetch.text() 166 | const context: Context = new Context( 167 | resFetch, 168 | resText, 169 | requestOptions 170 | ) 171 | 172 | if (context.error) { 173 | throw new Error( 174 | `[${context.error.statusCode}] On trying to execute ${method}: ${context.error.message}` 175 | ) 176 | } 177 | 178 | if (this._options.debug) 179 | console.debug('GLAPI: Fetched', context) 180 | 181 | return request.resolve(context) 182 | } catch (e) { 183 | return request.reject(e) 184 | } 185 | } 186 | 187 | async getSearchCreds(): Promise { 188 | return ( 189 | await this.enqueue( 190 | 'search/creds', 191 | {}, 192 | { 193 | method: 'GET', 194 | } 195 | ) 196 | ).response 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/models/Editor.ts: -------------------------------------------------------------------------------- 1 | import WebSocket from 'ws' 2 | import Project from '../structures/Project' 3 | import { WEBSOCKET_BASE_URL } from '../utils/constants' 4 | 5 | /** 6 | * Glitch application's editor class 7 | * 8 | * Connects to the project via WebSockets and provides full API of that project. 9 | * Firstly, you would need to invoke `Editor.connect()` function 10 | * in order to connect to the scokets. 11 | * 12 | * Then you would able to use any method avaliable on the 13 | * Glitch's editor page in the `application` variable 14 | * @class 15 | */ 16 | export default class Editor { 17 | /** 18 | * Project which editor would try to connect to 19 | */ 20 | public project: Project 21 | 22 | /** 23 | * Glitch token that retrieves from the glitch instance 24 | */ 25 | public token: string 26 | 27 | /** 28 | * Socket connection 29 | */ 30 | public socket: WebSocket 31 | 32 | /** 33 | * Error handler function 34 | */ 35 | public errorHandler: any 36 | 37 | /** 38 | * Close handler function 39 | */ 40 | public closeHandler: any 41 | 42 | /** 43 | * Message handler function 44 | */ 45 | public messageHandler: any 46 | 47 | constructor(project: Project, token: string) { 48 | if (!project) 49 | throw new Error( 50 | 'No project was provided when tried to create new Editor instance' 51 | ) 52 | if (!token) 53 | throw new Error( 54 | 'No token was provided when tried to create new Editor instance' 55 | ) 56 | 57 | this.project = project 58 | this.token = token 59 | this.socket = new WebSocket( 60 | `${WEBSOCKET_BASE_URL}/${this.project.id}/ot?authorization=${this.token}` 61 | ) 62 | 63 | this.errorHandler = (e: WebSocket.ErrorEvent) => { 64 | throw e.error 65 | } 66 | this.closeHandler = (event: WebSocket.CloseEvent) => event 67 | this.messageHandler = (message: WebSocket.MessageEvent) => message 68 | } 69 | 70 | /** 71 | * Connects to the Glitch's project via WebSockets 72 | * 73 | * Uses `ws` library for connection 74 | */ 75 | connect(): WebSocket { 76 | const open = () => 77 | this.socket.send( 78 | JSON.stringify({ 79 | type: 'master-state', 80 | clientId: Editor.generateClientId(), 81 | force: true, 82 | }) 83 | ) 84 | 85 | this.socket.onopen = open 86 | this.socket.onerror = this.errorHandler.bind(this) 87 | this.socket.onclose = this.closeHandler.bind(this) 88 | this.socket.onmessage = this.messageHandler.bind(this) 89 | 90 | return this.socket 91 | } 92 | 93 | /** 94 | * Generates a 10 symbols random string which is used as client ID 95 | */ 96 | static generateClientId(): string { 97 | return [...Array(10)] 98 | .map(_ => (~~(Math.random() * 36)).toString(36)) 99 | .join('') 100 | } 101 | 102 | /** 103 | * Sets handler for websocket error event 104 | * @param f - Handler function 105 | */ 106 | setErrorHandler(f: (event: WebSocket.ErrorEvent) => any): this { 107 | this.errorHandler = f 108 | this.socket.onerror = this.errorHandler.bind(this) 109 | return this 110 | } 111 | 112 | /** 113 | * Sets handler for websocket close event 114 | * @param f - Handler function 115 | */ 116 | setCloseHandler(f: (event: WebSocket.CloseEvent) => any): this { 117 | this.closeHandler = f 118 | this.socket.onclose = this.closeHandler.bind(this) 119 | return this 120 | } 121 | 122 | /** 123 | * Sets handler for websocket message event 124 | * @param f - Handler function 125 | */ 126 | setMessageHandler(f: (event: WebSocket.MessageEvent) => any): this { 127 | this.messageHandler = f 128 | this.socket.onmessage = this.messageHandler.bind(this) 129 | return this 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/models/Glitch.ts: -------------------------------------------------------------------------------- 1 | import API from './API' 2 | import { 3 | API_BASE_URL, 4 | API_BASE_URL_OLD, 5 | API_TIME_INTERVAL, 6 | API_HEADERS, 7 | } from '../utils/constants' 8 | import Context from '../structures/Context' 9 | import User from '../structures/User' 10 | 11 | export interface IGlitchOptions { 12 | /** 13 | * User Glitch API authorization token 14 | */ 15 | token: string | null 16 | 17 | /** 18 | * Timeout for requests 19 | */ 20 | apiTimeout: number 21 | 22 | /** 23 | * Interval between requests 24 | */ 25 | apiInterval: number 26 | 27 | /** 28 | * Headers for requests 29 | */ 30 | apiHeaders: Record 31 | 32 | /** 33 | * Specific base URL for an Glitch API 34 | */ 35 | apiBaseUrl: string 36 | 37 | /** 38 | * Specific base URL for an old Glitch API 39 | */ 40 | apiBaseUrlOld: string 41 | 42 | /** 43 | * State that shows whether to compress requests before sending. 44 | * @default true 45 | */ 46 | compress: boolean 47 | 48 | /** 49 | * Output to the console debugging info 50 | * @default false 51 | */ 52 | debug: boolean 53 | } 54 | 55 | /** 56 | * Glitch class 57 | * 58 | * @class 59 | */ 60 | export default class Glitch { 61 | /** 62 | * Glitch API class 63 | */ 64 | api: API 65 | 66 | /** 67 | * Instance options 68 | */ 69 | options: IGlitchOptions 70 | 71 | constructor({ 72 | token, 73 | apiTimeout, 74 | apiHeaders, 75 | apiBaseUrl, 76 | apiBaseUrlOld, 77 | apiInterval, 78 | compress, 79 | debug, 80 | }: Partial = {}) { 81 | this.options = { 82 | token: token ? token.toString() : null, 83 | apiTimeout: apiTimeout || 10e3, 84 | apiInterval: apiInterval || API_TIME_INTERVAL, 85 | apiHeaders: apiHeaders || API_HEADERS, 86 | apiBaseUrl: apiBaseUrl || API_BASE_URL, 87 | apiBaseUrlOld: apiBaseUrlOld || API_BASE_URL_OLD, 88 | compress: compress || true, 89 | debug: debug || false, 90 | } 91 | this.api = new API(this) 92 | } 93 | 94 | /** 95 | * Returns user token 96 | */ 97 | get token(): string { 98 | return this.options.token 99 | } 100 | 101 | /** 102 | * Returns package version 103 | */ 104 | get VERSION(): string { 105 | return require('../../package.json').version 106 | } 107 | 108 | /** 109 | * Sets token to the instance options 110 | * @param token - Token to set 111 | */ 112 | setToken(token: string): string { 113 | this.options.token = token 114 | this.api = new API(this) 115 | return this.options.token 116 | } 117 | 118 | // TODO: Returns {"message":"Internal server error","code":"Unauthenticated"} 119 | // Need to resolve this issue 120 | // 121 | /** 122 | * Allows to set an anonymous user token 123 | */ 124 | async setAnonToken() { 125 | try { 126 | const user: Context = await this.api.enqueue( 127 | 'users/anon', 128 | {}, 129 | { method: 'POST' } 130 | ) 131 | const token = user.response.persistentToken 132 | this.setToken(token) 133 | return token 134 | } catch (e) { 135 | throw e 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/models/Logs.ts: -------------------------------------------------------------------------------- 1 | import WebSocket from 'ws' 2 | import Project from '../structures/Project' 3 | import { WEBSOCKET_BASE_URL } from '../utils/constants' 4 | 5 | /** 6 | * Glitch project's logs class 7 | * 8 | * Connects to the project's logs via WebSockets. 9 | * @class 10 | */ 11 | export default class Logs { 12 | /** 13 | * Project which logs would try to connect to 14 | */ 15 | public project: Project 16 | 17 | /** 18 | * Glitch token that retrieves from the glitch instance 19 | */ 20 | public token: string 21 | 22 | /** 23 | * Socket connection 24 | */ 25 | public socket: WebSocket 26 | 27 | /** 28 | * Error handler function 29 | */ 30 | public errorHandler: any 31 | 32 | /** 33 | * Close handler function 34 | */ 35 | public closeHandler: any 36 | 37 | /** 38 | * Message handler function 39 | */ 40 | public messageHandler: any 41 | 42 | constructor(project: Project, token: string) { 43 | if (!project) 44 | throw new Error( 45 | 'No project was provided when tried to create new Logs instance' 46 | ) 47 | if (!token) 48 | throw new Error( 49 | 'No token was provided when tried to create new Logs instance' 50 | ) 51 | 52 | this.project = project 53 | this.token = token 54 | this.socket = new WebSocket( 55 | `${WEBSOCKET_BASE_URL}/${this.project.id}/logs?authorization=${this.token}` 56 | ) 57 | 58 | this.errorHandler = (e: WebSocket.ErrorEvent) => { 59 | throw e.error 60 | } 61 | this.closeHandler = (event: WebSocket.CloseEvent) => event 62 | this.messageHandler = (message: WebSocket.MessageEvent) => message 63 | } 64 | 65 | /** 66 | * Connects to the Glitch project's terminal via WebSockets 67 | * 68 | * Uses `ws` library for connection 69 | */ 70 | connect(): WebSocket { 71 | this.socket.onerror = this.errorHandler.bind(this) 72 | this.socket.onclose = this.closeHandler.bind(this) 73 | this.socket.onmessage = this.messageHandler.bind(this) 74 | 75 | return this.socket 76 | } 77 | 78 | /** 79 | * Sets handler for websocket error event 80 | * @param f - Handler function 81 | */ 82 | setErrorHandler(f: (event: WebSocket.ErrorEvent) => any): this { 83 | this.errorHandler = f 84 | this.socket.onerror = this.errorHandler.bind(this) 85 | return this 86 | } 87 | 88 | /** 89 | * Sets handler for websocket close event 90 | * @param f - Handler function 91 | */ 92 | setCloseHandler(f: (event: WebSocket.CloseEvent) => any): this { 93 | this.closeHandler = f 94 | this.socket.onclose = this.closeHandler.bind(this) 95 | return this 96 | } 97 | 98 | /** 99 | * Sets handler for websocket message event 100 | * @param f - Handler function 101 | */ 102 | setMessageHandler(f: (event: WebSocket.MessageEvent) => any): this { 103 | this.messageHandler = f 104 | this.socket.onmessage = this.messageHandler.bind(this) 105 | return this 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/models/Projects.ts: -------------------------------------------------------------------------------- 1 | import Algolia from 'algoliasearch' 2 | import API from './API' 3 | import Project from '../structures/Project' 4 | import Editor from '../models/Editor' 5 | import Context from '../structures/Context' 6 | import Logs from './Logs' 7 | import Terminal from './Terminal' 8 | 9 | /** @hidden */ 10 | const getParams = ['id', 'domain'] 11 | 12 | /** 13 | * Projects class 14 | * 15 | * @class 16 | */ 17 | export default class Projects { 18 | /** @hidden */ 19 | private _api: API 20 | 21 | /** 22 | * Projects constructor 23 | * 24 | * @param {API} api API instance 25 | */ 26 | constructor(api: API) { 27 | this._api = api 28 | } 29 | 30 | /** 31 | * Gets project by id or domain 32 | * 33 | * @param {Object} params 34 | * @param {string} params.id - Project ID 35 | * @param {string} params.domain - Project domain 36 | */ 37 | async get( 38 | params: Partial<{ id: string; domain: string }> 39 | ): Promise { 40 | const param = Object.keys(params || {}).find(e => getParams.some(p => p === e)) 41 | if (!params || !param) throw new Error('No parameter provided, supported: ' + getParams) 42 | 43 | const context: Context = await this._api.enqueue( 44 | `projects/by/${param}`, 45 | params, 46 | { 47 | method: 'GET', 48 | } 49 | ) 50 | 51 | if (Object.keys(context.response).length === 0) { 52 | return null 53 | } 54 | 55 | // @ts-ignore 56 | return new Project(context.response[params[param]]) 57 | } 58 | 59 | /** 60 | * Searches project by query 61 | * 62 | * @param {string} query - Query string 63 | */ 64 | async search(query: string) { 65 | if (!query) { 66 | throw new Error('No query parameter was provided') 67 | } 68 | 69 | if (!this._api._options.token) { 70 | throw new Error('No token in Glitch instance was provided') 71 | } 72 | 73 | // Get credentials to perform Algolia requests 74 | const creds = await this._api.getSearchCreds() 75 | const client = Algolia(creds.id, creds.searchKey) 76 | const index = client.initIndex('search_projects') 77 | const response = await index.search(query, { 78 | hitsPerPage: 100, 79 | facetFilters: 'notSafeForKids:false', 80 | }) 81 | 82 | return { 83 | ...response, 84 | hits: response.hits.map((project: Project) => new Project(project)), 85 | } 86 | } 87 | 88 | /** 89 | * Gets project's questions 90 | */ 91 | async questions() { 92 | return ( 93 | await this._api.enqueue( 94 | 'projects/questions', 95 | {}, 96 | { 97 | method: 'GET', 98 | oldApi: true, 99 | } 100 | ) 101 | ).response 102 | } 103 | 104 | /** 105 | * Remixes project 106 | * 107 | * @param id - Project ID 108 | */ 109 | async remix(id: string) { 110 | if (!id) { 111 | throw new Error('No project id was provided') 112 | } 113 | 114 | const context: Context = await this._api.enqueue( 115 | `projects/${id}/remix`, 116 | {}, 117 | { 118 | method: 'POST', 119 | } 120 | ) 121 | 122 | return new Project(context.response) 123 | } 124 | 125 | /** 126 | * Edits project 127 | * @param params 128 | * @param params.id - Project ID 129 | * @param params.domain - Project domain 130 | */ 131 | async edit( 132 | params: Partial<{ id: string; domain: string }> | Project 133 | ): Promise { 134 | if (params instanceof Project) { 135 | /** 136 | * We initialize editor with a project that 137 | * was might have been already got and passed as the parameter 138 | */ 139 | return new Editor(params, this._api._options.token) 140 | } else { 141 | /** Otherwise, we have to get the project */ 142 | return new Editor(await this.get(params), this._api._options.token) 143 | } 144 | } 145 | 146 | /** 147 | * Connects to the project logs 148 | * @param params 149 | * @param params.id - Project ID 150 | * @param params.domain - Project domain 151 | */ 152 | async logs( 153 | params: Partial<{ id: string; domain: string }> | Project 154 | ): Promise { 155 | if (params instanceof Project) { 156 | /** 157 | * We initialize logs class with a project that 158 | * was might have been already got and passed as the parameter 159 | */ 160 | return new Logs(params, this._api._options.token) 161 | } else { 162 | /** Otherwise, we have to get the project */ 163 | return new Logs(await this.get(params), this._api._options.token) 164 | } 165 | } 166 | 167 | /** 168 | * Connects to the project logs 169 | * @param params 170 | * @param params.id - Project ID 171 | * @param params.domain - Project domain 172 | */ 173 | async terminal( 174 | params: Partial<{ id: string; domain: string }> | Project 175 | ): Promise { 176 | if (params instanceof Project) { 177 | /** 178 | * We initialize terminal class with a project that 179 | * was might have been already got and passed as the parameter 180 | */ 181 | return new Terminal(params, this._api._options.token) 182 | } else { 183 | /** Otherwise, we have to get the project */ 184 | return new Terminal(await this.get(params), this._api._options.token) 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/models/Request.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Request class 3 | * 4 | * @class 5 | */ 6 | export default class Request { 7 | method: string 8 | params: Record 9 | requestParams: Record 10 | promise: Promise 11 | resolve: (value?: any) => void 12 | reject: (reason?: any) => void 13 | 14 | /** 15 | * Request constructor 16 | * 17 | * @param {string} method Method to run 18 | * @param {Object} params Parameters for method 19 | * @param {Object} requestParams Parameters for request 20 | */ 21 | constructor( 22 | method: string, 23 | params: Record = {}, 24 | requestParams: Record 25 | ) { 26 | this.method = method 27 | this.params = { ...params } 28 | this.requestParams = requestParams || { method: 'GET' } 29 | 30 | this.promise = new Promise((resolve, reject) => { 31 | this.resolve = resolve 32 | this.reject = reject 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/models/Teams.ts: -------------------------------------------------------------------------------- 1 | import Algolia from 'algoliasearch' 2 | import API from './API' 3 | import Team from '../structures/Team' 4 | import Context from '../structures/Context' 5 | 6 | /** @hidden */ 7 | const getParams = ['url', 'id'] 8 | 9 | /** 10 | * Teams class 11 | * 12 | * @class 13 | */ 14 | export default class Teams { 15 | /** 16 | * @hidden 17 | */ 18 | private _api: API 19 | 20 | /** 21 | * Teams constructor 22 | * 23 | * @param {API} api API instance 24 | */ 25 | constructor(api: API) { 26 | this._api = api 27 | } 28 | 29 | /** 30 | * Gets project by url 31 | * 32 | * @param {Object} params 33 | * @param {string} params.url - Project URL 34 | * @param {string} params.id - Project ID 35 | */ 36 | async get( 37 | params: Partial<{ url: string; id: string | number }> 38 | ): Promise { 39 | const param = Object.keys(params || {}).find(e => getParams.some(p => p === e)) 40 | if (!params || !param) throw new Error('No parameter provided, supported: ' + getParams) 41 | 42 | const context: Context = await this._api.enqueue( 43 | `teams/by/${param}`, 44 | params, 45 | { 46 | method: 'GET', 47 | } 48 | ) 49 | 50 | if (Object.keys(context.response).length === 0) { 51 | return null 52 | } 53 | 54 | // @ts-ignore 55 | return new Team(context.response[params[param]]) 56 | } 57 | 58 | /** 59 | * Searches team by query 60 | * 61 | * @param {string} query - Query string 62 | */ 63 | async search(query: string) { 64 | if (!query || query.length < 1) { 65 | throw new Error('No query parameter was provided') 66 | } 67 | 68 | if (!this._api._options.token) { 69 | throw new Error('No token in Glitch instance was provided') 70 | } 71 | 72 | // Get credentials to perform Algolia requests 73 | const creds = await this._api.getSearchCreds() 74 | const client = Algolia(creds.id, creds.searchKey) 75 | const index = client.initIndex('search_teams') 76 | const response = await index.search(query, { 77 | hitsPerPage: 100, 78 | }) 79 | 80 | return { 81 | ...response, 82 | hits: response.hits.map((team: Team) => new Team(team)), 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/models/Terminal.ts: -------------------------------------------------------------------------------- 1 | import WebSocket from 'ws' 2 | import Project from '../structures/Project' 3 | import { WEBSOCKET_BASE_URL } from '../utils/constants' 4 | 5 | /** 6 | * Glitch project's terminal class 7 | * 8 | * Connects to the project's terminal via WebSockets. 9 | * @class 10 | */ 11 | export default class Terminal { 12 | /** 13 | * Project which terminal would try to connect to 14 | */ 15 | public project: Project 16 | 17 | /** 18 | * Glitch token that retrieves from the glitch instance 19 | */ 20 | public token: string 21 | 22 | /** 23 | * Socket connection 24 | */ 25 | public socket: WebSocket 26 | 27 | /** 28 | * Error handler function 29 | */ 30 | public errorHandler: any 31 | 32 | /** 33 | * Close handler function 34 | */ 35 | public closeHandler: any 36 | 37 | /** 38 | * Message handler function 39 | */ 40 | public messageHandler: any 41 | 42 | constructor(project: Project, token: string) { 43 | if (!project) 44 | throw new Error( 45 | 'No project was provided when tried to create new Terminal instance' 46 | ) 47 | if (!token) 48 | throw new Error( 49 | 'No token was provided when tried to create new Terminal instance' 50 | ) 51 | 52 | this.project = project 53 | this.token = token 54 | this.socket = new WebSocket( 55 | `${WEBSOCKET_BASE_URL}/${this.project.domain}/console?authorization=${this.token}` 56 | ) 57 | 58 | this.errorHandler = (e: WebSocket.ErrorEvent) => { 59 | throw e.error 60 | } 61 | this.closeHandler = (event: WebSocket.CloseEvent) => event 62 | this.messageHandler = (message: WebSocket.MessageEvent) => message 63 | } 64 | 65 | /** 66 | * Connects to the Glitch project's terminal via WebSockets 67 | * 68 | * Uses `ws` library for connection 69 | */ 70 | connect(): WebSocket { 71 | this.socket.onerror = this.errorHandler.bind(this) 72 | this.socket.onclose = this.closeHandler.bind(this) 73 | this.socket.onmessage = this.messageHandler.bind(this) 74 | 75 | return this.socket 76 | } 77 | 78 | /** 79 | * Sets handler for websocket error event 80 | * @param f - Handler function 81 | */ 82 | setErrorHandler(f: (event: WebSocket.ErrorEvent) => any): this { 83 | this.errorHandler = f 84 | this.socket.onerror = this.errorHandler.bind(this) 85 | return this 86 | } 87 | 88 | /** 89 | * Sets handler for websocket close event 90 | * @param f - Handler function 91 | */ 92 | setCloseHandler(f: (event: WebSocket.CloseEvent) => any): this { 93 | this.closeHandler = f 94 | this.socket.onclose = this.closeHandler.bind(this) 95 | return this 96 | } 97 | 98 | /** 99 | * Sets handler for websocket message event 100 | * @param f - Handler function 101 | */ 102 | setMessageHandler(f: (event: WebSocket.MessageEvent) => any): this { 103 | this.messageHandler = f 104 | this.socket.onmessage = this.messageHandler.bind(this) 105 | return this 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/models/Users.ts: -------------------------------------------------------------------------------- 1 | import Algolia from 'algoliasearch' 2 | import User from '../structures/User' 3 | import API from './API' 4 | import Context from '../structures/Context' 5 | import SearchCreds from '../structures/SearchCreds' 6 | 7 | /** @hidden */ 8 | const getParams = ['id', 'login'] 9 | 10 | /** 11 | * Users class 12 | * 13 | * @class 14 | */ 15 | export default class Users { 16 | /** 17 | * @hidden 18 | */ 19 | private _api: API 20 | 21 | /** 22 | * Users constructor 23 | * 24 | * @param {API} api API class 25 | */ 26 | constructor(api: API) { 27 | this._api = api 28 | } 29 | 30 | /** 31 | * Gets user by id or login 32 | * 33 | * @param {Object} params 34 | * @param {number} params.id - User ID 35 | * @param {string} params.login - User login 36 | */ 37 | async get( 38 | params: Partial<{ id: string | number; login: string }> 39 | ): Promise { 40 | const param = Object.keys(params || {}).find(e => getParams.some(p => p === e)) 41 | if (!params || !param) throw new Error('No parameter provided, supported: ' + getParams) 42 | 43 | const context: Context = await this._api.enqueue( 44 | `users/by/${param}`, 45 | params, 46 | { 47 | method: 'GET', 48 | } 49 | ) 50 | 51 | if (Object.keys(context.response).length === 0) { 52 | return null 53 | } 54 | 55 | // @ts-ignore 56 | return new User(context.response[params[param]]) 57 | } 58 | 59 | /** 60 | * Searches user by query 61 | * 62 | * @param {string} query - Query string 63 | */ 64 | async search(query: string) { 65 | if (!query || query.length < 1) { 66 | throw new Error('No query parameter was provided') 67 | } 68 | 69 | if (!this._api._options.token) { 70 | throw new Error('No token in Glitch instance was provided') 71 | } 72 | 73 | // Get credentials to perform Algolia requests 74 | const creds = await this._api.getSearchCreds() 75 | const client = Algolia(creds.id, creds.searchKey) 76 | const index = client.initIndex('search_users') 77 | const response = await index.search(query, { 78 | hitsPerPage: 100, 79 | }) 80 | 81 | return { 82 | ...response, 83 | hits: response.hits.map((user: User) => new User(user)), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/structures/Context.ts: -------------------------------------------------------------------------------- 1 | import { Response } from 'node-fetch' 2 | import { IRequestOptions } from 'src/interfaces/Requests' 3 | 4 | /** Error interface in Context class */ 5 | export interface IContextError { 6 | /** Error's status code, like 404 or 401 */ 7 | statusCode: number 8 | /** Error's message text */ 9 | message: string 10 | /** Request options */ 11 | requestOptions: IRequestOptions 12 | } 13 | 14 | /** 15 | * Context class 16 | * 17 | * Represents a context of an API response 18 | * @class 19 | */ 20 | export default class Context { 21 | /** API response */ 22 | response: T 23 | /** Requets object */ 24 | request: Request 25 | /** Contains error that was got from Glitch API */ 26 | error: IContextError 27 | /** Requets options */ 28 | params: IRequestOptions 29 | 30 | /** 31 | * 32 | * @param response - Response object 33 | * @param text - Response text 34 | * @param requestOptions - Request options 35 | */ 36 | constructor( 37 | response: Response, 38 | text: string, 39 | requestOptions: IRequestOptions 40 | ) { 41 | if (!response) throw new Error('No response object was provided') 42 | try { 43 | this.response = JSON.parse(text) 44 | } catch (e) { 45 | throw new Error(`Could not parse the given response text (${text})`) 46 | } 47 | 48 | this.params = requestOptions 49 | if (!response.ok) 50 | this.error = { 51 | statusCode: response.status, 52 | message: text, 53 | requestOptions, 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/structures/Feature.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Feature class 3 | * 4 | * Represents a Glitch project's feature 5 | * @class 6 | */ 7 | export default class Feature { 8 | /** 9 | * Feature ID 10 | */ 11 | id: number 12 | /** 13 | * Feature name 14 | */ 15 | name: string 16 | /** 17 | * Feature data (usually null) 18 | */ 19 | data?: any 20 | /** 21 | * Date when the project's feature would expire 22 | */ 23 | expiresAt: Date 24 | constructor(options: Partial) { 25 | this.id = options.id 26 | this.name = options.name 27 | this.data = options.data 28 | this.expiresAt = new Date(options.expiresAt) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/structures/Member.ts: -------------------------------------------------------------------------------- 1 | import IMemberOptions from '../interfaces/MemberOptions' 2 | 3 | /** 4 | * Member class 5 | * 6 | * Represents a Glitch project's member. 7 | * It vares from [[User]] class, so if you want to see all fields the user has, check [[User]] 8 | * @class 9 | */ 10 | export default class Member { 11 | /** 12 | * User ID 13 | */ 14 | id: number 15 | /** 16 | * User access level in the project 17 | */ 18 | accessLevel: number 19 | constructor(options: Partial) { 20 | this.id = options.userId 21 | this.accessLevel = options.accessLevel 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/structures/Project.ts: -------------------------------------------------------------------------------- 1 | import ProjectMember from './Member' 2 | import Feature from './Feature' 3 | import Member from './Member' 4 | import IMemberOptions from '../interfaces/MemberOptions' 5 | 6 | /** 7 | * Project class 8 | * 9 | * Represents a Glitch project 10 | * @class 11 | */ 12 | export default class Project { 13 | /** 14 | * Project ID 15 | */ 16 | id: string 17 | /** 18 | * Project description 19 | */ 20 | description?: string 21 | /** 22 | * Project domain name 23 | */ 24 | domain: string 25 | /** 26 | * Project base ID 27 | * Shows the ID of project it was remixed from 28 | */ 29 | baseId?: string 30 | /** 31 | * Shows whether the project is private or not 32 | */ 33 | private: boolean 34 | /** 35 | * Project likes count 36 | */ 37 | likesCount: number 38 | /** 39 | * Shows whether the project is suspended or not 40 | */ 41 | isSuspended: boolean 42 | /** 43 | * If the project is suspended, then it represents the reason of the suspension 44 | */ 45 | suspendedReason?: string 46 | /** 47 | * Date when the project avatar was last updated 48 | */ 49 | avatarUpdatedAt: Date 50 | /** 51 | * Shows whether the project should be shown as glitch team's 52 | */ 53 | showAsGlitchTeam: boolean 54 | /** 55 | * Shows whether the project could be only in the embedded form 56 | */ 57 | isEmbedOnly: boolean 58 | /** 59 | * Project's remix chain 60 | */ 61 | remixChain: string[] 62 | /** 63 | * Shows whether the project is not safe for kids 64 | */ 65 | notSafeForKids: boolean 66 | /** 67 | * Date when the project was created 68 | */ 69 | createdAt: Date 70 | /** 71 | * Date when the project was updated 72 | */ 73 | updatedAt: Date 74 | /** 75 | * List of project members (see [[Member]]) 76 | */ 77 | permissions?: Member[] 78 | /** 79 | * Project features (see [[Feature]]) 80 | */ 81 | features?: Feature[] 82 | /** 83 | * IDs of teams the project is in 84 | */ 85 | teamIds?: number[] 86 | constructor(options: Partial) { 87 | this.id = options.id 88 | this.description = options.description 89 | this.domain = options.domain 90 | this.baseId = options.baseId 91 | this.private = options.private 92 | this.likesCount = options.likesCount 93 | this.isSuspended = !!options.suspendedReason 94 | this.suspendedReason = options.suspendedReason 95 | this.avatarUpdatedAt = new Date(options.avatarUpdatedAt) 96 | this.showAsGlitchTeam = options.showAsGlitchTeam 97 | this.isEmbedOnly = options.isEmbedOnly 98 | this.remixChain = options.remixChain 99 | this.notSafeForKids = options.notSafeForKids 100 | this.createdAt = new Date(options.createdAt) 101 | this.updatedAt = new Date(options.updatedAt) 102 | this.permissions = options.permissions 103 | ? options.permissions.map( 104 | (member: IMemberOptions) => new ProjectMember(member) 105 | ) 106 | : [] 107 | this.features = options.features 108 | ? options.features.map((feature: Feature) => new Feature(feature)) 109 | : [] 110 | this.teamIds = options.teamIds 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/structures/SearchCreds.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Credentials used for search class 3 | * 4 | * Represents an Algolia creds for searching 5 | * @class 6 | */ 7 | export default class SearchCreds { 8 | /** 9 | * Algoila app ID 10 | */ 11 | id: string 12 | /** 13 | * Algolia search key 14 | */ 15 | searchKey: string 16 | constructor(options: Partial) { 17 | this.id = options.id 18 | this.searchKey = this.searchKey 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/structures/Team.ts: -------------------------------------------------------------------------------- 1 | import Member from './Member' 2 | import Feature from './Feature' 3 | 4 | /** 5 | * Team class 6 | * 7 | * Represents a Glitch team 8 | * @class 9 | */ 10 | export default class Team { 11 | /** 12 | * Team ID 13 | */ 14 | id: number 15 | /** 16 | * Team description 17 | */ 18 | description?: string 19 | /** 20 | * Team URL domain 21 | * It is a URL domain for team's page 22 | */ 23 | url: string 24 | /** 25 | * Team name 26 | * It shows in a team's page 27 | */ 28 | name: string 29 | /** 30 | * Shows whether the team has an avatar image 31 | */ 32 | hasAvatarImage: boolean 33 | /** 34 | * Represents cover color in `rgb()` format 35 | */ 36 | coverColor: string 37 | /** 38 | * Represents background color in `rgb()` format 39 | */ 40 | backgroundColor: string 41 | /** 42 | * Shows whether the team has a cover image 43 | */ 44 | hasCoverImage: boolean 45 | /** 46 | * Team location 47 | */ 48 | location?: string 49 | /** 50 | * Show whether the team is verified by Glitch team 51 | */ 52 | isVerified: boolean 53 | /** 54 | * Allowed domain for the team 55 | */ 56 | whitelistedDomain?: string 57 | /** 58 | * ID of the featured project 59 | */ 60 | featuredProjectId?: string 61 | /** 62 | * Date when the team was created 63 | */ 64 | createdAt: Date 65 | /** 66 | * Date when the team was last updated 67 | */ 68 | updatedAt: Date 69 | /** 70 | * List of team's members 71 | */ 72 | teamPermissions: Member[] 73 | /** 74 | * List of team's features 75 | */ 76 | 77 | features: Feature[] 78 | constructor(options: Partial) { 79 | this.id = options.id 80 | this.description = options.description 81 | this.url = options.url 82 | this.name = options.name 83 | this.hasAvatarImage = options.hasAvatarImage 84 | this.coverColor = options.coverColor 85 | this.backgroundColor = options.backgroundColor 86 | this.hasCoverImage = options.hasCoverImage 87 | this.location = options.location 88 | this.isVerified = options.isVerified 89 | this.whitelistedDomain = options.whitelistedDomain 90 | this.featuredProjectId = options.featuredProjectId 91 | this.createdAt = new Date(options.createdAt) 92 | this.updatedAt = new Date(options.updatedAt) 93 | this.teamPermissions = options.teamPermissions 94 | ? options.teamPermissions.map((member: Member) => new Member(member)) 95 | : [] 96 | this.features = options.features 97 | ? options.features.map((feature: Feature) => new Feature(feature)) 98 | : [] 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/structures/User.ts: -------------------------------------------------------------------------------- 1 | import Feature from './Feature' 2 | 3 | /** 4 | * User class 5 | * 6 | * Represents a Glitch user 7 | * @class 8 | */ 9 | export default class User { 10 | /** 11 | * User ID 12 | */ 13 | id: number 14 | /** 15 | * User login 16 | */ 17 | login: string 18 | /** 19 | * User name 20 | */ 21 | name: string 22 | /** 23 | * User page description 24 | */ 25 | description?: string 26 | /** 27 | * User location 28 | * ex. Ferndale, WA 29 | */ 30 | location?: string 31 | /** 32 | * User thanks count 33 | */ 34 | thanksCount: number 35 | /** 36 | * User's UTC offset 37 | */ 38 | utcOffset: number 39 | /** 40 | * User color represented in HEX format 41 | */ 42 | color: string 43 | /** 44 | * Shows whether the user has a cover image 45 | */ 46 | hasCoverImage: boolean 47 | /** 48 | * User's cover color represented in HEX format 49 | */ 50 | coverColor: string 51 | /** 52 | * User's avatar URL 53 | */ 54 | avatarUrl: string 55 | /** 56 | * User's avatar thumbnail URL 57 | */ 58 | avatarThumbnailUrl: string 59 | /** 60 | * Date when the user was created 61 | */ 62 | createdAt: Date 63 | /** 64 | * Date when the user was last updated 65 | */ 66 | updatedAt: Date 67 | /** 68 | * User's featured project id 69 | */ 70 | featuredProjectId?: string 71 | /** 72 | * Shows whether the user is a support user 73 | */ 74 | isSupport: boolean 75 | /** 76 | * Shows whether the user is an infrastructure user 77 | */ 78 | isInfrastructureUser: boolean 79 | /** 80 | * List of user's features 81 | */ 82 | features: Feature[] 83 | /** 84 | * Users's persistent token 85 | */ 86 | persistentToken?: string 87 | googleId?: string 88 | googleToken?: string 89 | slackId?: string 90 | slackToken?: string 91 | slackTeamId?: string 92 | facebookId?: string 93 | facebookToken?: string 94 | githubId?: string 95 | githubToken?: string 96 | passwordEnabled?: boolean 97 | loginAttempts?: number 98 | accountLocked?: boolean 99 | twoFactorEnabled?: boolean 100 | constructor(options: Partial) { 101 | this.id = options.id 102 | this.login = options.login 103 | this.name = options.name 104 | this.description = options.description 105 | this.location = options.location 106 | this.thanksCount = options.thanksCount 107 | this.utcOffset = options.utcOffset 108 | this.color = options.color 109 | this.hasCoverImage = options.hasCoverImage 110 | this.coverColor = options.coverColor 111 | this.avatarUrl = options.avatarUrl 112 | this.avatarThumbnailUrl = options.avatarThumbnailUrl 113 | this.createdAt = new Date(options.createdAt) 114 | this.updatedAt = new Date(options.updatedAt) 115 | this.featuredProjectId = options.featuredProjectId 116 | this.isSupport = options.isSupport 117 | this.isInfrastructureUser = options.isInfrastructureUser 118 | this.features = options.features 119 | ? options.features.map((feature: Feature) => new Feature(feature)) 120 | : [] 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Glitch API v1 base url 3 | */ 4 | export const API_BASE_URL: string = 'https://api.glitch.com/v1' 5 | 6 | /** 7 | * Glitch API old base url 8 | */ 9 | export const API_BASE_URL_OLD: string = 'https://api.glitch.com' 10 | 11 | /** 12 | * Glitch API websockets base url 13 | */ 14 | export const WEBSOCKET_BASE_URL: string = 'wss://api.glitch.com' 15 | 16 | /** 17 | * Default interval between requests 18 | */ 19 | export const API_TIME_INTERVAL: number = 0 20 | 21 | export const API_HEADERS = { 22 | accept: 'application/json, text/plain, */*', 23 | 'accept-encoding': 'gzip, deflate, br', 24 | 'accept-language': 'en-US,en;q=0.9,ru;q=0.8', 25 | 'content-length': 0, 26 | } 27 | -------------------------------------------------------------------------------- /tests/api.test.ts: -------------------------------------------------------------------------------- 1 | import { Glitch, Request } from '../src' 2 | 3 | const glitch = new Glitch({ debug: true }) 4 | const { api } = glitch 5 | const ID = 1424713 6 | 7 | describe('Get teams', () => { 8 | it('should log debug info', async () => { 9 | let consoleOutput: string[] = [] 10 | console.debug = (s: string) => consoleOutput.push(s) 11 | await api.users.get({ id: ID }) 12 | expect( 13 | consoleOutput.some(e => e.startsWith('GLAPI: Fetching')) 14 | ).toBeTruthy() 15 | expect(consoleOutput.some(e => e.startsWith('GLAPI: Fetched'))).toBeTruthy() 16 | }) 17 | 18 | it('should throw on fetch error', async () => { 19 | try { 20 | await api.callMethod(new Request('METHOD_THAT_IS_NOT_DEFINED', {}, {})) 21 | } catch (e) { 22 | expect(e).toBeTruthy() 23 | } 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /tests/glitch.test.ts: -------------------------------------------------------------------------------- 1 | import { Glitch } from '../src' 2 | 3 | const TOKEN = 'token' 4 | const glitch = new Glitch({ token: TOKEN }) 5 | 6 | describe('Get teams', () => { 7 | it('should return token', async () => { 8 | expect(glitch.token).toBe(TOKEN) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /tests/projects.test.ts: -------------------------------------------------------------------------------- 1 | import { Glitch, Editor, Project } from '../src' 2 | 3 | const glitch = new Glitch() 4 | const { api } = glitch 5 | const ID = 'a0fcd798-9ddf-42e5-8205-17158d4bf5bb' 6 | const DOMAIN = 'hello-express' 7 | const editor = new Editor(new Project({}), 'token') 8 | 9 | describe('Get projects', () => { 10 | it('should get project by id', async () => { 11 | await glitch.setAnonToken() 12 | const res = await api.projects.get({ id: ID }) 13 | 14 | expect(res.id).toBe(ID) 15 | }) 16 | 17 | it('should get project by domain', async () => { 18 | await glitch.setAnonToken() 19 | const res = await api.projects.get({ domain: DOMAIN }) 20 | 21 | expect(res.domain).toBe(DOMAIN) 22 | }) 23 | 24 | it('should throw without param', () => { 25 | expect(() => api.projects.get(null)).rejects.toThrow() 26 | expect(() => api.projects.get({})).rejects.toThrow() 27 | }) 28 | }) 29 | 30 | describe('Search project', () => { 31 | it( 32 | 'should search project by query', 33 | async () => { 34 | await glitch.setAnonToken() 35 | const res = await api.projects.search(DOMAIN) 36 | 37 | expect(res).not.toBeNull() 38 | }, 39 | 60 * 1000 40 | ) 41 | 42 | it('should throw without param', () => { 43 | expect(() => api.projects.search(null)).rejects.toThrow() 44 | }) 45 | }) 46 | 47 | describe('Edit project', () => { 48 | it('should throw without param', () => { 49 | expect(() => api.projects.edit(null)).rejects.toThrow() 50 | }) 51 | 52 | it('should throw without a project', async () => { 53 | expect(() => new Editor(null, null)).toThrow() 54 | }) 55 | it('should throw without a token', async () => { 56 | expect(() => new Editor(new Project({}), null)).toThrow() 57 | }) 58 | it('should return valid client ID', async () => { 59 | expect(Editor.generateClientId()) 60 | }) 61 | it('should set error handler', async () => { 62 | try { 63 | editor.setErrorHandler(() => {}) 64 | } catch (e) { 65 | expect(e).toBeFalsy() 66 | } 67 | }) 68 | it('should set close handler', async () => { 69 | try { 70 | editor.setCloseHandler(() => {}) 71 | } catch (e) { 72 | expect(e).toBeFalsy() 73 | } 74 | }) 75 | it('should set message handler', async () => { 76 | try { 77 | editor.setMessageHandler(() => {}) 78 | } catch (e) { 79 | expect(e).toBeFalsy() 80 | } 81 | }) 82 | }) 83 | -------------------------------------------------------------------------------- /tests/teams.test.ts: -------------------------------------------------------------------------------- 1 | import { Glitch } from '../src' 2 | 3 | const glitch = new Glitch() 4 | const { api } = glitch 5 | const ID = 4683 6 | const _URL= 'tihon' 7 | 8 | describe('Get teams', () => { 9 | it('should get team by url', async () => { 10 | await glitch.setAnonToken() 11 | const res = await api.teams.get({ url: _URL }) 12 | 13 | expect(res.url).toBe(_URL) 14 | }) 15 | 16 | it('should get team by id', async () => { 17 | await glitch.setAnonToken() 18 | const res = await api.teams.get({ id: ID }) 19 | 20 | expect(res.id).toBe(ID) 21 | }) 22 | 23 | it('should throw without param', () => { 24 | expect(() => api.teams.get(null)).rejects.toThrow() 25 | expect(() => api.teams.get({})).rejects.toThrow() 26 | }) 27 | }) 28 | 29 | describe('Search teams', () => { 30 | it('should search teams by query', async () => { 31 | await glitch.setAnonToken() 32 | const res = await api.teams.search(_URL) 33 | 34 | expect(res).not.toBeNull() 35 | }) 36 | 37 | it('should throw without param', () => { 38 | expect(() => api.teams.search(null)).rejects.toThrow() 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /tests/users.test.ts: -------------------------------------------------------------------------------- 1 | import { Glitch } from '../src' 2 | 3 | const glitch = new Glitch() 4 | const { api } = glitch 5 | const ID = 1424713 6 | const LOGIN = 'jarvis394' 7 | 8 | describe('Get users', () => { 9 | it('should get user by id', async () => { 10 | await glitch.setAnonToken() 11 | const res = await api.users.get({ id: ID }) 12 | 13 | expect(res.id).toBe(ID) 14 | }) 15 | 16 | it('should get user by login', async () => { 17 | await glitch.setAnonToken() 18 | const res = await api.users.get({ login: LOGIN }) 19 | 20 | expect(res.login).toBe(LOGIN) 21 | }) 22 | 23 | it('should throw without param', () => { 24 | expect(api.users.get(null)).rejects.toThrow() 25 | expect(api.users.get({})).rejects.toThrow() 26 | }) 27 | }) 28 | 29 | describe('Search users', () => { 30 | it('should search users by query', async () => { 31 | await glitch.setAnonToken() 32 | const res = await api.users.search(LOGIN) 33 | 34 | expect(res).not.toBeNull() 35 | }) 36 | 37 | it('should throw without param', () => { 38 | expect(api.users.search(null)).rejects.toThrow() 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "esModuleInterop": true, 4 | "removeComments": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "noImplicitAny": true, 8 | "module": "commonjs", 9 | "target": "es5", 10 | "sourceMap": false, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": false 14 | }, 15 | "include": ["src/**/*"], 16 | "exclude": ["node_modules", "dist", "**/*.test.(t|j)s"], 17 | "typedocOptions": { 18 | "mode": "modules", 19 | "out": "docs" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /watch.json: -------------------------------------------------------------------------------- 1 | { 2 | "delay": 10000 3 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const TypedocWebpackPlugin = require('typedoc-webpack-plugin') 3 | 4 | module.exports = { 5 | mode: 'production', 6 | entry: './src/index.ts', 7 | target: 'node', 8 | externals: ['bufferutil', 'utf-8-validate'], 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.tsx?$/, 13 | use: 'awesome-typescript-loader', 14 | exclude: /node_modules/ 15 | } 16 | ] 17 | }, 18 | optimization: { 19 | minimize: false 20 | }, 21 | resolve: { 22 | extensions: ['.tsx', '.ts', '.js'] 23 | }, 24 | output: { 25 | libraryTarget: 'umd', 26 | library: 'glitch-api', 27 | filename: 'index.js', 28 | path: path.resolve(__dirname, 'lib') 29 | }, 30 | plugins: [ 31 | new TypedocWebpackPlugin({ 32 | name: 'glitch-api', 33 | mode: 'file' 34 | }, './src') 35 | ] 36 | } 37 | --------------------------------------------------------------------------------