├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .pug-lintrc ├── Jenkinsfile ├── LICENSE ├── build ├── _iconfont.less ├── css.js ├── docs.js ├── libraries.json ├── misc.js ├── tmp │ └── .gitignore └── util.js ├── config ├── .gitignore └── default.yaml ├── gulpfile.js ├── package.json ├── readme.md └── src ├── .eslintrc ├── backendConfig.js ├── css ├── _book.less ├── _bootstrap.less ├── _footer.less ├── _highlight.less ├── _intro.less ├── _layout.less ├── _mixins.less ├── _rest.less ├── _slide.less ├── _type.less ├── _variables.less └── style.less ├── icons ├── link-1.svg ├── lock-3.svg ├── logo-github.svg ├── minus-5.svg ├── share-8.svg ├── time-1.svg └── view-headline.svg ├── img ├── DevBot.png ├── apple-touch-icon-144x144.png ├── apple-touch-icon-152x152.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── footer-logo.png ├── icon │ ├── LICENSE │ ├── book-open-2.svg │ ├── bubble-chat-smiley-1.svg │ ├── bubble-chat-typing-3.svg │ ├── chat-double-bubble-1.svg │ ├── computer-chip.svg │ ├── download-circle.svg │ ├── game-controller-2.svg │ ├── lang-haxe.svg │ ├── lang-java.svg │ ├── lang-js.svg │ ├── lang-python.svg │ ├── lang-swift.svg │ ├── logo-github.svg │ ├── logo-twitter-bird.svg │ ├── rank-army-star-1.svg │ └── rocket.svg ├── intro.png ├── line-pattern.png ├── logo.png ├── mstile-144x144.png ├── reference │ ├── interactive │ │ ├── HighLevelOverview.svg │ │ ├── InteractiveHierarchy.svg │ │ ├── ProjectLifecycle.svg │ │ ├── TransactionLifecycle.svg │ │ ├── button.png │ │ ├── cdk │ │ │ ├── cdkHTML.png │ │ │ ├── cdkOverview.png │ │ │ ├── cdkPreact.png │ │ │ └── cdkStartScreen.png │ │ ├── controls │ │ │ └── project-heirarchy.svg │ │ ├── joystick.png │ │ ├── joystickCoordinates.svg │ │ ├── label.png │ │ ├── link-demo.gif │ │ ├── studio │ │ │ ├── PublishProcess.svg │ │ │ ├── controls.png │ │ │ ├── createNewProject.png │ │ │ ├── editorTabs.png │ │ │ ├── editors │ │ │ │ ├── add.png │ │ │ │ ├── button.png │ │ │ │ └── list.png │ │ │ ├── grid_with_controls.png │ │ │ ├── scenes.png │ │ │ └── share │ │ │ │ ├── explicitSharing.png │ │ │ │ ├── shareButton.jpg │ │ │ │ ├── shareButton.png │ │ │ │ └── shareCode.png │ │ └── textbox.png │ └── teststreams │ │ └── enable.png └── tutorials │ ├── interactive │ └── joystick.png │ └── rest │ └── java │ └── runconfig.png ├── index.pug ├── js ├── modals.js ├── names.js ├── oauth.js └── scroll.js ├── layout.pug ├── legal ├── attributions.pug ├── developer-agreement.pug └── licenses.txt ├── mixins.pug ├── oauthreturn.pug ├── reference ├── book_layout.pug ├── chat │ ├── chatEndpoint.json │ ├── data.json │ ├── exchange.txt │ ├── index.pug │ └── menu.pug ├── constellation │ ├── events.json │ ├── events_table.pug │ └── index.pug ├── interactive │ ├── c_api.pug │ ├── cplusplus │ │ ├── data.json │ │ └── index.pug │ ├── csharp │ │ ├── data.json │ │ └── index.pug │ ├── design.pdf │ ├── index.pug │ ├── menu.pug │ └── protocol │ │ ├── Makefile │ │ ├── img │ │ ├── client-method-optional.pdf │ │ ├── client-method-optional.svg │ │ ├── client-method.pdf │ │ ├── client-method.svg │ │ ├── compression-diagram.pdf │ │ ├── server-method-optional.pdf │ │ ├── server-method-optional.svg │ │ ├── server-method.pdf │ │ ├── server-method.svg │ │ ├── simplified-oauth.monopic │ │ ├── simplified-oauth.pdf │ │ └── state-diagram.pdf │ │ ├── protocol.pdf │ │ └── protocol.rst ├── interactive1 │ ├── index.pug │ ├── menu.pug │ ├── proto │ │ └── tetris.proto │ ├── protocol.ascii │ └── protocol.pug ├── interactive_next │ ├── best-practices.pug │ ├── controls │ │ ├── basic-register.jsx │ │ ├── basic.jsx │ │ ├── hello-give-input.jsx │ │ ├── hello-inputs.jsx │ │ ├── scene-01.jsx │ │ ├── scene-register.jsx │ │ ├── style-example-01.jsx │ │ ├── style-example-02.jsx │ │ ├── style-example-03.jsx │ │ └── style-example-04.jsx │ ├── game-clients.pug │ ├── game-clients │ │ ├── data-from-custom-control.js │ │ ├── example-broadcast-event.json │ │ ├── example-participant-meta-unset.json │ │ ├── example-participant-meta.json │ │ ├── example-participant.json │ │ ├── example-scenes.json │ │ └── tutorial │ │ │ ├── go-live-method.js │ │ │ ├── handle-click-event.js │ │ │ ├── handle-update-1.js │ │ │ ├── handle-update-2.js │ │ │ └── sample-event-receive.txt │ ├── index.pug │ ├── installing-cdk-1.pug │ ├── installing-cdk-2.pug │ ├── internationalization │ │ ├── plural-example.json │ │ ├── score-counter-demo.jsx │ │ └── translation-demo.jsx │ ├── page.pug │ ├── quickstart-html.pug │ ├── quickstart-html │ │ ├── basic.js │ │ ├── make-a-change-01.html │ │ ├── make-a-change-02.js │ │ ├── make-a-change-03.js │ │ └── make-a-change-04.js │ ├── quickstart-preact.pug │ ├── workflow.pug │ └── writing-clients.pug ├── oauth │ └── index.pug ├── reference_layout.pug ├── teststreams │ └── index.pug └── webhooks │ ├── curl_request.sh │ ├── example_full_request.txt │ ├── example_hook.json │ ├── example_registered.json │ ├── index.pug │ ├── verify.cs │ ├── verify.go │ ├── verify.js.txt │ ├── verify.php │ └── verify.py ├── rest.pug ├── rest ├── resource.pug ├── template.pug └── type.pug ├── snippets └── help.pug └── tutorials ├── channelid.pug ├── chatbot.pug ├── code ├── java │ ├── chatbot │ │ ├── 1.java │ │ ├── 2.java │ │ ├── 3.java │ │ ├── 4.java │ │ ├── 5.java │ │ └── 6.java │ ├── interactive │ │ ├── 1.java │ │ ├── 2.java │ │ ├── 3.java │ │ └── 4.java │ └── rest │ │ ├── 1.java │ │ ├── 2.java │ │ ├── 3.java │ │ ├── 4.java │ │ ├── 5.java │ │ └── 5_response.txt ├── node │ ├── .eslintrc │ ├── chatbot │ │ ├── 1.js │ │ ├── 1_response.txt │ │ ├── 2.js │ │ ├── 2_response.txt │ │ └── 3.js │ ├── constellation │ │ ├── 1.js │ │ ├── 2.js │ │ ├── 3.js │ │ ├── 4.js │ │ ├── 5.js │ │ └── example_data.js │ ├── interactive │ │ ├── 1.js │ │ ├── 2.js │ │ ├── 2_response.json │ │ ├── 3.js │ │ ├── 4.js │ │ └── 5.js │ └── rest │ │ ├── 1.js │ │ ├── 2.js │ │ ├── 2_response.txt │ │ ├── 3.js │ │ ├── 4.js │ │ ├── 5.js │ │ └── 5_response.txt ├── python │ └── rest │ │ ├── 1.py │ │ ├── 2.py │ │ ├── 2_response.py │ │ ├── 3.py │ │ ├── 4.py │ │ └── 4_response.txt └── raw │ └── chat │ ├── 1.sh │ ├── 2.sh │ └── 3.sh ├── constellation.pug ├── interactive.pug ├── java_prereqs.pug ├── rest.pug ├── rest_intro.pug ├── run_config.pug ├── snippets ├── java │ ├── chat_project_manager.pug │ ├── gradle_chat_dependancy.gradle │ ├── gradle_chat_repo.gradle │ ├── gradle_interactive_dependancy.gradle │ ├── interactive_project_manager.pug │ ├── maven_chat_dependancy.xml │ ├── maven_chat_repo.xml │ └── maven_interactive_dependancy.xml └── oauth_introduction.pug └── tutorial_layout.pug /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | src/tutorials 4 | build/tmp 5 | internal 6 | external 7 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parserOptions": { 4 | "sourceType": "script" 5 | }, 6 | "rules": { 7 | "indent": [2, 4], 8 | "func-names": 0, 9 | "no-param-reassign": 0, 10 | "space-before-function-paren": [2, "always"], 11 | "strict": [2, "global"], 12 | "arrow-body-style": 0, 13 | "global-require": 0, 14 | "consistent-return": 0 15 | }, 16 | "globals": { 17 | "log": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | #### node #### 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # nyc test coverage 20 | .nyc_output 21 | 22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 23 | .grunt 24 | 25 | # node-waf configuration 26 | .lock-wscript 27 | 28 | # Compiled binary addons (http://nodejs.org/api/addons.html) 29 | build/Release 30 | 31 | # Dependency directories 32 | node_modules 33 | jspm_packages 34 | 35 | # Optional npm cache directory 36 | .npm 37 | 38 | # Optional REPL history 39 | .node_repl_history 40 | 41 | # Compiled assets 42 | /dist 43 | /src/css/_iconfont.less 44 | 45 | # projects 46 | *.sublime-project 47 | *.sublime-workspace 48 | .vscode 49 | 50 | .DS_Store 51 | 52 | .vscode 53 | 54 | #config files 55 | /config/* 56 | !/config/default.yaml 57 | /.vs 58 | /package-lock.json 59 | -------------------------------------------------------------------------------- /.pug-lintrc: -------------------------------------------------------------------------------- 1 | { 2 | "disallowAttributeConcatenation": true, 3 | "disallowClassAttributeWithStaticValue": true, 4 | "disallowClassLiteralsBeforeIdLiterals": true, 5 | "disallowDuplicateAttributes": true, 6 | "disallowHtmlText": true, 7 | "disallowMultipleLineBreaks": true, 8 | "disallowStringConcatenation": true, 9 | "requireClassLiteralsBeforeAttributes:": true, 10 | "requireIdLiteralsBeforeAttributes": true, 11 | "requireLowerCaseAttributes": true, 12 | "requireLowerCaseTags": true, 13 | "requireSpaceAfterCodeOperator": true, 14 | "requireSpecificAttributes": [{ 15 | "img": ["alt", "src"] 16 | }], 17 | "requireStrictEqualityOperators": true, 18 | "validateAttributeQuoteMarks": "'", 19 | "validateDivTags": true, 20 | "validateExtensions": true, 21 | "validateIndentation": 4, 22 | "validateSelfClosingTags": true, 23 | "validateAttributeSeparator": { 24 | "separator": " ", 25 | "multiLineSeparator": "\n " 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | properties([[$class: 'BuildDiscarderProperty', strategy: [$class: 'LogRotator', artifactNumToKeepStr: '5', numToKeepStr: '5']]]) 2 | 3 | def isMaster = env.BRANCH_NAME == "master" 4 | 5 | node { 6 | try { 7 | stage("Checkout") { 8 | checkout scm 9 | sh 'rm -rf internal external' 10 | } 11 | stage("Install") { 12 | sh 'npm install' 13 | } 14 | stage("Lint") { 15 | sh 'npm run lint -s' 16 | } 17 | stage("Build") { 18 | sh 'npm run build -s' 19 | sh 'mv dist external' 20 | } 21 | stage("Build internal") { 22 | sh 'npm run build:internal -s' 23 | sh 'mv dist internal' 24 | } 25 | stage("Archive artifacts") { 26 | sh "git rev-parse --short HEAD > git-commit-id" 27 | writeFile file: 'build-id', text: env.BUILD_NUMBER 28 | archiveArtifacts artifacts: "git-commit-id, build-id, external/**/*, internal/**/*", fingerprint: false 29 | } 30 | stage("Deploy internal") { 31 | if (isMaster) { 32 | ftpPublisher alwaysPublishFromMaster: false, continueOnError: false, failOnError: true, publishers: [ 33 | [ 34 | configName: 'internal_doc_site', 35 | transfers: [ [removePrefix: 'internal', sourceFiles: 'internal/**/*', excludes: ''] ] 36 | ] 37 | ] 38 | } 39 | } 40 | currentBuild.result = "SUCCESS" 41 | } catch(e) { 42 | currentBuild.result = "FAILURE" 43 | throw e 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Microsoft Corporation 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 | -------------------------------------------------------------------------------- /build/_iconfont.less: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: '<%= fontName %>'; 3 | src: url('<%= fontPath %><%= fontName %>.eot'); 4 | src: url('<%= fontPath %><%= fontName %>.eot?#iefix') format('eot'), 5 | url('<%= fontPath %><%= fontName %>.woff') format('woff'), 6 | url('<%= fontPath %><%= fontName %>.ttf') format('truetype'), 7 | url('<%= fontPath %><%= fontName %>.svg#<%= fontName %>') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | .iconFont() { 13 | font-family: '<%= fontName %>'; 14 | font-style: normal; 15 | font-weight: normal; 16 | line-height: 1; 17 | -webkit-font-smoothing: antialiased; 18 | -moz-osx-font-smoothing: grayscale; 19 | } 20 | 21 | .icon { 22 | .iconFont(); 23 | 24 | display: inline-flex; 25 | align-items: center; 26 | justify-content: center; 27 | width: 1rem; 28 | height: 1rem; 29 | position: relative; 30 | } 31 | 32 | .<%= className %>-2x { font-size: 2em; } 33 | .<%= className %>-3x { font-size: 3em; } 34 | .<%= className %>-4x { font-size: 4em; } 35 | .<%= className %>-5x { font-size: 5em; } 36 | 37 | <% _.each(glyphs, function(glyph) { %> 38 | .<%= className %>-<%= glyph.name %>-mixin() { content: '\<%= glyph.unicode[0].charCodeAt(0).toString(16).toUpperCase() %>' } 39 | .<%= className %>-<%= glyph.name %>:before { content: '\<%= glyph.unicode[0].charCodeAt(0).toString(16).toUpperCase() %>' } 40 | <% }); %> 41 | -------------------------------------------------------------------------------- /build/css.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('config'); 4 | const path = require('path'); 5 | 6 | /** 7 | * Registers a task that compiles 8 | * @param {Gulp} gulp 9 | * @param {Object} $ plugin loader 10 | * @param {Object} flags additional build flags 11 | * @return {Stream} 12 | */ 13 | module.exports = (gulp, $) => { 14 | /** 15 | * Task: `iconfont` 16 | * - pipe .svg files 17 | * - compile them to an iconfont 18 | * - generate css 19 | */ 20 | gulp.task('iconfont', () => { 21 | const fontName = 'beam'; 22 | return gulp.src(config.src.icons) 23 | .pipe($.iconfont({ 24 | fontName, 25 | normalize: true, 26 | formats: ['ttf', 'eot', 'woff', 'svg'], 27 | })) 28 | .on('glyphs', glyphs => { 29 | gulp.src(path.join(__dirname, '_iconfont.less')) 30 | .pipe($.consolidate('lodash', { 31 | glyphs, 32 | fontName, 33 | fontPath: '../font/', 34 | className: 'icon', 35 | })) 36 | .pipe(gulp.dest('src/css')); 37 | }) 38 | .pipe(gulp.dest(config.dist.font)); 39 | }); 40 | 41 | /** 42 | * Task: `css` 43 | * - pipe .less files 44 | * - compile with less 45 | * - autoprefix browser specific prefixes 46 | * - minify in production 47 | * - output 48 | */ 49 | gulp.task('css', ['iconfont'], () => { 50 | return gulp.src(config.src.css) 51 | .pipe($.less()) 52 | .pipe($.autoprefixer()) 53 | .pipe($.if(config.minify, $.cleanCss())) 54 | .pipe(gulp.dest(config.dist.css)); 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /build/libraries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "language": "java", 4 | "name": "Mixer/beam-client-java", 5 | "alias": "client-java", 6 | "official": true 7 | }, 8 | { 9 | "language": "js", 10 | "name": "Mixer/beam-client-node", 11 | "alias": "client-node", 12 | "official": true 13 | }, 14 | { 15 | "language": "python", 16 | "name": "Mixer/beam-client-python", 17 | "alias": "client-python", 18 | "official": true 19 | }, 20 | { 21 | "language": "swift", 22 | "name": "Mixer/beam-client-swift", 23 | "alias": "client-swift", 24 | "official": true 25 | }, 26 | { 27 | "language": "js", 28 | "name": "Mixer/interactive-node", 29 | "alias": "interactive-node", 30 | "official": true 31 | }, 32 | { 33 | "language": "java", 34 | "name": "Mixer/interactive-java", 35 | "alias": "interactive-java", 36 | "official": true 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /build/tmp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /build/util.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Creates a new object with all values from the passed object ordered by keys. 5 | * @param {Object} obj 6 | * @return {Object} 7 | */ 8 | exports.orderObject = function orderObject (obj) { 9 | const ret = {}; 10 | const orderedKeys = Object.keys(obj).sort(); 11 | for (const key of orderedKeys) { 12 | ret[key] = obj[key]; 13 | } 14 | return ret; 15 | }; 16 | -------------------------------------------------------------------------------- /config/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | !default.yaml 4 | -------------------------------------------------------------------------------- /config/default.yaml: -------------------------------------------------------------------------------- 1 | # Whether the resulting build should be minified, or not. 2 | minify: false 3 | 4 | # include internal APIs 5 | includeInternal: false 6 | 7 | # Source and corresponding destination paths for resources. You should 8 | # probably not change these unless you know what you're doing: 9 | src: 10 | tmp: build/tmp 11 | css: src/css/style.less 12 | js: 13 | - node_modules/sticky-kit/dist/sticky-kit.js 14 | - node_modules/bootstrap/dist/js/bootstrap.js 15 | - src/js/*.js 16 | html: src/**/*.pug 17 | snippets: src/**/*.{java,python,js,json} 18 | icons: src/icons/*.svg 19 | images: src/**/*.{svg,png,jpg,gif,pdf} 20 | 21 | dist: 22 | css: dist/css 23 | js: dist/js 24 | html: dist 25 | font: dist/font 26 | images: dist 27 | javadoc: dist/java-doc 28 | 29 | # Location of locally stored repositories. If these are not enabled, we'll 30 | # try to clone them into a temp directory for building. 31 | # repos: 32 | # backend: ../backend 33 | # beam-client-java: ../beam-client-java 34 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | const config = require('config'); 6 | const gulp = require('gulp'); 7 | const $ = require('gulp-load-plugins')(); 8 | 9 | const flags = { 10 | internal: false, 11 | }; 12 | 13 | require('./build/docs')(gulp, $, flags); 14 | require('./build/misc')(gulp, $, flags); 15 | require('./build/css')(gulp, $, flags); 16 | 17 | const defaultTasks = ['html', 'js', 'css', 'images']; 18 | 19 | gulp.task('default', defaultTasks); 20 | gulp.task('internal', ['set-internal', ...defaultTasks]); 21 | gulp.task('recompile', ['html-quick', 'js', 'css', 'images']); 22 | 23 | gulp.task('watch', () => { 24 | gulp.watch('src/css/**/*.less', ['css']); 25 | gulp.watch(config.src.html, ['html-quick']); 26 | gulp.watch(config.src.snippets, ['html-quick']); 27 | gulp.watch(config.src.js, ['js']); 28 | if (config.repos && config.repos.backend) { 29 | gulp.watch(path.join(config.repos.backend, '**/*.raml'), ['html-raml']); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "beam-developers", 3 | "version": "2.0.0", 4 | "description": "Centralized hub for all things related to Beam development", 5 | "main": "gulp", 6 | "scripts": { 7 | "lint:js": "eslint .", 8 | "lint:pug": "pug-lint src", 9 | "lint:json": "gulp lint-json", 10 | "lint": "npm-run-all --parallel lint:js lint:pug lint:json", 11 | "test": "npm run lint", 12 | "build": "gulp", 13 | "build:internal": "gulp internal", 14 | "rebuild": "gulp recompile", 15 | "start": "http-server -p 8000 dist", 16 | "watch": "gulp watch" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/mixer/developers.git" 21 | }, 22 | "author": "Beam Interactive, Inc.", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/mixer/developers/issues" 26 | }, 27 | "homepage": "https://github.com/mixer/developers#readme", 28 | "devDependencies": { 29 | "bluebird": "^3.4.1", 30 | "bootstrap": "^3.3.7", 31 | "config": "^1.21.0", 32 | "del": "^2.2.0", 33 | "eslint": "^2.13.0", 34 | "eslint-config-airbnb": "^9.0.1", 35 | "eslint-plugin-import": "^1.9.2", 36 | "eslint-plugin-jsx-a11y": "^1.5.3", 37 | "eslint-plugin-react": "^5.2.2", 38 | "gulp": "^3.9.1", 39 | "gulp-autoprefixer": "^3.1.0", 40 | "gulp-clean-css": "^2.0.10", 41 | "gulp-concat": "^2.6.0", 42 | "gulp-consolidate": "^0.2.0", 43 | "gulp-data": "^1.2.1", 44 | "gulp-iconfont": "^8.0.0", 45 | "gulp-if": "^2.0.1", 46 | "gulp-imagemin": "^3.0.1", 47 | "gulp-less": "^3.1.0", 48 | "gulp-load-plugins": "^1.2.4", 49 | "gulp-minify-html": "^1.0.6", 50 | "gulp-pug": "^3.3.0", 51 | "gulp-uglify": "^3.0.0", 52 | "highlight.js": "^9.5.0", 53 | "http-server": "^0.9.0", 54 | "js-yaml": "^3.11.0", 55 | "lodash": "^4.13.1", 56 | "marked": "^0.3.5", 57 | "node-fetch": "^1.5.3", 58 | "npm-run-all": "^4.0.2", 59 | "pug-lint": "^2.2.2", 60 | "raml-1-parser": "^0.2.33", 61 | "sticky-kit": "^1.1.3" 62 | }, 63 | "optionalDependencies": { 64 | "@mcph/beam-common": "^5.0.7" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Developers [![Join the chat at https://gitter.im/Mixer/developers](https://badges.gitter.im/Mixer/developers.svg)](https://gitter.im/Mixer/developers?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 2 | 3 | # Legacy Project used to generate contents of the [Developer Documentation](https://dev.mixer.com). 4 | 5 | Not currently in use. 6 | 7 | ## Requirements 8 | 9 | - [Gulp](http://gulpjs.com/) 10 | - [node](https://nodejs.org/en/) 11 | - Git 12 | 13 | ## Config 14 | 15 | We use `node-config`, please refer to `config/default.yaml` for more info. 16 | 17 | In order to use local repositories (such as backend for fast raml doc preview). 18 | You need to specify the `repos` section in the config such as this: 19 | 20 | ```yaml 21 | repos: 22 | backend: ../backend 23 | ``` 24 | 25 | ## Build Scripts 26 | 27 | Run `npm run build` for a full build. 28 | 29 | Run `gulp recompile` for a build without JavaDoc and RAMLDoc. This will also work for 3rd parties looking to contribute. 30 | 31 | Run `gulp watch` for development. 32 | 33 | Run `npm start` to start a local server at port 8000. 34 | -------------------------------------------------------------------------------- /src/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "jquery": true, 5 | "es6": false 6 | }, 7 | "rules": { 8 | "no-var": 0, 9 | "vars-on-top": 0, 10 | "prefer-arrow-callback": 0, 11 | "prefer-template": 0, 12 | "object-shorthand": 0 13 | }, 14 | } 15 | -------------------------------------------------------------------------------- /src/backendConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | ratelimit: { 4 | buckets: { 5 | global: { 6 | max: 1000, 7 | interval: 60 * 1000, 8 | codes: ['xxx'], 9 | }, 10 | analytics: { 11 | max: 100, 12 | interval: 60 * 1000, 13 | }, 14 | 'channel-read': { 15 | max: 1000, 16 | interval: 5 * 60 * 1000, 17 | }, 18 | 'channel-search': { 19 | max: 20, 20 | interval: 5 * 1000, 21 | }, 22 | 'channel-write': { 23 | max: 250, 24 | interval: 5 * 60 * 1000, 25 | }, 26 | 'channel-follow': { 27 | max: 100, 28 | interval: 60 * 1000, 29 | }, 30 | upload: { 31 | max: 5, 32 | interval: 10 * 60 * 1000, 33 | }, 34 | 'upload-interactive': { 35 | max: 15, 36 | interval: 10 * 30 * 1000, 37 | }, 38 | 'user-read': { 39 | max: 500, 40 | interval: 60 * 1000, 41 | }, 42 | 'user-write': { 43 | max: 100, 44 | interval: 60 * 1000, 45 | }, 46 | 'user-login': { 47 | max: 50, 48 | interval: 60 * 1000, 49 | }, 50 | 'user-login-failed': { 51 | max: 8, 52 | interval: 15 * 60 * 1000, 53 | codes: ['401'], 54 | }, 55 | 'user-email': { 56 | max: 2, 57 | interval: 24 * 60 * 60 * 1000, 58 | }, 59 | 'notification-read': { 60 | max: 100, 61 | interval: 60 * 1000, 62 | }, 63 | chats: { 64 | max: 500, 65 | interval: 60 * 1000, 66 | }, 67 | report: { 68 | max: 10, 69 | interval: 60 * 1000, 70 | }, 71 | contact: { 72 | max: 3, 73 | interval: 60 * 1000, 74 | }, 75 | ingest: { 76 | max: 5, 77 | interval: 60 * 1000, 78 | }, 79 | 'mail-subscribe': { 80 | max: 3, 81 | interval: 60 * 1000, 82 | }, 83 | }, 84 | }, 85 | }; 86 | -------------------------------------------------------------------------------- /src/css/_book.less: -------------------------------------------------------------------------------- 1 | @import '_mixins'; 2 | 3 | .book { 4 | &-primary { 5 | background: #fff; 6 | 7 | -webkit-text-size-adjust: 100%; 8 | -webkit-font-smoothing: antialiased; 9 | 10 | p { 11 | font-size: 1.1em; 12 | line-height: 2em; 13 | } 14 | 15 | figure { 16 | margin: @grid-gutter-width 0; 17 | text-align: center; 18 | 19 | img { 20 | max-width: 100%; 21 | } 22 | } 23 | 24 | figure figcaption { 25 | font-size: 0.9em; 26 | color: #666; 27 | font-style: italic; 28 | line-height: 2em; 29 | margin-top: 1em; 30 | } 31 | } 32 | 33 | &-sidebar { 34 | position: relative; 35 | 36 | .sidebar-variant(#000); 37 | 38 | @media (max-width: @screen-md-min) { 39 | display: block; 40 | position: relative !important; 41 | top: 0 !important; 42 | width: auto !important; 43 | 44 | &::before { 45 | left: @grid-gutter-width / -2; 46 | top: -@grid-gutter-width; 47 | bottom: @grid-gutter-width / -2; 48 | width: 100vw; 49 | height: auto; 50 | } 51 | } 52 | } 53 | 54 | &-nav { 55 | padding: 8px; 56 | border: 1px solid @gray-lighter; 57 | width: 7em; 58 | text-align: center; 59 | 60 | &:hover { 61 | background: @gray-lighter; 62 | } 63 | 64 | &-next { 65 | align-self: flex-end; 66 | } 67 | } 68 | 69 | &-page-title { 70 | display: flex; 71 | align-items: center; 72 | margin: (@grid-gutter-width * 1.5) 0; 73 | 74 | h1 { 75 | flex-grow: 1; 76 | text-align: center; 77 | margin: 0; 78 | } 79 | } 80 | 81 | &-page-footer { 82 | .bar-transition-top(5px); 83 | 84 | display: flex; 85 | align-items: center; 86 | padding-top: @grid-gutter-width; 87 | margin-top: @grid-gutter-width; 88 | position: relative; 89 | border-top: 2px solid @gray-lighter; 90 | 91 | span { 92 | flex-grow: 1; 93 | text-align: center; 94 | color: #999; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/css/_footer.less: -------------------------------------------------------------------------------- 1 | .site-footer { 2 | .bar-transition-top(); 3 | margin-top: @grid-gutter-width; 4 | background: @gray-lighter; 5 | position: relative; 6 | 7 | ul { 8 | padding: 0; 9 | margin: 0 0 @grid-gutter-width; 10 | } 11 | 12 | li { 13 | list-style-type: none; 14 | padding: 0.2em; 15 | 16 | a { 17 | color: #666; 18 | 19 | &:hover { 20 | text-decoration: underline; 21 | } 22 | } 23 | } 24 | 25 | .footer-logo { 26 | display: block; 27 | margin-top: 40px; 28 | max-width: 100%; 29 | 30 | img { 31 | width: 100%; 32 | } 33 | } 34 | 35 | .copyright { 36 | color: #aaa; 37 | font-size: 0.8em; 38 | margin: 0 0 @grid-gutter-width / 2; 39 | text-align: center; 40 | 41 | a { 42 | color: #aaa; 43 | text-decoration: underline; 44 | 45 | &:hover { 46 | text-decoration: none; 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/css/_highlight.less: -------------------------------------------------------------------------------- 1 | // SOURCE: https://raw.githubusercontent.com/isagalaev/highlight.js/master/src/styles/androidstudio.css 2 | 3 | /* 4 | Copyright (c) 2006, Ivan Sagalaev 5 | All rights reserved. 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | * Neither the name of highlight.js nor the names of its contributors 15 | may be used to endorse or promote products derived from this software 16 | without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY 19 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY 22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | */ 29 | 30 | /* 31 | 32 | github.com style (c) Vasily Polovnyov 33 | 34 | */ 35 | 36 | .hljs { 37 | display: block; 38 | overflow-x: auto; 39 | padding: 0.5em; 40 | color: #333; 41 | background: #f8f8f8; 42 | } 43 | 44 | .hljs-comment, 45 | .hljs-quote { 46 | color: #998; 47 | font-style: italic; 48 | } 49 | 50 | .hljs-keyword, 51 | .hljs-selector-tag, 52 | .hljs-subst { 53 | color: #333; 54 | font-weight: bold; 55 | } 56 | 57 | .hljs-number, 58 | .hljs-literal, 59 | .hljs-variable, 60 | .hljs-template-variable, 61 | .hljs-tag .hljs-attr { 62 | color: #008080; 63 | } 64 | 65 | .hljs-string, 66 | .hljs-doctag { 67 | color: #d14; 68 | } 69 | 70 | .hljs-title, 71 | .hljs-section, 72 | .hljs-selector-id { 73 | color: #900; 74 | font-weight: bold; 75 | } 76 | 77 | .hljs-subst { 78 | font-weight: normal; 79 | } 80 | 81 | .hljs-type, 82 | .hljs-class .hljs-title { 83 | color: #458; 84 | font-weight: bold; 85 | } 86 | 87 | .hljs-tag, 88 | .hljs-name, 89 | .hljs-attribute { 90 | color: #000080; 91 | font-weight: normal; 92 | } 93 | 94 | .hljs-regexp, 95 | .hljs-link { 96 | color: #009926; 97 | } 98 | 99 | .hljs-symbol, 100 | .hljs-bullet { 101 | color: #990073; 102 | } 103 | 104 | .hljs-built_in, 105 | .hljs-builtin-name { 106 | color: #0086b3; 107 | } 108 | 109 | .hljs-meta { 110 | color: #999; 111 | font-weight: bold; 112 | } 113 | 114 | .hljs-deletion { 115 | background: #fdd; 116 | } 117 | 118 | .hljs-addition { 119 | background: #dfd; 120 | } 121 | 122 | .hljs-emphasis { 123 | font-style: italic; 124 | } 125 | 126 | .hljs-strong { 127 | font-weight: bold; 128 | } 129 | 130 | .token { 131 | background-color: #DDD; 132 | cursor: pointer; 133 | color: inherit; 134 | padding: 5px; 135 | } 136 | 137 | .token:hover { 138 | color: inherit; 139 | background-color: #CCC; 140 | } 141 | -------------------------------------------------------------------------------- /src/css/_intro.less: -------------------------------------------------------------------------------- 1 | @keyframes 'blink' { 2 | from, to { background: transparent; } 3 | 50% { background: #fff; } 4 | } 5 | 6 | .intro { 7 | margin: 40px 0 90px; 8 | 9 | h1 { 10 | font-family: @headings-font-family; 11 | color: #fff; 12 | font-size: 3.5em; 13 | letter-spacing: 2px; 14 | 15 | &:after { 16 | content: ""; 17 | width: 2px; 18 | height: 1em; 19 | display: inline-block; 20 | position: relative; 21 | top: 0.15em; 22 | animation: 1s blink step-end infinite; 23 | } 24 | } 25 | 26 | p { 27 | color: #fff; 28 | font-size: 1.2em; 29 | line-height: 2em; 30 | margin: 1em 0 1.5em; 31 | font-weight: 300; 32 | } 33 | 34 | .btn { 35 | margin-right: 1em; 36 | } 37 | 38 | @media (max-width: @screen-xs-min) { 39 | .btn { 40 | display: block; 41 | margin: 0 0 (@grid-gutter-width / 2) 0; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/css/_layout.less: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | min-height: 100vh; 4 | flex-direction: column; 5 | } 6 | 7 | .site-content { 8 | flex: 1 0 auto; 9 | } 10 | 11 | .navbar-wrapper { 12 | .bar-transition-bottom(10px); 13 | background: @brand-primary; 14 | 15 | &:after { 16 | margin-top: 0 !important; 17 | } 18 | 19 | .navbar { 20 | padding: 10px 0; 21 | background: none; 22 | } 23 | 24 | .navbar-nav > li > a { 25 | position: relative; 26 | z-index: 0; 27 | font-family: 'Heebo'; 28 | font-size: 1.1em; 29 | padding-left: @grid-gutter-width * 2 / 3; 30 | padding-right: @grid-gutter-width * 2 / 3; 31 | 32 | &:after { 33 | content: ""; 34 | position: absolute; 35 | z-index: -1; 36 | left: 0; 37 | top: 0; 38 | right: 0; 39 | bottom: 0; 40 | background: #fff; 41 | transform: scale(0.8, 0.6); 42 | transition: transform 0.1s; 43 | visibility: hidden; 44 | 45 | @media (max-width: @screen-xs-max) { 46 | transform: scale(1, 0.6); 47 | } 48 | } 49 | 50 | &:hover:after { 51 | visibility: visible; 52 | } 53 | } 54 | 55 | .navbar-nav > li.dropdown.open > a, 56 | .navbar-nav > li > a:active { 57 | color: #000; 58 | 59 | &:after { 60 | visibility: visible; 61 | transform: scale(1, 1); 62 | } 63 | 64 | } 65 | 66 | .navbar-brand { 67 | background: url('../img/logo.png') center center no-repeat; 68 | background-size: contain; 69 | min-width: 200px; 70 | } 71 | 72 | .dropdown.open .dropdown-menu { 73 | @keyframes dropdown { 74 | from { opacity: 0; transform: translateY(-10px); } 75 | to { opacity: 1; transform: translateY(0px); } 76 | } 77 | 78 | opacity: 0; 79 | border: transparent; 80 | animation-delay: 0.2s; 81 | animation-duration: 0.2s; 82 | animation-name: dropdown; 83 | animation-fill-mode: forwards; 84 | } 85 | 86 | h1 { 87 | color: #fff; 88 | margin-bottom: @grid-gutter-width; 89 | 90 | small { 91 | color: fade(#fff, 50%); 92 | } 93 | } 94 | 95 | &.dark { 96 | .bar-transition-bottom(15px); 97 | background: url('../img/intro.png') top center; 98 | background-size: cover; 99 | } 100 | 101 | .navbar-toggle { 102 | padding-bottom: 5px; 103 | color: #fff; 104 | margin-right: 0; 105 | 106 | &:hover, &:focus { 107 | color: #000; 108 | } 109 | } 110 | } 111 | 112 | pre { 113 | position: relative; 114 | background: @gray-lighter; 115 | border: none; 116 | overflow: visible; 117 | font-family: monospace; 118 | 119 | td > & { 120 | margin-left: 15px; 121 | } 122 | 123 | &:before { 124 | content: ""; 125 | position: absolute; 126 | right: 100%; 127 | top: 0; 128 | bottom: 0; 129 | width: 15px; 130 | display: block; 131 | background: url('../img/line-pattern.png') @gray-lighter; 132 | } 133 | } 134 | 135 | .bar-transition(@size) { 136 | content: ""; 137 | display: block; 138 | height: @size; 139 | background: url('../img/line-pattern.png'); 140 | } 141 | 142 | .bar-transition-top(@size: 30px) { 143 | &:before { 144 | .bar-transition(@size); 145 | margin-bottom: @grid-gutter-width; 146 | } 147 | } 148 | 149 | .bar-transition-bottom(@size: 30px) { 150 | &:after { 151 | .bar-transition(@size); 152 | margin-top: @grid-gutter-width; 153 | } 154 | } 155 | 156 | .bar-transition-top { .bar-transition-top(); } 157 | .bar-transition-bottom { .bar-transition-bottom(); } 158 | -------------------------------------------------------------------------------- /src/css/_mixins.less: -------------------------------------------------------------------------------- 1 | .sidebar-variant(@color) { 2 | li { 3 | a { 4 | &:hover { 5 | color: @color; 6 | 7 | &:before { 8 | background-color: @color; 9 | } 10 | } 11 | } 12 | 13 | &.active, 14 | &.active-locked { 15 | > a { 16 | border-left-color: @color; 17 | color: @color; 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/css/_slide.less: -------------------------------------------------------------------------------- 1 | .slide { 2 | margin: (@grid-gutter-width * 2) 0; 3 | 4 | &.gray { background: #eee; } 5 | &.bar-transition-top { margin-top: @grid-gutter-width; } 6 | &.bar-transition-bottom { margin-bottom: @grid-gutter-width; } 7 | 8 | .slide-title { 9 | font-size: 62px; 10 | letter-spacing: -2px; 11 | margin-bottom: @grid-gutter-width; 12 | 13 | .muted { 14 | color: #ccc; 15 | } 16 | } 17 | 18 | 19 | @media (max-width: @screen-sm-min) { 20 | .slide-title { 21 | text-align: center; 22 | 23 | .muted { 24 | display: block; 25 | font-size: 0.6em; 26 | } 27 | } 28 | } 29 | } 30 | 31 | .clickable-box() { 32 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); 33 | transition: 0.2s; 34 | cursor: pointer; 35 | 36 | &:hover { 37 | text-decoration: none; 38 | transform: translateY(1px); 39 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2); 40 | } 41 | } 42 | 43 | .slide-features { 44 | text-align: center; 45 | 46 | a { 47 | .clickable-box(); 48 | padding: @grid-gutter-width 0; 49 | display: block; 50 | height: initial; 51 | font-size: 1.2em; 52 | font-weight: 300; 53 | color: @brand-primary; 54 | background: #fafafa; 55 | margin: (@grid-gutter-width / 2) 0; 56 | } 57 | 58 | &-img { 59 | @size: 110px; 60 | width: @size; 61 | height: @size; 62 | margin: 0 auto @grid-gutter-width / 2; 63 | display: flex; 64 | justify-content: center; 65 | align-items: center; 66 | 67 | img { 68 | width: 80%; 69 | max-height: 80%; 70 | } 71 | } 72 | } 73 | 74 | .slide-grab { 75 | &-left { 76 | position: relative; 77 | z-index: 0; 78 | display: flex; 79 | flex-direction: column; 80 | } 81 | 82 | &-devbot { 83 | position: relative; 84 | margin: @grid-gutter-width * 2 0 -@grid-gutter-width 0; 85 | 86 | img { 87 | position: relative; 88 | width: 100%; 89 | } 90 | 91 | &:before { 92 | content: ""; 93 | position: absolute; 94 | bottom: 0; 95 | top: 0; 96 | right: 0; 97 | width: 200%; 98 | display: block; 99 | z-index: -1; 100 | } 101 | } 102 | } 103 | 104 | .slide-library { 105 | .clickable-box(); 106 | 107 | position: relative; 108 | display: flex; 109 | flex-direction: row; 110 | align-items: center; 111 | padding: @grid-gutter-width / 2; 112 | margin-bottom: @grid-gutter-width; 113 | background: #fff; 114 | 115 | &-icon { 116 | > img { 117 | width: 80px; 118 | height: 80px; 119 | } 120 | } 121 | 122 | &-name { 123 | margin-left: @grid-gutter-width / 2; 124 | flex-grow: 1; 125 | 126 | h3 { 127 | white-space: nowrap; 128 | margin: 0; 129 | } 130 | 131 | small { 132 | display: block; 133 | font-size: 0.5em; 134 | } 135 | } 136 | 137 | &-meta { 138 | position: absolute; 139 | right: @grid-gutter-width / 3; 140 | bottom: @grid-gutter-width / 4; 141 | line-height: 1.2em; 142 | 143 | span { 144 | display: inline-block; 145 | margin: 0 0.5em 0 0.25em; 146 | height: 0.9em; 147 | color: #999; 148 | } 149 | 150 | img { 151 | height: 1em; 152 | } 153 | } 154 | } 155 | 156 | .slide-focal { 157 | text-align: center; 158 | font-size: 1.5em; 159 | font-weight: 300; 160 | max-width: 800px; 161 | margin: 10px auto 30px; 162 | } 163 | -------------------------------------------------------------------------------- /src/css/_type.less: -------------------------------------------------------------------------------- 1 | h1, h2, h3, h4, h5, h6 { 2 | font-family: @headings-font-family; 3 | font-weight: @headings-font-weight; 4 | color: lighten(@brand-secondary, 10%); 5 | 6 | b { 7 | font-weight: 300; 8 | } 9 | } 10 | 11 | h1, h2 { 12 | margin-top: 30px; 13 | } 14 | 15 | p { 16 | margin-bottom: 15px; 17 | } 18 | 19 | .text-monospace { 20 | font-family: @font-family-monospace; 21 | } 22 | 23 | pre { 24 | white-space: pre-wrap; 25 | } 26 | 27 | a > code { 28 | color: @brand-primary; 29 | background: fade(#000, 5%); 30 | } 31 | -------------------------------------------------------------------------------- /src/css/style.less: -------------------------------------------------------------------------------- 1 | // Core variables and mixins 2 | @import '../../node_modules/bootstrap/less/mixins.less'; 3 | 4 | // Reset and dependencies 5 | @import '../../node_modules/bootstrap/less/normalize.less'; 6 | @import '../../node_modules/bootstrap/less/print.less'; 7 | 8 | // Core CSS 9 | @import '../../node_modules/bootstrap/less/scaffolding.less'; 10 | @import '../../node_modules/bootstrap/less/type.less'; 11 | @import '../../node_modules/bootstrap/less/code.less'; 12 | @import '../../node_modules/bootstrap/less/grid.less'; 13 | @import '../../node_modules/bootstrap/less/tables.less'; 14 | @import '../../node_modules/bootstrap/less/buttons.less'; 15 | @import '../../node_modules/bootstrap/less/forms.less'; 16 | 17 | // Components 18 | @import '../../node_modules/bootstrap/less/component-animations.less'; 19 | @import '../../node_modules/bootstrap/less/dropdowns.less'; 20 | // @import '../../node_modules/bootstrap/less/button-groups.less'; 21 | // @import '../../node_modules/bootstrap/less/input-groups.less'; 22 | @import '../../node_modules/bootstrap/less/navs.less'; 23 | @import '../../node_modules/bootstrap/less/navbar.less'; 24 | // @import '../../node_modules/bootstrap/less/breadcrumbs.less'; 25 | // @import '../../node_modules/bootstrap/less/pagination.less'; 26 | // @import '../../node_modules/bootstrap/less/pager.less'; 27 | // @import '../../node_modules/bootstrap/less/labels.less'; 28 | // @import '../../node_modules/bootstrap/less/badges.less'; 29 | // @import '../../node_modules/bootstrap/less/jumbotron.less'; 30 | // @import '../../node_modules/bootstrap/less/thumbnails.less'; 31 | @import '../../node_modules/bootstrap/less/alerts.less'; 32 | // @import '../../node_modules/bootstrap/less/progress-bars.less'; 33 | // @import '../../node_modules/bootstrap/less/media.less'; 34 | // @import '../../node_modules/bootstrap/less/list-group.less'; 35 | @import '../../node_modules/bootstrap/less/panels.less'; 36 | // @import '../../node_modules/bootstrap/less/responsive-embed.less'; 37 | // @import '../../node_modules/bootstrap/less/wells.less'; 38 | @import '../../node_modules/bootstrap/less/close.less'; 39 | 40 | // Components w/ JavaScript 41 | @import '../../node_modules/bootstrap/less/modals.less'; 42 | // @import '../../node_modules/bootstrap/less/tooltip.less'; 43 | // @import '../../node_modules/bootstrap/less/popovers.less'; 44 | // @import '../../node_modules/bootstrap/less/carousel.less'; 45 | 46 | // Utility classes 47 | @import '../../node_modules/bootstrap/less/utilities.less'; 48 | @import '../../node_modules/bootstrap/less/responsive-utilities.less'; 49 | 50 | @import '_bootstrap'; 51 | @import '_footer'; 52 | @import '_highlight'; 53 | @import '_iconfont'; 54 | @import '_intro'; 55 | @import '_layout'; 56 | @import '_slide'; 57 | @import '_rest'; 58 | @import '_type'; 59 | @import '_book'; 60 | @import '_variables'; 61 | 62 | 63 | .caption { 64 | font-style: italic; 65 | color:#999; 66 | } 67 | -------------------------------------------------------------------------------- /src/icons/link-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/lock-3.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/logo-github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/icons/minus-5.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/share-8.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/time-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/view-headline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/DevBot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/DevBot.png -------------------------------------------------------------------------------- /src/img/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /src/img/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /src/img/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/favicon-16x16.png -------------------------------------------------------------------------------- /src/img/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/favicon-32x32.png -------------------------------------------------------------------------------- /src/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/favicon.ico -------------------------------------------------------------------------------- /src/img/footer-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/footer-logo.png -------------------------------------------------------------------------------- /src/img/icon/LICENSE: -------------------------------------------------------------------------------- 1 | Several of the icons in this folder are from https://github.com/konpa/devicon, 2 | which is provided under the following license. 3 | 4 | All product names, logos, and brandsare property of their respective owners. 5 | All company, product and service names used in this website are for 6 | identification purposes only. Use of these names, logos, 7 | and brands does not imply endorsement. 8 | 9 | -------------------------------------------------------------------------------- 10 | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2015 konpa 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of 16 | this software and associated documentation files (the "Software"), to deal in 17 | the Software without restriction, including without limitation the rights to 18 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 19 | the Software, and to permit persons to whom the Software is furnished to do so, 20 | subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 27 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 28 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 29 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 30 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | 32 | ================================================================================ 33 | 34 | Those not licensed under the above are licensed under Streamline 35 | Icons' standard license, licensed to and used solely by Connor Peet: 36 | http://www.streamlineicons.com/license.html 37 | -------------------------------------------------------------------------------- /src/img/icon/book-open-2.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/bubble-chat-smiley-1.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/bubble-chat-typing-3.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/chat-double-bubble-1.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/computer-chip.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/download-circle.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/icon/game-controller-2.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/lang-haxe.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/img/icon/lang-java.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/img/icon/lang-js.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/icon/lang-python.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/lang-swift.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/logo-github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/logo-twitter-bird.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/img/icon/rank-army-star-1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/icon/rocket.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/img/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/intro.png -------------------------------------------------------------------------------- /src/img/line-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/line-pattern.png -------------------------------------------------------------------------------- /src/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/logo.png -------------------------------------------------------------------------------- /src/img/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/mstile-144x144.png -------------------------------------------------------------------------------- /src/img/reference/interactive/ProjectLifecycle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Page-1 37 | 39 | 40 | 41 | 42 | Rectangle 43 | Build 44 | 45 | 46 | 47 | 48 | 49 | 50 | Build 51 | 52 | Rectangle.1004 53 | Test 54 | 55 | 56 | 57 | 58 | 59 | 60 | Test 61 | 62 | Dynamic connector.1007 63 | 64 | 65 | 66 | Rectangle.1008 67 | Publish 68 | 69 | 70 | 71 | 72 | 73 | 74 | Publish 75 | 76 | Dynamic connector.1010 77 | 78 | 79 | 80 | Dynamic connector.1011 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/img/reference/interactive/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/button.png -------------------------------------------------------------------------------- /src/img/reference/interactive/cdk/cdkHTML.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/cdk/cdkHTML.png -------------------------------------------------------------------------------- /src/img/reference/interactive/cdk/cdkOverview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/cdk/cdkOverview.png -------------------------------------------------------------------------------- /src/img/reference/interactive/cdk/cdkPreact.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/cdk/cdkPreact.png -------------------------------------------------------------------------------- /src/img/reference/interactive/cdk/cdkStartScreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/cdk/cdkStartScreen.png -------------------------------------------------------------------------------- /src/img/reference/interactive/joystick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/joystick.png -------------------------------------------------------------------------------- /src/img/reference/interactive/joystickCoordinates.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 | Page-1 25 | 27 | 28 | Circle 29 | 30 | 31 | 32 | 33 | 34 | 35 | Sheet.3 36 | 37 | 38 | 39 | Sheet.4 40 | 41 | 42 | 43 | Circle.6 44 | 45 | 46 | 47 | 48 | 49 | 50 | Circle.7 51 | 52 | 53 | 54 | 55 | 56 | 57 | Circle.8 58 | 59 | 60 | 61 | 62 | 63 | 64 | Sheet.9 65 | 0, 0 66 | 67 | 68 | 69 | 0, 0 70 | 71 | Sheet.10 72 | 1, 1 73 | 74 | 75 | 76 | 1, 1 77 | 78 | Sheet.11 79 | -1, -1 80 | 81 | 82 | 83 | -1, -1 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/img/reference/interactive/label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/label.png -------------------------------------------------------------------------------- /src/img/reference/interactive/link-demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/link-demo.gif -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/PublishProcess.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Page-1 37 | 39 | 40 | 41 | 42 | Rectangle 43 | Draft 44 | 45 | 46 | 47 | 48 | 49 | 50 | Draft 51 | 52 | Rectangle.1004 53 | In Review 54 | 55 | 56 | 57 | 58 | 59 | 60 | In Review 61 | 62 | Dynamic connector.1007 63 | 64 | 65 | 66 | Rectangle.1008 67 | Published 68 | 69 | 70 | 71 | 72 | 73 | 74 | Published 75 | 76 | Dynamic connector.1010 77 | 78 | 79 | 80 | Dynamic connector.1011 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/controls.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/createNewProject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/createNewProject.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/editorTabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/editorTabs.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/editors/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/editors/add.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/editors/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/editors/button.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/editors/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/editors/list.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/grid_with_controls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/grid_with_controls.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/scenes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/scenes.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/share/explicitSharing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/share/explicitSharing.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/share/shareButton.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/share/shareButton.jpg -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/share/shareButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/share/shareButton.png -------------------------------------------------------------------------------- /src/img/reference/interactive/studio/share/shareCode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/studio/share/shareCode.png -------------------------------------------------------------------------------- /src/img/reference/interactive/textbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/interactive/textbox.png -------------------------------------------------------------------------------- /src/img/reference/teststreams/enable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/reference/teststreams/enable.png -------------------------------------------------------------------------------- /src/img/tutorials/interactive/joystick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/tutorials/interactive/joystick.png -------------------------------------------------------------------------------- /src/img/tutorials/rest/java/runconfig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/img/tutorials/rest/java/runconfig.png -------------------------------------------------------------------------------- /src/js/modals.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | // open modal on hashes like #_action_get 5 | var activeModalEl = null; 6 | $(window).bind('hashchange', function () { 7 | if (activeModalEl) { 8 | activeModalEl.modal('hide'); 9 | } 10 | 11 | var anchorId = document.location.hash.substr(1); // strip # 12 | if (!anchorId) { 13 | return; 14 | } 15 | // Don't do this when we're retrieving an OAuth Token 16 | if (anchorId.indexOf('access_token') !== -1) { 17 | return; 18 | } 19 | 20 | var element = $('#' + anchorId); 21 | 22 | // do we have such element + is it a modal? --> show it 23 | if (element.length && element.hasClass('modal')) { 24 | element.modal('show'); 25 | activeModalEl = element; 26 | } 27 | // for links to pages we should "flash" the highlighted region 28 | var panel = element.closest('.panel'); 29 | if (panel.length > 0) { 30 | panel.addClass('panel-flash'); 31 | 32 | setTimeout(function () { 33 | panel.removeClass('panel-flash'); 34 | }, 3000); 35 | } 36 | }); 37 | 38 | // execute hashchange on first page load 39 | $(window).trigger('hashchange'); 40 | 41 | $('.modal').on('hidden.bs.modal', function () { 42 | try { 43 | if (history && history.replaceState) { 44 | history.replaceState({}, '', '#'); 45 | } 46 | } catch (e) { 47 | /* Do Nothing */ 48 | } 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/js/names.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var nouns = [ 4 | 'chicken', 5 | 'walrus', 6 | 'horse', 7 | 'cat', 8 | 'dog', 9 | 'mouse', 10 | 'hippo', 11 | 'elephant', 12 | 'potato', 13 | 'giraffe', 14 | 'cow', 15 | 'pig', 16 | 'deer', 17 | 'rabbit', 18 | 'dolphin', 19 | 'hamster', 20 | 'bear', 21 | 'antelope', 22 | 'squirrel', 23 | 'frog', 24 | ]; 25 | 26 | var adjectives = [ 27 | 'adventurous', 28 | 'quarrelsome', 29 | 'exhausted', 30 | 'excited', 31 | 'fabulous', 32 | 'nimble', 33 | 'jazzy', 34 | 'incredible', 35 | 'heroic', 36 | 'grateful', 37 | 'hungry', 38 | 'handsome', 39 | 'exotic', 40 | 'dramatic', 41 | 'cute', 42 | 'cosmic', 43 | 'astonishing', 44 | 'peaceful', 45 | 'pretty', 46 | 'royal', 47 | 'unusual', 48 | 'thankful', 49 | ]; 50 | 51 | var adverbs = [ 52 | 'unbearably', 53 | 'amazingly', 54 | 'elegantly', 55 | 'tremendously', 56 | 'really', 57 | 'occasionally', 58 | 'joyfully', 59 | 'generally', 60 | 'annually', 61 | 'curiously', 62 | 'somewhat', 63 | 'perfectly', 64 | 'wonderfully', 65 | 'consistently', 66 | 'faithfully', 67 | 'very', 68 | 'rarely', 69 | 'mostly', 70 | 'recently', 71 | 'surprisingly', 72 | 'ferociously', 73 | 'optimistically', 74 | ]; 75 | 76 | function randomArray (array) { 77 | return array[Math.floor(Math.random() * array.length)]; 78 | } 79 | 80 | function projectName () { 81 | return randomArray(adverbs) + '-' + randomArray(adjectives) + '-' + randomArray(nouns); 82 | } 83 | 84 | window.addEventListener('load', function () { 85 | var name = projectName(); 86 | Array.prototype.slice.call(document.querySelectorAll('.project-name')).forEach(function (el) { 87 | el.textContent = name; 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /src/js/oauth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | (function () { 4 | var oauthUtils = { 5 | // Based on: https://stackoverflow.com/questions/4068373/center-a-popup-window-on-screen#4068385 6 | /** 7 | * Opens a popup window, in the aproximate center of the screen the source window is on. 8 | */ 9 | popupCenter: function (url) { 10 | // Fixes dual-screen position Most browsers Firefox 11 | var dualScreenLeft = window.screenLeft !== undefined ? window.screenLeft : screen.left; 12 | var dualScreenTop = window.screenTop !== undefined ? window.screenTop : screen.top; 13 | 14 | var documentClientWidth = document.documentElement.clientWidth; 15 | var documentClientHeight = document.documentElement.clientHeight; 16 | 17 | var width = window.innerWidth || documentClientWidth || screen.width; 18 | var height = window.innerHeight || documentClientHeight || screen.height; 19 | 20 | // I fiddled around here to make the W and Height automatic, 1.5 looks good. 21 | var w = width / 1.5; 22 | var h = height / 1.5; 23 | 24 | var left = ((width / 2) - (w / 2)) + dualScreenLeft; 25 | var top = ((height / 2) - (h / 2)) + dualScreenTop; 26 | var newWindow = window.open(url, '_blank', 'location=0,menubar=0,status=0,toolbar=0' + 27 | ',width=' + w + 28 | ',height=' + h + 29 | ',top=' + top + 30 | ',left=' + left); 31 | 32 | // Puts focus on the newWindow 33 | if (window.focus) { 34 | newWindow.focus(); 35 | } 36 | }, 37 | openImplicitOAuthWindow: function (scopes) { 38 | var clientId = 'fa54866255ea641235e596e5659fa726a4aa9f7ecc72758f'; 39 | var redirectURI = 'https://dev.mixer.com/oauthreturn.html'; 40 | var url = 'https://mixer.com/oauth/authorize?response_type=token&' + 41 | 'redirect_uri=' + redirectURI + '&' + 42 | 'scope=' + scopes + '&' + 43 | 'client_id=' + clientId; 44 | oauthUtils.popupCenter(url); 45 | }, 46 | 47 | /** 48 | * Retrieve an implicit access token from the url hash 49 | */ 50 | getAccessToken: function () { 51 | var hash = window.location.hash.slice(1); 52 | 53 | if (hash.length === 0) { 54 | return null; 55 | } 56 | 57 | var hashParts = hash.split('&'); 58 | for (var i = 0; i < hashParts.length; i++) { 59 | var item = hashParts[i].split('='); 60 | if (item[0] === 'access_token') { 61 | return item[1]; 62 | } 63 | } 64 | 65 | return null; 66 | }, 67 | 68 | handleOAuthResponse: function () { 69 | var token = oauthUtils.getAccessToken(); 70 | 71 | window.opener.oauthUtils.replaceToken(token); 72 | window.close(); 73 | }, 74 | replaceToken: function (token) { 75 | if (!token) { 76 | $('.auth-token').html('Error retrieving token'); 77 | return; 78 | } 79 | $('.auth-token').html(token).addClass('retrieved'); 80 | }, 81 | registerOAuthClickHandler: function (scopes) { 82 | $('.auth-token').off('click').on('click', function (e) { 83 | if (e.currentTarget.classList.contains('retrieved')) { 84 | return; 85 | } 86 | oauthUtils.openImplicitOAuthWindow(scopes); 87 | }); 88 | }, 89 | registerClientIdClickHandler: function () { 90 | $('.client-id').off('click').on('click', function () { 91 | oauthUtils.popupCenter('https://mixer.com/lab/keypopup'); 92 | }); 93 | }, 94 | }; 95 | 96 | window.oauthUtils = oauthUtils; 97 | }()); 98 | -------------------------------------------------------------------------------- /src/js/scroll.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | $(function () { 4 | $('.js-smooth-anchor').on('mousedown', function (e) { 5 | $('html, body').animate({ scrollTop: $(this.hash).offset().top }, 400); 6 | e.preventDefault(); 7 | return false; 8 | }); 9 | 10 | if (window.innerWidth < 992) { 11 | return; // no scrollspy on small screens 12 | } 13 | $('.rest-sidebar').each(function (i, el) { 14 | var json = el.dataset.scrollspy; 15 | var opts = json ? JSON.parse(json) : { container: 'body' }; 16 | opts.target = '.rest-sidebar'; 17 | $(opts.container).scrollspy(opts); 18 | }); 19 | 20 | $('.rest-sidebar').each(function (i, el) { 21 | var json = el.dataset.scrollStick; 22 | var defaults = { parent: '.site-content', recalc_every: 500 }; 23 | $(el).stick_in_parent(json ? JSON.parse(json) : defaults); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/legal/attributions.pug: -------------------------------------------------------------------------------- 1 | extends ../layout.pug 2 | 3 | block navbar 4 | .navbar-wrapper 5 | .container 6 | nav.navbar.navbar-inverse: +navbar() 7 | h1 Attributions & Licenses 8 | 9 | block content 10 | .container 11 | .page-header 12 | p Here at Mixer we use a significant amount of code from the open source community, and we try to #[a(href='https://github.com/mixer') give back] when we can too! Listed below are licenses for many of the modules and submodules used in various places across the Mixer platform. 13 | 14 | +highlightFile('text', 'legal/licenses.txt') 15 | -------------------------------------------------------------------------------- /src/mixins.pug: -------------------------------------------------------------------------------- 1 | mixin highlightFile(language, file) 2 | +highlightStr( 3 | language, 4 | readFile(file) 5 | .toString() 6 | /* Removes eslinst-disable or global definition comments in source files */ 7 | .replace(/\/\*\W+(eslint-disable|global)((?!\*\/).)*\*\/\s*/ig, '')) 8 | 9 | mixin highlightJson(obj) 10 | +highlightStr('json', JSON.stringify(obj, null, 3)) 11 | 12 | mixin highlightStr(language, str) 13 | pre 14 | code(class=language) 15 | != highlight(language, str) 16 | 17 | mixin scripts 18 | script( 19 | src='https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js' 20 | crossorigin='anonymous' 21 | integrity='sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4= sha512-3P8rXCuGJdNZOnUx/03c1jOTnMn3rP63nBip5gOP2qmUh5YAdVAvFZ1E+QLZZbC1rtMrQb+mah3AfYW11RUrWA==' 22 | ) 23 | script(src='/js/developers.js') 24 | 25 | mixin bsTabs(name, tabs) 26 | - 27 | bsTabs[name] = tabs; 28 | Object.defineProperty(tabs, '$active', { 29 | value: Object.keys(tabs).shift(), 30 | }); 31 | 32 | ul.nav.nav-tabs(role='tablist') 33 | for tabName, tabId in tabs 34 | li( 35 | class=(tabs.$active === tabId ? 'active' : null) 36 | role='presentation' 37 | ) 38 | a(data-toggle='tab' href=`#${name}_${tabId}`)= tabName 39 | if block 40 | .tab-content 41 | block 42 | 43 | mixin bsTabItem(parent, name) 44 | - 45 | if (!bsTabs[parent]) { 46 | throw new Error(`Attempted to define child tab of unknown bsTab ${parent}`) 47 | } 48 | if (!bsTabs[parent][name]) { 49 | throw new Error(`${name} not found in bsTabs ${parent}`); 50 | } 51 | 52 | .tab-pane.fade( 53 | class=(bsTabs[parent].$active === name ? 'active' : null) && 'active in' 54 | data-name=bsTabs[parent][name] 55 | id=`${parent}_${name}` 56 | role='tabpanel' 57 | ) 58 | block 59 | 60 | mixin oauthLinks(scopes) 61 | script. 62 | $(function () { 63 | oauthUtils.registerOAuthClickHandler('#{scopes}'); 64 | }); 65 | 66 | mixin clientIdLinks() 67 | script. 68 | $(function () { 69 | oauthUtils.registerClientIdClickHandler(); 70 | }); 71 | 72 | mixin bookPageTitle(pages) 73 | = pages.hasOwnProperty(fileBasename) ? pages[fileBasename].name : fileBasename 74 | 75 | mixin bookPageNav(location, page, isTopLevel=true) 76 | - 77 | const isActive = fileBasename === location; 78 | 79 | if (typeof page === 'string') { 80 | page = { name: page }; 81 | } 82 | if (isTopLevel) { 83 | location = `./${location}.html`; 84 | } else { 85 | location = `#${location}`; 86 | } 87 | 88 | li(class=isActive ? 'active-locked' : '') 89 | a(href=location)= page.name 90 | 91 | if page.sections 92 | ul.nav.nav-stacked 93 | each section, hash in page.sections 94 | +bookPageNav(hash, section, false) 95 | 96 | mixin bookPrevArrow(pages) 97 | - 98 | const keys = Object.keys(pages); 99 | const index = keys.indexOf(fileBasename); 100 | 101 | if index > 0 102 | a.book-nav.book-nav-prev(href=`./${keys[index - 1]}.html` title=pages[keys[index - 1]].name) 103 | | ← Previous 104 | 105 | mixin bookNextArrow(pages) 106 | - 107 | const keys = Object.keys(pages); 108 | const index = keys.indexOf(fileBasename); 109 | 110 | if index < keys.length - 1 111 | a.book-nav.book-nav-prev(href=`./${keys[index + 1]}.html` title=pages[keys[index + 1]].name) 112 | | Next → 113 | -------------------------------------------------------------------------------- /src/oauthreturn.pug: -------------------------------------------------------------------------------- 1 | include ./mixins.pug 2 | doctype html 3 | html 4 | head 5 | meta(charset='utf-8') 6 | meta(name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1') 7 | link(rel='apple-touch-icon-precomposed' sizes='144x144' href='/img/apple-touch-icon-144x144.png') 8 | link(rel='apple-touch-icon-precomposed' sizes='152x152' href='/img/apple-touch-icon-152x152.png') 9 | link(rel='shortcut icon' type='image/x-icon' href='/img/favicon.ico') 10 | link(rel='icon' type='image/png' href='/img/favicon-32x32.png' sizes='32x32') 11 | link(rel='icon' type='image/png' href='/img/favicon-16x16.png' sizes='16x16') 12 | 13 | link(href='https://fonts.googleapis.com/css?family=Heebo:100,300|Inconsolata:400,700|Open+Sans:400,300' rel='stylesheet') 14 | link(rel='stylesheet' href='/css/style.css') 15 | 16 | block head 17 | 18 | title 19 | block title 20 | 21 | body 22 | +scripts() 23 | script. 24 | $(document).ready(function () { 25 | oauthUtils.handleOAuthResponse(); 26 | }); 27 | -------------------------------------------------------------------------------- /src/reference/book_layout.pug: -------------------------------------------------------------------------------- 1 | extends ../layout.pug 2 | include ../mixins.pug 3 | 4 | block title 5 | block bookPages 6 | if pages 7 | +bookPageTitle(pages) 8 | = ' | Interactive Reference | Mixer Developers' 9 | 10 | block navbar 11 | .navbar-wrapper 12 | .container 13 | nav.navbar.navbar-inverse: +navbar() 14 | h1 15 | block bookName 16 | 17 | block content 18 | block bookPages 19 | if pages 20 | .container: .row 21 | .col-md-3 22 | .hidden-print.book-sidebar.rest-sidebar(role='complementary') 23 | nav 24 | ul.nav.nav-stacked 25 | each page, location in pages 26 | +bookPageNav(location, page) 27 | 28 | .col-md-9.book-primary(role='main') 29 | - 30 | const keys = Object.keys(pages); 31 | const index = keys.indexOf(fileBasename); 32 | const next = pages[keys[index + 1]]; 33 | 34 | .book-page-title 35 | +bookPrevArrow(pages) 36 | h1: +bookPageTitle(pages) 37 | +bookNextArrow(pages) 38 | 39 | block reference 40 | 41 | .book-page-footer 42 | +bookPrevArrow(pages) 43 | 44 | if next 45 | span= `Next up: ${next.name}` 46 | +bookNextArrow(pages) 47 | else 48 | span That's it! If you have questions, ask #[a(href='https://gitter.im/Mixer/developers') on Gitter]! 49 | -------------------------------------------------------------------------------- /src/reference/chat/chatEndpoint.json: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Connection: keep-alive 3 | Content-Type: application/json; charset=utf-8 4 | Date: Sun, 26 Jun 2016 22:50:41 GMT 5 | 6 | { 7 | "authkey": "b41e9ae6b14df18c59f415419b1f827c", 8 | "endpoints": [ 9 | "wss://chat2-dal.mixer.com:443", 10 | "wss://chat1-dal.mixer.com:443" 11 | ], 12 | "permissions": [ 13 | "chat", 14 | "connect", 15 | "poll_vote" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/reference/chat/exchange.txt: -------------------------------------------------------------------------------- 1 | Client: {"type":"method","method":"auth","arguments":[1,1,"fc3f865c156f32cac0755cde007654a8"],"id":0} 2 | Server: {"type":"reply","error":null,"id":0,"data":{"authenticated":true,"roles":["Owner"]}} 3 | Client: {"type":"method","method":"msg","arguments":["Hello world :)"],"id":2} 4 | Server: {"type":"reply","error":null,"id":2,"data":{"channel":1,"id":"6351f9e0-3bf2-11e6-a3b3-bdc62094c158","user_name":"connor","user_id":1,"user_roles":["Owner"],"message":{"message":[{"type":"text","data":"Hello world ","text":"Hello world!"}],"meta":{}}}} 5 | Server: {"type":"event","event":"ChatMessage","data":{"channel":1,"id":"6351f9e0-3bf2-11e6-a3b3-bdc62094c158","user_name":"connor","user_id":1,"user_roles":["Owner"],"message":{"message":[{"type":"text","data":"Hello world ","text":"Hello world!"}],"meta":{}}}} 6 | -------------------------------------------------------------------------------- /src/reference/chat/menu.pug: -------------------------------------------------------------------------------- 1 | li 2 | a(href='#chat__introduction') Introduction 3 | li 4 | a(href='#chat__connection') Connection 5 | li 6 | a(href='#chat__packets') Packets 7 | ul.nav.nav-stacked 8 | li: a(href='#chat__packets__method') Method 9 | li: a(href='#chat__packets__reply') Reply 10 | li: a(href='#chat__packets__event') Event 11 | li 12 | a(href='#chat__methods') Methods 13 | ul.nav.nav-stacked 14 | for data, method in chat.methods 15 | li: a(href=`#chat__methods__${method.replace(/\W/g, '_')}`)= method 16 | li 17 | a(href='#chat__events') Events 18 | ul.nav.nav-stacked 19 | for data, event in chat.events 20 | li: a(href=`#chat__events__${event}`)= event 21 | li 22 | a(href='#chat__costreaming') Costreaming 23 | li 24 | a(href='#chat__troubleshooting') Troubleshooting 25 | -------------------------------------------------------------------------------- /src/reference/constellation/events_table.pug: -------------------------------------------------------------------------------- 1 | include ../../rest/type.pug 2 | 3 | mixin liveEventsTable(showWhereRequiresAuth) 4 | table.table 5 | thead 6 | tr 7 | th Event 8 | th Description 9 | tbody 10 | for data, name in liveEvents 11 | if !data.requiresAuth || showWhereRequiresAuth 12 | tr 13 | td(style='white-space:nowrap;'): code= name 14 | td 15 | div!= marked(data.description) 16 | if data.payload 17 | strong Payload:  18 | small 19 | +typeNameString({ type: data.payload.type, required: true }) 20 | +simpleType('', data.payload, true) 21 | if data.example 22 | p 23 | a(href=`#example_${name.replace(/\W/g, '_')}`) View example 24 | 25 | for data, name in liveEvents 26 | if data.example 27 | .modal.fade(tabindex='0' id=`example_${name.replace(/\W/g, '_')}`) 28 | .modal-dialog.modal-lg 29 | .modal-content 30 | .modal-header 31 | button.close(type='button' data-dismiss='modal' aria-hidden='true')!= '×' 32 | h4.modal-title 33 | span.text-primary(style='font-weight:300') Example 34 | |   35 | span= name 36 | .modal-body 37 | code 38 | if typeof data.example === 'object' 39 | +highlightStr('json', JSON.stringify(data.example, ' ', 2)) 40 | else 41 | = data.example 42 | -------------------------------------------------------------------------------- /src/reference/interactive/cplusplus/index.pug: -------------------------------------------------------------------------------- 1 | extends ../../reference_layout.pug 2 | include ../c_api.pug 3 | 4 | block navbar 5 | +cnavbar() 6 | block menu 7 | +menu(cClients.cpp) 8 | block reference 9 | +reference(cClients.cpp, 'C++') 10 | -------------------------------------------------------------------------------- /src/reference/interactive/csharp/index.pug: -------------------------------------------------------------------------------- 1 | extends ../../reference_layout.pug 2 | include ../c_api.pug 3 | 4 | block navbar 5 | +cnavbar() 6 | block menu 7 | +menu(cClients.csharp) 8 | block reference 9 | +reference(cClients.csharp, 'C#') 10 | -------------------------------------------------------------------------------- /src/reference/interactive/design.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/design.pdf -------------------------------------------------------------------------------- /src/reference/interactive/menu.pug: -------------------------------------------------------------------------------- 1 | li 2 | a(href='#introduction') Introduction 3 | li 4 | a(href='#getting-started') Getting Started 5 | ul.nav.nav-stacked 6 | li 7 | a(href='#prerequisites') Prerequisites 8 | li 9 | a(href='#choosing-an-sdk-environment') Choosing an SDK / Environment 10 | li 11 | a(href='#creating-an-interactive-project') Creating an Interactive Project 12 | li 13 | a(href='#the-info-step') Info Section 14 | li 15 | a(href='#the-build-step') Build Section 16 | ul.nav.nav-stacked 17 | li 18 | a(href='#scenes-area') Scenes Area 19 | li 20 | a(href='#controls-area') Controls Area 21 | li 22 | a(href='#grid-area') Grid Area 23 | li 24 | a(href='#the-code-step') Code Section 25 | li 26 | 27 | li 28 | a(href='#publish-step') Publish Step 29 | ul.nav.nav-stacked 30 | li 31 | a(href='#publishing-flow') Publishing Flow 32 | li 33 | a(href='#overview') Interactive Overview 34 | ul.nav.nav-stacked 35 | li 36 | a(href='#the-gameclient') The GameClient 37 | li 38 | a(href='#the-interactive-service') The Interactive Service 39 | li 40 | a(href='#an-interactive-project') An Interactive Project 41 | li 42 | a(href='#participants') Participants 43 | li 44 | a(href='#interactive-experience-structure') Interactive Experience Structure 45 | li 46 | a(href='#scenes') Scenes 47 | li 48 | a(href='#groups') Groups 49 | li 50 | a(href='#controls') Controls 51 | ul.nav.nav-stacked 52 | li 53 | a(href='#buttons') Buttons 54 | ul.nav.nav-stacked 55 | li 56 | a(href='#joysticks') Joysticks 57 | li 58 | a(href='#labels') Labels 59 | li 60 | a(href='#textboxes') Textboxes 61 | li 62 | a(href='#more-customization') Looking for More Customization? 63 | li 64 | a(href='#project-access') Managing Project Access 65 | ul.nav.nav-stacked 66 | li 67 | a(href='#sharing-your-project') Sharing your Project 68 | ul.nav.nav-stacked 69 | li 70 | a(href='#share-codes') Share Codes 71 | li 72 | a(href='#explicit-sharing') Explicit Sharing 73 | li 74 | a(href='#project-editors') Adding project editors 75 | li 76 | a(href='#access-best-practices') Project Access Best Practices 77 | li 78 | a(href='#sparks') Sparks 79 | ul.nav.nav-stacked 80 | li 81 | a(href='#what-are-sparks') What are Sparks? 82 | li 83 | a(href='#how-are-sparks-earnt') How are Sparks Earnt? 84 | li 85 | a(href='#buy-sparks') Can users buy Sparks? 86 | li 87 | a(href='#sparks-best-practices') Best Practices for Sparks 88 | li 89 | a(href='#spark-transactions') Spark Transactions 90 | 91 | li 92 | a(href='#scale') Scale Considerations 93 | ul.nav.nav-stacked 94 | li 95 | a(href='#scale-throttling') Throttling 96 | li 97 | a(href='#scale-testing') Scale Testing 98 | li 99 | a(href='#design') Interactive Game Design 100 | li 101 | a(href='#protocol-overview') Protocol Overview 102 | ul.nav.nav-stacked 103 | li 104 | a(href='#wire-format') Wire Format 105 | li 106 | a(href='#packets') Packets 107 | ul.nav.nav-stacked 108 | li 109 | a(href='#method') Method 110 | li 111 | a(href='#reply') Reply 112 | li 113 | a(href='#compression') Compression 114 | li 115 | a(href='#authentication') Authentication 116 | ul.nav.nav-stacked 117 | li 118 | a(href='#oauth-20') OAuth 2.0 119 | li 120 | a(href='#xtoken') XToken 121 | li 122 | a(href='#where-to-get-help') Where to get help 123 | -------------------------------------------------------------------------------- /src/reference/interactive/protocol/Makefile: -------------------------------------------------------------------------------- 1 | RST_SRC = $(wildcard *.rst) 2 | VERSION=1.7.0 3 | RST_GEN = $(RST_SRC:.rst=.pdf) 4 | 5 | all: $(RST_GEN) 6 | 7 | %.pdf: %.rst 8 | pandoc -S -s -f rst -t latex -o $@ $^ --latex-engine=xelatex --toc --variable urlcolor=cyan --variable author='Document Version: ${VERSION}' 9 | 10 | clean: 11 | rm -f $(RST_GEN) 12 | 13 | .PHONY: all clean 14 | -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/client-method-optional.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/img/client-method-optional.pdf -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/client-method-optional.svg: -------------------------------------------------------------------------------- 1 | client-method-optional -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/client-method.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/img/client-method.pdf -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/client-method.svg: -------------------------------------------------------------------------------- 1 | client-method -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/compression-diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/img/compression-diagram.pdf -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/server-method-optional.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/img/server-method-optional.pdf -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/server-method-optional.svg: -------------------------------------------------------------------------------- 1 | server-method-optional -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/server-method.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/img/server-method.pdf -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/server-method.svg: -------------------------------------------------------------------------------- 1 | server-method -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/simplified-oauth.monopic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/img/simplified-oauth.monopic -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/simplified-oauth.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/img/simplified-oauth.pdf -------------------------------------------------------------------------------- /src/reference/interactive/protocol/img/state-diagram.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/img/state-diagram.pdf -------------------------------------------------------------------------------- /src/reference/interactive/protocol/protocol.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixer/developers/b76f6c808adb473d960bef6e43817a0c2d9cf812/src/reference/interactive/protocol/protocol.pdf -------------------------------------------------------------------------------- /src/reference/interactive1/index.pug: -------------------------------------------------------------------------------- 1 | extends ../reference_layout.pug 2 | include ../../rest/type.pug 3 | block title 4 | | Interactive Reference | Mixer Developers 5 | 6 | block navbar 7 | .navbar-wrapper 8 | .container 9 | nav.navbar.navbar-inverse: +navbar() 10 | h1 Interactive Reference Documentation 11 | block menu 12 | .hidden-print.rest-sidebar(role='complementary') 13 | nav 14 | ul.nav.nav-stacked 15 | include ./menu.pug 16 | block reference 17 | h1(id='interactive') Interactive 18 | .alert.alert-warning 19 | p These reference docs are for Interactive 1. Check out our #[a(href='/reference/interactive/index.html') Interactive 2 docs]. 20 | 21 | p Mixer Interactive is a new way to let viewers directly control the environment around streamers using a visual interface. It works with any game and is built into the Mixer platform. Interactive games can be played with streamers or fully autonomously. 22 | 23 | p Developers can create integrations with as few as 25 lines of code and receive input from viewers to influence a live stream directly. 24 | 25 | h1(id='interactive__introduction') Introduction 26 | p Our interactive platform communicates using #[a(href='https://developers.google.com/protocol-buffers/') Google's Protocol Buffer] specification over a standard secure websocket protocol #[code wss] connection. Viewers' inputs from the site are sent to Mixer's aggregator server, which analyzes them for you, before being sent to clients. 27 | 28 | include ./protocol.pug 29 | include ../../snippets/help.pug 30 | -------------------------------------------------------------------------------- /src/reference/interactive1/menu.pug: -------------------------------------------------------------------------------- 1 | li 2 | a(href='#interactive__introduction') Introduction 3 | li 4 | a(href='#interactive__connection') Connection 5 | li 6 | a(href='#interactive__flow') Data Flow 7 | li 8 | a(href='#interactive__packets') Packets 9 | li 10 | a(href='#interactive__protocol') Specification 11 | -------------------------------------------------------------------------------- /src/reference/interactive1/protocol.ascii: -------------------------------------------------------------------------------- 1 | ┌──────┐ ┌──────┐ 2 | │ │<─────Handshake───────│ │ 3 | │ │ │ │ 4 | │ │────HandshakeACK─────>│ │ 5 | │ │ (or error) │ │ 6 | │ │ │ │ 7 | │Server│<───Progress Update───│Client│ 8 | │ │<─────────────────────│ │ 9 | │ │ │ │ 10 | │ │────────Report───────>│ │ 11 | │ │─────────────────────>│ │ 12 | │ │ │ │ 13 | └──────┘ └──────┘ 14 | -------------------------------------------------------------------------------- /src/reference/interactive1/protocol.pug: -------------------------------------------------------------------------------- 1 | include ../../mixins.pug 2 | h1#interactive__connection Connection 3 | p. 4 | To make a connection you first need to have an authenticated session with our 5 | #[a(href='#') REST API]. Once you have one you make a #[code GET] request to 6 | #[code /interactive/{channel}/robot] where #[code channel] is the channel id of the 7 | channel you wish to connect to. 8 | include ../../tutorials/channelid.pug 9 | p. 10 | In response to the request you'll receive a WebSocket address and a key: 11 | +highlightFile('json','./tutorials/code/node/interactive/2_response.json') 12 | 13 | p. 14 | A connection should then be made to the received address with #[code /robot] appended. The key and 15 | channel id must be included in the #[code Handshake] packet. 16 | 17 | h2(id='interactive__flow') Data Flow 18 | 19 | p Then general protocol flow is the following: 20 | 21 | pre 22 | include ./protocol.ascii 23 | ol 24 | li The Client establishes a TCP connection to the server. It sends a Handshake packet, populated with the channel ID and auth key. 25 | li Mixer Interactive verifies the key. If it's correct, a blank HandshakeACK packet is sent. If it's not correct, an Error packet is sent down. 26 | li Mixer Interactive repeatedly sends Reports down to the Client every #[code reportInterval]. 27 | li The Client can send Progress Updates to the Server at it's discretion. 28 | 29 | h2(id='interactive__packets') Packets 30 | p. 31 | Protocol Buffer packets are encoded in a binary websocket frame. They start with their their packet ID, formatted as variable length unsigned integers. Variable-length integers are described in the #[a(href='https://developers.google.com/protocol-buffers/docs/encoding#varints') protocol buffer specification]. 32 | 33 | p A single packet can be represented as follows: 34 | pre. 35 | ┌───────────────────────┐ 36 | │ Packet ID (varuint) │ 37 | └───────────────────────┘ 38 | ┌───────────────────────┐ 39 | │ Packet Data │ 40 | │ (protocol buffer) │ 41 | ......................... 42 | p. 43 | The protocol is bi-directional. Mixer Interactive expects information sent 44 | to it to be encoded in prescribed format, and it will in turn send 45 | information in the same format. 46 | p. 47 | Packet ID mapping is as follows: 48 | table.table 49 | tr 50 | th ID 51 | th Packet 52 | tr 53 | td 0 54 | td Handshake 55 | tr 56 | td 1 57 | td HandshakeACK 58 | tr 59 | td 2 60 | td Report 61 | tr 62 | td 3 63 | td Error 64 | tr 65 | td 4 66 | td Progress Update 67 | h2(id='interactive__protocol') Specification 68 | p. 69 | The protocol buffer specification for Mixer interactive lists packet types and 70 | structures within those packets in more detail: 71 | 72 | +highlightFile('protobuf','./reference/interactive1/proto/tetris.proto') 73 | -------------------------------------------------------------------------------- /src/reference/interactive_next/best-practices.pug: -------------------------------------------------------------------------------- 1 | extends ./page.pug 2 | 3 | block reference 4 | h2#test-across-platforms Test Across Platforms 5 | 6 | p With desktop computers as the primary development tools, it's easy to forget that most viewers aren't on desktop. Xbox and mobile viewers account for large chunks of the streaming audience. The CDK provides some tools to test on various screen sizes and resolutions, and we recommend that you also test using different browsers and different devices once your private bundle is uploaded to Mixer. 7 | 8 | p For more information about the testing and development process, see our #[a(href='/reference/interactive_next/workflow.html') Workflow guide]. 9 | 10 | h2#use-internationalizations Use Internationalization 11 | 12 | p Most people don't speak your native language, so most of your potential viewers or players won't either! Internationalizing your controls is an easy win to help reach the largest possible audience. Even if you don't have translated strings initially, using the #[code <Translate />] tag is useful so that translations will be a drop-in later. 13 | 14 | +highlightFile('javascript', 'reference/interactive_next/internationalization/translation-demo.jsx') 15 | 16 | p You can read more in our #[a(href='/reference/interactive_next/quickstart-preact.html#internationalization') Internationalization guide]. 17 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/basic-register.jsx: -------------------------------------------------------------------------------- 1 | import { HelloWorldControl } from './HelloWorld'; 2 | 3 | // ... 4 | 5 | const registry = new Mixer.Registry().register(Button, Joystick, HelloWorldControl, PreactScene); 6 | 7 | // ... 8 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/basic.jsx: -------------------------------------------------------------------------------- 1 | import * as Mixer from '@mixer/cdk-std'; 2 | import { h } from 'preact'; 3 | 4 | import { PreactControl } from './alchemy/preact'; 5 | 6 | @Mixer.Control({ kind: 'helloWorld' }) 7 | export class HelloWorldControl extends PreactControl { 8 | render() { 9 | return

Hello world!

; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/hello-give-input.jsx: -------------------------------------------------------------------------------- 1 | @Mixer.Control({ kind: 'helloWorld' }) 2 | export class HelloWorldControl extends PreactControl { 3 | @Mixer.Input() public dimensions: Mixer.IDimensions; 4 | @Mixer.Input() public text: string; 5 | @Mixer.Input({ kind: Mixer.InputKind.Color }) public color: string; 6 | 7 | render() { 8 | return ; 11 | } 12 | 13 | /** 14 | * This function gets called when the user presses their mouse on the 15 | * button. @bind is needed here to preserve function context (a JavaScript 16 | * language quirk). 17 | */ 18 | @bind 19 | fire() { 20 | const participant = this.control.state.participant; 21 | this.control.giveInput({ 22 | event: 'clicked', 23 | username: participant.get('username'), 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/hello-inputs.jsx: -------------------------------------------------------------------------------- 1 | import * as Mixer from '@mixer/cdk-std'; 2 | import { h } from 'preact'; 3 | 4 | import { PreactControl } from './alchemy/preact'; 5 | 6 | @Mixer.Control({ kind: 'helloWorld' }) 7 | export class HelloWorldControl extends PreactControl { 8 | /** 9 | * Position of the control. This is required if you want to edit your 10 | * controls in the Interactive Studio! 11 | */ 12 | @Mixer.Input() public dimensions: Mixer.IDimensions; 13 | 14 | /** 15 | * Text to display in the control. The "string" type can be seen an inferred, 16 | * and people will be able to set it in the GUI. 17 | */ 18 | @Mixer.Input() public text: string; 19 | 20 | /** 21 | * Color of the text. The kind is specified manually here. 22 | */ 23 | @Mixer.Input({ kind: Mixer.InputKind.Color }) public color: string; 24 | 25 | render() { 26 | return

{this.text}

; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/scene-01.jsx: -------------------------------------------------------------------------------- 1 | import * as Mixer from '@mixer/cdk-std'; 2 | import { h } from 'preact'; 3 | 4 | import { PreactScene, Translate } from './alchemy/preact'; 5 | 6 | @Mixer.Scene({ id: 'leaderboard' }) 7 | export class LeaderboardScene extends PreactScene<{}> { 8 | render() { 9 | return
10 |

11 |
; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/scene-register.jsx: -------------------------------------------------------------------------------- 1 | import { HelloWorldControl } from './HelloWorld'; 2 | import { LeaderboardScene } from './LeaderboardScene'; 3 | 4 | // ... 5 | 6 | const registry = new Mixer.Registry().register( 7 | Button, Joystick, HelloWorldControl, PreactScene, LeaderboardScene); 8 | 9 | // ... 10 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/style-example-01.jsx: -------------------------------------------------------------------------------- 1 | @Mixer.Control({ kind: 'helloWorld' }) 2 | export class HelloWorldControl extends PreactControl { 3 | @Mixer.Input() public dimensions: Mixer.IDimensions; 4 | @Mixer.Input() public text: string; 5 | @Mixer.Input({ kind: Mixer.InputKind.Color }) public color: string; 6 | 7 | render() { 8 | return
9 |

{this.text}

10 |
; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/style-example-02.jsx: -------------------------------------------------------------------------------- 1 | import { RuleSet, css } from './alchemy/Style'; 2 | 3 | // This outputs: `color:#fff;background-color:#000;width:500 4 | console.log( 5 | new RuleSet({ 6 | color: '#fff', 7 | backgroundColor: '#000', 8 | width: 500, 9 | }).compile(); 10 | ); 11 | 12 | 13 | // css() is a shortcut for that. Outputs: `color:#fff`. 14 | console.log(css({ color: '#fff' })) 15 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/style-example-03.jsx: -------------------------------------------------------------------------------- 1 | @Mixer.Control({ kind: 'helloWorld' }) 2 | export class HelloWorldControl extends PreactControl { 3 | @Mixer.Input() public dimensions: Mixer.IDimensions; 4 | @Mixer.Input() public text: string; 5 | @Mixer.Input({ kind: Mixer.InputKind.Color }) public color: string; 6 | 7 | render() { 8 | const style = this.props.style.concat({ color: this.color }); 9 | return

{this.text}

; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/reference/interactive_next/controls/style-example-04.jsx: -------------------------------------------------------------------------------- 1 | @Mixer.Control({ kind: 'helloWorld' }) 2 | export class HelloWorldControl extends PreactControl { 3 | @Mixer.Input() public dimensions: Mixer.IDimensions; 4 | @Mixer.Input() public text: string; 5 | @Mixer.Input() public awesome: boolean; 6 | 7 | render() { 8 | return
12 |

{this.text}

13 |
; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/data-from-custom-control.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* global mixer */ 3 | mixer.socket.call('giveInput', { 4 | controlID: 'my-control', 5 | event: 'my-custom-event', 6 | dataFieldOne: 1, 7 | someOtherObject: { 8 | full: 'of exciting data!', 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/example-broadcast-event.json: -------------------------------------------------------------------------------- 1 | { 2 | "scope": [ 3 | "group:default" 4 | ], 5 | "data": { 6 | "my-custom-object": { 7 | "with": "custom data" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/example-participant-meta-unset.json: -------------------------------------------------------------------------------- 1 | { 2 | "my-custom-object": null 3 | } -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/example-participant-meta.json: -------------------------------------------------------------------------------- 1 | { 2 | "sessionID": "e1fefc78-d4cc-4c69-b2d9-b36ed5c52893", 3 | "userID": 1, 4 | "username": "Mr_Tester", 5 | "level": 42, 6 | "lastInputAt": 1520463222192, 7 | "connectedAt": 1520463222192, 8 | "disabled": false, 9 | "groupID": "default", 10 | "sparks": 500, 11 | "my-custom-object": { 12 | "with": "custom data" 13 | } 14 | } -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/example-participant.json: -------------------------------------------------------------------------------- 1 | { 2 | "sessionID": "e1fefc78-d4cc-4c69-b2d9-b36ed5c52893", 3 | "userID": 1, 4 | "username": "Mr_Tester", 5 | "level": 42, 6 | "lastInputAt": 1520463222192, 7 | "connectedAt": 1520463222192, 8 | "disabled": false, 9 | "groupID": "default", 10 | "sparks": 500 11 | } -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/example-scenes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "sceneID": "default", 4 | "controls": [ 5 | { 6 | "controlID": "my-control", 7 | "kind": "button", 8 | "text": "My First Button", 9 | "position": [ 10 | { 11 | "width": 10, 12 | "height": 8, 13 | "size": "large", 14 | "x": 0, 15 | "y": 0 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | ] -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/tutorial/go-live-method.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* global mixer */ 3 | this.client.state.getControl('overlay').on('click', (clickEvent) => { 4 | this.client.updateControls({ 5 | sceneID: 'default', 6 | controls: [ 7 | { 8 | controlID: 'overlay', 9 | titlePosition: clickEvent.input.position, 10 | }, 11 | ], 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/tutorial/handle-click-event.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* global mixer */ 3 | const overlay = document.getElementById('overlay'); 4 | overlay.addEventListener('click', function handleOverlayClicked (event) { 5 | mixer.socket.call('giveInput', { 6 | controlID: 'overlay', 7 | event: 'click', 8 | position: { 9 | x: event.offsetX / overlay.offsetWidth, 10 | y: event.offsetY / overlay.offsetHeight, 11 | }, 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/tutorial/handle-update-1.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* global mixer */ 3 | const titleElement = document.getElementById('title'); 4 | function handleControlUpdate (update) { 5 | const filteredControls = update.controls.filter(c => c.controlID === 'overlay'); 6 | if (filteredControls.length !== 1) { 7 | return; 8 | } 9 | 10 | const overlayControl = filteredControls[0]; 11 | titleElement.style.left = `${overlayControl.titlePosition.x * 100}%`; 12 | titleElement.style.top = `${overlayControl.titlePosition.y * 100}%`; 13 | } 14 | 15 | mixer.socket.on('onControlUpdate', handleControlUpdate); 16 | -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/tutorial/handle-update-2.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* eslint-disable no-undef */ 3 | /* global mixer */ 4 | function handleSceneUpdate (update) { 5 | const filteredScenes = update.scenes.filter(c => c.sceneID === 'default'); 6 | if (filteredScenes.length !== 1) { 7 | return; 8 | } 9 | 10 | const defaultScene = filteredScenes[0]; 11 | handleControlUpdate(defaultScene); 12 | } 13 | 14 | mixer.socket.on('onSceneUpdate', handleSceneUpdate); 15 | mixer.socket.on('onSceneCreate', handleSceneUpdate); 16 | -------------------------------------------------------------------------------- /src/reference/interactive_next/game-clients/tutorial/sample-event-receive.txt: -------------------------------------------------------------------------------- 1 | <<< {"type":"method","method":"giveInput","params":{"participantID":"eb6baf2b-ba96-49d6-a3ea-7d76a42198e1","input":{"controlID":"overlay","event":"click","position":{"x":0.6435868331441543,"y":0.3217821782178218}}},"id":0,"seq":11,"discard":true} -------------------------------------------------------------------------------- /src/reference/interactive_next/index.pug: -------------------------------------------------------------------------------- 1 | extends ./page.pug 2 | 3 | block reference 4 | p Custom controls is a feature for Mixer Interactive that allows you to create Interactive experiences for Mixer viewers and streamers. Unlike our standard controls and prefabs, such as the button and joystick, this allow you to create rich and engaging experiences with effectively limitless customizability! 5 | 6 | p The magic here is that with custom controls you can completely change the look, behavior and feel of Interactive Experiences by developing your own controls from scratch, or by using some of our standard controls as a base. Controls are written in HTML, JavaScript, and CSS which are then uploaded and served from Mixer's infrastructure at no cost to you. When combined with a Game Client in your Game or Application, they allow for a truly unique and customized experience that can fit the look and feel of your brand and ideas. 7 | 8 | h2#dive-in Looking to Dive in? 9 | p If you're looking to dive straight into writing custom control experiences then you have two choices: 10 | ul 11 | li #[a(href='quickstart-preact.html') Use Preact] - Leverage the power of Preact, TypeScript and our Existing Controls to get started quickly. 12 | li #[a(href='quickstart-html.html') Use HTML] - Start from scratch, use your favorite UI Library or Draw your Interactivity directly using a HTML Canvas. 13 | 14 | p You can also continue reading for an Overview on how the components of Custom Controls work. 15 | 16 | h2#components Custom Control Components 17 | 18 | p Custom Controls rely on several key components that work together to deliver an Interactive Experience on Mixer. 19 | 20 | h3#projects Projects 21 | 22 | p In Mixer, Interactive creations are organized into Projects, which include a name, description, and authorship information. Each Project has multiple Versions, which contain specific information about the layout of controls, groups, and scenes. For more information about Interactive in general, check out our #[a(href='/reference/interactive/index.html') Interactive Reference]. 23 | 24 | h3#bundles Bundles 25 | 26 | p The collection of HTML, CSS and JavaScript that you develop when creating a Custom Controls experience is called an Interactive #[strong Bundle], and project versions can be assigned to load a specific Bundle. 27 | 28 | figure 29 | img(src='/img/reference/interactive/controls/project-heirarchy.svg' alt='Diagram showing the project hierarchy') 30 | figcaption The project "My Awesome Project" has three versions. Two we've assigned to the default bundle (called the #[code interactive-launchpad]) and for version 2.0 we've created a custom bundle. 31 | 32 | p Bundles themselves can have multiple bundle versions. Bundles are initially created privately, accessible only to you. Once you decide to "publish" a bundle version to make it public, you won't be able to modify it. This ensures that you don't break users who are relying on released versions of your bundles. 33 | 34 | h3#controls Controls 35 | 36 | p Within a Custom Control bundle are a set of Controls. 37 | 38 | p Controls are interactive elements within an Interactive experience such as a Button, Joystick or even a MiniMap. They allow users to give inputs and receive data from your Game or Application. They exist within scenes of an Interactive experience and have a free-form set of properties that can be used to modify their behavior or appearance. They can also send inputs and data to your Game Client which can in turn send data back to update the control's appearance or behavior. 39 | 40 | p All of Mixer's standard controls and experiences such as Share your Controller are built using Custom Controls. They use #[a(href='https://preactjs.com/' target='_blank') Preact], a super lightweight framework that takes care of the dirty work for you, and #[a(href='https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html' target='_blank') TypeScript], a superset of JavaScript which gives you the option of adding type annotations to your code. 41 | 42 | p For more information on Mixer's Standard Controls, check out #[a(href='../interactive/index.html#controls') the documentation for them]. 43 | 44 | h3#summary Summary 45 | 46 | p In summary, when a streamer goes Interactive with a version of your Interactive Project, Mixer will look up the linked bundle for this version and load it onto the streamer's channel page for any viewers watching. They'll be connected through Mixer to your Game or Application where you can start receiving inputs and data to change and enhance a streamer's game or broadcast. 47 | -------------------------------------------------------------------------------- /src/reference/interactive_next/installing-cdk-1.pug: -------------------------------------------------------------------------------- 1 | h3(id=`${prefix}-prerequisites`) Prerequisites 2 | 3 | p You first need to #[a(href='https://nodejs.org/en/download/' target='_blank') install Node.js] on your computer. We require Node.js version 8.0.0 or higher. Node.js lets you run our build scripts and tools. You'll also need a decent code editor. We recommend #[a(href='https://code.visualstudio.com/' target='_blank') Visual Studio Code] as a powerful editor with great JavaScript support out-of-the-box. 4 | 5 | h3(id=`${prefix}-download`) Downloading the Control Development Kit 6 | 7 | p After Node.js is installed you'll need to download our Control Development Kit (CDK). The CDK powers custom control development with an integrated set of tools such as previewing, testing and more. You can download the CDK #[a(href='https://aka.ms/MixerCDK' target='_blank') here]. Once you've downloaded the installer, double click on it to open it. This will install and then run the CDK. 8 | 9 | h3(id=`${prefix}-create-project`) Create a Project 10 | 11 | p Once the CDK is running you'll see the following screen: 12 | img(src='/img/reference/interactive/cdk/cdkStartScreen.png' alt='The Start Screen of the CDK' width='800') 13 | 14 | p Custom Controls are stored within Projects in the CDK so the first step is to create a new project. To do this select the new project option in the File menu along the top of the CDK. This will launch a wizard which will guide you through the steps to create your new project. 15 | 16 | p First, select the folder that the project will be created in. We recommend you create a new folder to store all of your Custom Control projects so that they are organized. For example you could put them in #[code D:\dev\custom-controls\]. 17 | -------------------------------------------------------------------------------- /src/reference/interactive_next/installing-cdk-2.pug: -------------------------------------------------------------------------------- 1 | p You will be asked to choose a control layout. For this guide, please select "Full Screen". 2 | 3 | //TODO: Layouts section of the docs to cover other options 4 | 5 | p You'll then be asked to enter some details about your project; feel free to fill these in. This is your chance to explain what the project is about. 6 | 7 | p Once you've clicked "Create My Project", the CDK will spin up a new project for you. This will download the template and install its dependencies. This will take a few moments. Behind the scenes here we're setting up your new project as a standard #[a(href='https://docs.npmjs.com/getting-started/creating-node-modules' target='_blank') NPM Module] which is built using #[a(href='https://webpack.js.org/' target='_blank') Webpack]. 8 | 9 | p The CDK will tell you when the installation completes, and give you the option to open your project's folder in an IDE (if it can detect one) or in your file browser. You can also do this at any time by clicking your project name in the top action bar of the CDK and selecting "Open Folder". 10 | 11 | h3(id=`${prefix}-login`) Login 12 | 13 | p In order for some parts of the CDK to work such as uploading or publishing you need to login to the tool with your Mixer user account. We recommend you do this now so that it is done and you don't need to to worry about it. Click the "Not Logged In" Icon in the top right of the CDK to begin the login process. 14 | -------------------------------------------------------------------------------- /src/reference/interactive_next/internationalization/plural-example.json: -------------------------------------------------------------------------------- 1 | { 2 | displayScore: "You have {count, plural, \ 3 | =0 {no points} \ 4 | =1 {one point} \ 5 | =2 {a couple points} \ 6 | other {# points}}" 7 | } 8 | -------------------------------------------------------------------------------- /src/reference/interactive_next/internationalization/score-counter-demo.jsx: -------------------------------------------------------------------------------- 1 | import { Component, h } from 'preact'; 2 | import { Translate } from './alchemy/preact'; 3 | 4 | class ScoreBoard extends Component { 5 | 6 | // ... 7 | 8 | render() { 9 | return
10 | 11 |
; 12 | } 13 | 14 | // ... 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/reference/interactive_next/internationalization/translation-demo.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'preact'; 2 | import { PreactControl, Translate } from './alchemy/preact'; 3 | 4 | class MyButton extends PreactControl { 5 | render() { 6 | return 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/reference/interactive_next/quickstart-html/basic.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* global mixer */ 3 | window.addEventListener('load', function initMixer () { 4 | // Move the video by a static offset amount 5 | const offset = 50; 6 | mixer.display.moveVideo({ 7 | top: offset, 8 | bottom: offset, 9 | left: offset, 10 | right: offset, 11 | }); 12 | 13 | // Whenever someone clicks on "Hello World", we'll send an event 14 | // to the game client on the control ID "hello-world" 15 | document.getElementById('hello-world').onclick = function (event) { 16 | mixer.socket.call('giveInput', { 17 | controlID: 'hello-world', 18 | event: 'click', 19 | button: event.button, 20 | }); 21 | }; 22 | 23 | mixer.isLoaded(); 24 | }); 25 | /* eslint-disable eol-last */ 26 | -------------------------------------------------------------------------------- /src/reference/interactive_next/quickstart-html/make-a-change-01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | My Awesome Interactive Integration 7 | 8 | 9 | 10 |
11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/reference/interactive_next/quickstart-html/make-a-change-02.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* global mixer */ 3 | /* eslint-disable no-unused-vars */ 4 | function handleVideoResized (position) { 5 | const overlay = document.getElementById('overlay'); 6 | const player = position.connectedPlayer; 7 | overlay.style.top = `${player.top}px`; 8 | overlay.style.left = `${player.left}px`; 9 | overlay.style.height = `${player.height}px`; 10 | overlay.style.width = `${player.width}px`; 11 | } 12 | /* eslint-disable eol-last */ -------------------------------------------------------------------------------- /src/reference/interactive_next/quickstart-html/make-a-change-03.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* eslint-disable no-undef */ 3 | /* global mixer */ 4 | mixer.display.position().subscribe(handleVideoResized); 5 | /* eslint-disable eol-last */ -------------------------------------------------------------------------------- /src/reference/interactive_next/quickstart-html/make-a-change-04.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | /* eslint-disable no-use-before-define */ 3 | /* global mixer */ 4 | window.addEventListener('load', function initMixer () { 5 | mixer.display.position().subscribe(handleVideoResized); 6 | 7 | // Move the video by a static offset amount 8 | const offset = 50; 9 | mixer.display.moveVideo({ 10 | top: offset, 11 | bottom: offset, 12 | left: offset, 13 | right: offset, 14 | }); 15 | 16 | // Whenever someone clicks on "Hello World", we'll send an event 17 | // to the game client on the control ID "hello-world" 18 | document.getElementById('hello-world').onclick = function (event) { 19 | mixer.socket.call('giveInput', { 20 | controlID: 'hello-world', 21 | event: 'click', 22 | button: event.button, 23 | }); 24 | }; 25 | 26 | mixer.isLoaded(); 27 | }); 28 | 29 | function handleVideoResized (position) { 30 | const overlay = document.getElementById('overlay'); 31 | const player = position.connectedPlayer; 32 | overlay.style.top = `${player.top}px`; 33 | overlay.style.left = `${player.left}px`; 34 | overlay.style.height = `${player.height}px`; 35 | overlay.style.width = `${player.width}px`; 36 | } 37 | /* eslint-disable eol-last */ 38 | -------------------------------------------------------------------------------- /src/reference/interactive_next/writing-clients.pug: -------------------------------------------------------------------------------- 1 | extends ./page.pug 2 | 3 | block reference 4 | p This page serves as a guideline for writing game client libraries SDKs for Interactive with awareness for the flexibility lent by Custom Controls. Specifics may vary between languages. This guide is meant to be descriptive of best practices and patterns, not prescriptive. Where relevant, examples are given from our own SDKs and codebases. 5 | 6 | .alert.alert-info If you haven't already, check out the #[a(href='/reference/interactive/protocol/protocol.pdf') protocol specification]. This guide will make references to it. 7 | 8 | h2#state-model State Model 9 | 10 | p Resources are the basis of Interactive. Currently there are four resource types: groups, participants, scenes, and controls. Resources have common behaviors. Most are created, updated, and deleted in approximately the same way, and all resources allow the user to store and update custom properties. They all have unique ID, a memory footprint (accessible via the #[code getMemoryStats] method), and are JSON serializable with the same base algorithms. It makes sense to derive this behavior in a common way through inheritance or composition. 11 | 12 | p Event emitters are often used on resources so that consumers can listen to updates and deletions on specific resource instances. 13 | 14 | p Various languages necessitate different approaches to dynamic, user-defined properties, and these can often be challenging to implement. In loosely or dynamically typed languages like the Python SDK, properties can each be attached and detached from resource instances on the fly. In statically typed object-oriented languages like Java SDK, it usually makes sense to provide a generic base Resource that consumers can inherit from, along with a mechanism for the consumer to pass that class to some kind of registry. 15 | 16 | h2#talking-to-interactive Talking to Interactive 17 | 18 | p Most callable methods on the protocol allow for bulk changes to be made. Using these, when possible, reduces the amount of network and computation load for the user's computer. Depending on practices in the language you're using, it may make sense to automatically bulk updates on behalf of the developer. For example, a JavaScript client library might defer sending control updates until the next event loop tick so that it's able to automatically bundle any synchronous updates that the developer makes together. 19 | 20 | p Many Interactive methods are fire-and-forget methods. In the protocol document, methods which cannot return an error are called with #[code discard:true] in their examples and don't have any listed "Unsuccessful Replies". For these methods, you need not wait for confirmation from the service; unless the user's connection drops before they arrive, they will be successful. 21 | 22 | p The Interactive protocol provides compression algorithms you can use in the Interactive protocol. Using these can significantly reduce bandwidth consumption at the cost of increased CPU load. Whether to enable these is a choice that should ultimately be left up to the consumer of your SDK, but the ability to switch between protocols should not be omitted. 23 | 24 | h2#receiving-input Receiving Input 25 | 26 | p It's important to remember that input you get from the service should be treated as untrusted. The service does some minimal validation on it—messages that are too large are rejected, messages that don't adhere it its variant of JSON (yes, there are variations, #[a(href='http://seriot.ch/parsing_json.php') "Parsing JSON is a Minefield"]) are rejected, inputs that don't have certain properties on it like the #[code controlID] are thrown away—but it is loose by necessity. It cannot predict and validate every possible input that a custom control might give, and these inputs are given from untrusted user machines. Therefore, as tempting as it may seem, don't create magic wrappers that allow custom controls to call or set any property on any game object, don't assume that a certain ID will fit into your 64-byte #[code char *]. Validate! 27 | 28 | p You should also take care to protect the game from too much valid input. If a large streamer picks up your game and interacts with an audience of tens or hundreds of thousands, you don't want the game to fall over. The Interactive service provides a mean to throttle input by event name in the #[code setBandwidthThrottle]. It usually does not make sense for the SDK to handle dynamic throttling itself but the consumer should be able to change them readily. Reasonable default throttles are a good idea to protect game developers who may not anticipate those massive load spikes; average bandwidth in the United States is, at the time of writing, in the mid-3 mbps range. You can also squelch an event entirely by setting its throttle to zero. 29 | -------------------------------------------------------------------------------- /src/reference/reference_layout.pug: -------------------------------------------------------------------------------- 1 | extends ../layout.pug 2 | 3 | block navbar 4 | .navbar-wrapper 5 | .container 6 | nav.navbar.navbar-inverse: +navbar() 7 | h1 Reference Documentation 8 | 9 | block content 10 | .container: .row 11 | .col-md-3 12 | block menu 13 | .col-md-9(role='main') 14 | block reference 15 | -------------------------------------------------------------------------------- /src/reference/teststreams/index.pug: -------------------------------------------------------------------------------- 1 | extends ../reference_layout.pug 2 | 3 | block title 4 | | Test Streams | Mixer Developers 5 | 6 | block navbar 7 | .navbar-wrapper 8 | .container 9 | nav.navbar.navbar-inverse: +navbar() 10 | h1 Test Streams 11 | block menu 12 | .hidden-print.rest-sidebar(role='complementary') 13 | nav 14 | ul.nav.nav-stacked 15 | li: a(href='#introduction') Introduction 16 | li: a(href='#applying') Applying for Test Stream Access 17 | li: a(href='#enabling') Enabling Test Stream Mode 18 | li: a(href='#controlling') Controlling Access 19 | li: a(href='#best-practices') Best Practices 20 | block reference 21 | 22 | h2#introduction Introduction 23 | 24 | p Test streams allow you to broadcast your content in private. This means if you are working on an unannounced game or just want to try out some new streaming settings then you can use test streams to broadcast without anyone being able to see your content or broadcast. 25 | 26 | h2#applying Applying for Test Stream Access 27 | p Test Streams is currently released to a limited audience. You can apply to use test streams with the following steps: 28 | ol 29 | li Visit the #[a(href='https://mixer.com/lab') Mixer Developer Lab] 30 | li Click the Test Streams tab 31 | li Fill out the displayed form and click Send Request 32 | p Once the form is filled in it will be reviewed by Mixer staff who will notify you when the feature is enabled. 33 | 34 | h2#enabling Enabling Test Stream Mode 35 | 36 | p Once you've been granted access, to enable test stream mode: 37 | ol 38 | li Open the settings pane by clicking your profile picture in the upper, right of the mixer.com website. 39 | li Go to Manage Channel in the Setting pane. 40 | li In the Test Streams section, toggle Enable Test Mode. 41 | li Once you have enabled test stream mode you will see a bar on the top whenever you broadcast. 42 | li This bar will also contain a link you can share with others to view your stream. 43 | 44 | .figure 45 | img(src='/img/reference/teststreams/enable.png' alt='Picture showing how to enable test streams') 46 | 47 | p Once you have enabled test streams for an account, it is enabled for every broadcast, across all devices. You don't need to enable test streams for each device you broadcast from. There is one, global setting applied to the account. 48 | 49 | h2#controlling Controlling Access 50 | p Whilst in test stream mode there are two ways to grant viewers access to view your test stream. 51 | ol 52 | li By Making them a moderator in the channel - Moderators of your channel can see your test streams 53 | li By Sharing out the access key link - At the top of your channel page whilst streaming in test stream mode is a banner which has a link in it. You can give this link out and those with the link will be able to access your test stream. This link resets for every stream. 54 | 55 | h2#best-practices Best Practices 56 | ul 57 | li Use the #[code /clear] command at the end of the stream to clear chat 58 | li Keep your access key / link secure. If you need to reset it, restart the broadcast to generate a new one. 59 | -------------------------------------------------------------------------------- /src/reference/webhooks/curl_request.sh: -------------------------------------------------------------------------------- 1 | curl -XPOST https://mixer.com/api/v1/hooks \ 2 | -H Client-ID:da400f1a81d7efc477920b5d686e95be6f92c88af09c2342 \ 3 | -H Authorization:'Secret c51ff3c4e44e7be32be2f639b28e3569f1775f8530b95a5d972ace2cb9310ab8' \ 4 | -d '{ "kind": "web", "events":["channel:314:broadcast"], "url":"https://dev.mixer.com/onHook" }' 5 | -------------------------------------------------------------------------------- /src/reference/webhooks/example_full_request.txt: -------------------------------------------------------------------------------- 1 | HTTP/1.1 POST 2 | Host: test.mixer.com:8888 3 | Content-Type: application/json; charset=utf-8 4 | Poker-Nth-retry: 0 5 | Poker-Hook-Id: 6cd6fa9f-b9a5-45b6-bdf0-412fe7859dad 6 | Poker-Signature: sha384=5EB3E48ED381446210D527AA1D88D9A5F36C840DD088665F35BEA51D3FA429837430E81973835774CC0AE69EEDE6AAE7 7 | Content-Length: 183 8 | Connection: Keep-Alive 9 | 10 | {"event":"channel:314:update","id":"96445358-d5b1-417e-a9ac-57f1cb001916","payload":{"broacastId":"9976edaf-c327-4560-a1cb-89425cb1131f"},"sentAt":"2018-02-08T03:28:06.8605874+00:00"} 11 | -------------------------------------------------------------------------------- /src/reference/webhooks/example_hook.json: -------------------------------------------------------------------------------- 1 | { 2 | "event": "channel:314:broadcast", 3 | "payload": { 4 | "broadcastId": "9976edaf-c327-4560-a1cb-89425cb1131f" 5 | }, 6 | "sentAt": "2018-02-07T01:36:30.5109036+00:00", 7 | "id": "7ddc8489-5afd-401d-83cc-49ea95fc9bba" 8 | } 9 | -------------------------------------------------------------------------------- /src/reference/webhooks/example_registered.json: -------------------------------------------------------------------------------- 1 | { 2 | "deactivationReason": null, 3 | "events": [ 4 | "channel:314:broadcast" 5 | ], 6 | "id": "875829e3-52c5-4976-a053-2861d8c3bc79", 7 | "isActive": true, 8 | "kind": "web", 9 | "url": "https://dev.mixer.com/onHook", 10 | "expiresAt": "2018-05-17T00:30:06.1751718+00:00", 11 | "renewUrl": "https://mixer.com/api/v1/hooks/875829e3-52c5-4976-a053-2861d8c3bc79/renew" 12 | } 13 | -------------------------------------------------------------------------------- /src/reference/webhooks/verify.cs: -------------------------------------------------------------------------------- 1 | // Using asp.net style requests, you may need to adjust it :) 2 | public bool IsRequestValid(IHttpContext context, string secret, string body) { 3 | var hmac = new HMACSHA384(Encoding.UTF8.GetBytes(secret)); 4 | var hash = BitConverter.ToString(hmac.ComputeHash(Encoding.UTF8.GetBytes(body))).Replace("-", string.Empty); 5 | return context.HttpContext.Request.Headers["Poker-Signature"].Equals($"sha384={hash}"); 6 | } 7 | -------------------------------------------------------------------------------- /src/reference/webhooks/verify.go: -------------------------------------------------------------------------------- 1 | func IsRequestValid(r *http.Request, secret, body []byte) bool { 2 | mac := hmac.New(sha512.New384, secret) 3 | mac.Write(body) 4 | actual := []byte("sha384=" + strings.ToUpper(hex.EncodeToString(mac.Sum(nil)))) 5 | return hmac.Equal([]byte(r.Header.Get("Poker-Signature")), actual) 6 | } 7 | -------------------------------------------------------------------------------- /src/reference/webhooks/verify.js.txt: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | function isRequestValid(req, secret, body) { 4 | const hmac = crypto.createHmac('SHA384', secret); 5 | hmac.update(body); 6 | const digest = hmac.digest('hex').toUpperCase(); 7 | return req.headers['poker-signature'] === `sha384=${digest}`; 8 | } 9 | -------------------------------------------------------------------------------- /src/reference/webhooks/verify.php: -------------------------------------------------------------------------------- 1 | () { 3 | public void onSuccess(AuthenticationReply reply) { 4 | chatConnectable.send(ChatSendMethod.of("Hello World!")); 5 | } 6 | public void onFailure(Throwable var1) { 7 | var1.printStackTrace(); 8 | } 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /src/tutorials/code/java/chatbot/4.java: -------------------------------------------------------------------------------- 1 | //... 2 | chatConnectable.on(UserJoinEvent.class, event -> { 3 | chatConnectable.send(ChatSendMethod.of( 4 | String.format("Hi %s! I'm pingbot! Write !ping and I will pong back!", 5 | event.data.username))); 6 | }); 7 | //... 8 | -------------------------------------------------------------------------------- /src/tutorials/code/java/chatbot/5.java: -------------------------------------------------------------------------------- 1 | //... 2 | chatConnectable.on(IncomingMessageEvent.class, event -> { 3 | if (event.data.message.message.get(0).text.startsWith("!ping")) { 4 | chatConnectable.send(ChatSendMethod.of(String.format("@%s PONG!",event.data.userName))); 5 | } 6 | }); 7 | //... 8 | -------------------------------------------------------------------------------- /src/tutorials/code/java/chatbot/6.java: -------------------------------------------------------------------------------- 1 | import com.mixer.api.MixerAPI; 2 | import com.mixer.api.resource.MixerUser; 3 | import com.mixer.api.resource.chat.MixerChat; 4 | import com.mixer.api.resource.chat.events.IncomingMessageEvent; 5 | import com.mixer.api.resource.chat.events.UserJoinEvent; 6 | import com.mixer.api.resource.chat.methods.AuthenticateMessage; 7 | import com.mixer.api.resource.chat.methods.ChatSendMethod; 8 | import com.mixer.api.resource.chat.replies.AuthenticationReply; 9 | import com.mixer.api.resource.chat.replies.ReplyHandler; 10 | import com.mixer.api.resource.chat.ws.MixerChatConnectable; 11 | import com.mixer.api.services.impl.ChatService; 12 | import com.mixer.api.services.impl.UsersService; 13 | 14 | import java.util.concurrent.ExecutionException; 15 | 16 | public class Chat { 17 | public static void main(String[] args) throws ExecutionException, InterruptedException { 18 | MixerAPI mixer = new MixerAPI("CLIENT_ID", "AUTH_TOKEN"); 19 | 20 | MixerUser user = mixer.use(UsersService.class).getCurrent().get(); 21 | MixerChat chat = mixer.use(ChatService.class).findOne(user.channel.id).get(); 22 | MixerChatConnectable chatConnectable = chat.connectable(mixer); 23 | 24 | if (chatConnectable.connect()) { 25 | chatConnectable.send(AuthenticateMessage.from(user.channel, user, chat.authkey), new ReplyHandler() { 26 | public void onSuccess(AuthenticationReply reply) { 27 | chatConnectable.send(ChatSendMethod.of("Hello World!")); 28 | } 29 | public void onFailure(Throwable var1) { 30 | var1.printStackTrace(); 31 | } 32 | }); 33 | } 34 | 35 | chatConnectable.on(IncomingMessageEvent.class, event -> { 36 | if (event.data.message.message.get(0).text.startsWith("!ping")) { 37 | chatConnectable.send(ChatSendMethod.of(String.format("@%s PONG!",event.data.userName))); 38 | } 39 | }); 40 | 41 | chatConnectable.on(UserJoinEvent.class, event -> { 42 | chatConnectable.send(ChatSendMethod.of( 43 | String.format("Hi %s! I'm pingbot! Write !ping and I will pong back!", 44 | event.data.username))); 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/tutorials/code/java/interactive/1.java: -------------------------------------------------------------------------------- 1 | package pro.beam.interactive.example; 2 | 3 | import pro.beam.api.BeamAPI; 4 | import pro.beam.interactive.net.packet.Protocol; 5 | import pro.beam.interactive.robot.RobotBuilder; 6 | 7 | import java.awt.*; 8 | import java.util.concurrent.ExecutionException; 9 | 10 | public class Main { 11 | 12 | public static void main(String[] args) throws AWTException { 13 | BeamAPI beam = new BeamAPI("AUTH_TOKEN"); //An instance of the BeamAPI from beam-client-java 14 | Robot controller = new Robot(); // An AWT Robot, Not a Beam Robot 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tutorials/code/java/interactive/2.java: -------------------------------------------------------------------------------- 1 | pro.beam.interactive.robot.Robot robot = new RobotBuilder() 2 | .channel(user.channel) 3 | .build(beam, false) 4 | .get(); 5 | -------------------------------------------------------------------------------- /src/tutorials/code/java/interactive/3.java: -------------------------------------------------------------------------------- 1 | robot.on(Protocol.Report.class, report -> { 2 | // If we have any joysticks in the report 3 | if (report.getJoystickCount() > 0) { 4 | // Get the coordMean from the joystick 5 | Protocol.Coordinate coordMean = report.getJoystick(0).getCoordMean(); 6 | Point mousePosition = MouseInfo.getPointerInfo().getLocation(); 7 | 8 | // Apply it to the current mouse position, if its values are not NaN 9 | if (!Double.isNaN(coordMean.getX()) && !Double.isNaN(coordMean.getY())) { 10 | controller.mouseMove( 11 | ((int) (mousePosition.getX() + 300 * coordMean.getX())), 12 | ((int) (mousePosition.getY() + 300 * coordMean.getY())) 13 | ); 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/tutorials/code/java/interactive/4.java: -------------------------------------------------------------------------------- 1 | package pro.beam.interactive.example; 2 | 3 | import pro.beam.api.BeamAPI; 4 | import pro.beam.interactive.net.packet.Protocol; 5 | import pro.beam.interactive.robot.RobotBuilder; 6 | 7 | import java.awt.*; 8 | 9 | import java.util.concurrent.ExecutionException; 10 | 11 | public class Main { 12 | 13 | public static void main(String[] args) throws AWTException { 14 | BeamAPI beam = new BeamAPI("AUTH_TOKEN"); 15 | Robot controller = new Robot(); 16 | BeamUser user = beam.use(UsersService.class).getCurrent().get(); 17 | try { 18 | pro.beam.interactive.robot.Robot robot = new RobotBuilder() 19 | .channel(user.channel) 20 | .build(beam, false) 21 | .get(); 22 | 23 | robot.on(Protocol.Report.class, report -> { 24 | // If we have any joysticks in the report 25 | if (report.getJoystickCount() > 0) { 26 | // Get the coordMean from the joystick 27 | Protocol.Coordinate coordMean = report.getJoystick(0).getCoordMean(); 28 | Point mousePosition = MouseInfo.getPointerInfo().getLocation(); 29 | 30 | // Apply it to the current mouse position, if its values are not NaN 31 | if (!Double.isNaN(coordMean.getX()) && !Double.isNaN(coordMean.getY())) { 32 | controller.mouseMove( 33 | ((int) (mousePosition.getX() + 300 * coordMean.getX())), 34 | ((int) (mousePosition.getY() + 300 * coordMean.getY())) 35 | ); 36 | } 37 | } 38 | }); 39 | 40 | } catch (InterruptedException | ExecutionException e) { 41 | e.printStackTrace(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/tutorials/code/java/rest/1.java: -------------------------------------------------------------------------------- 1 | import com.mixer.api.MixerAPI; 2 | import com.mixer.api.http.SortOrderMap; 3 | import com.mixer.api.resource.MixerUser; 4 | import com.mixer.api.resource.channel.MixerChannel; 5 | import com.mixer.api.response.channels.ShowChannelsResponse; 6 | import com.mixer.api.services.impl.ChannelsService; 7 | import com.mixer.api.services.impl.UsersService; 8 | 9 | import java.util.concurrent.ExecutionException; 10 | 11 | public class Tutorial { 12 | public static MixerAPI mixer; 13 | 14 | public static void main(String[] args) throws ExecutionException, InterruptedException { 15 | mixer = new MixerAPI("CLIENT_ID"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/tutorials/code/java/rest/2.java: -------------------------------------------------------------------------------- 1 | //... 2 | MixerChannel channel = mixer.use(ChannelsService.class).findOneByToken(args[0]).get(); 3 | 4 | int viewers = user.channel.viewersTotal; 5 | System.out.format("You have %d total viewers...\n", viewers); 6 | 7 | run(0,viewers,1); 8 | //... 9 | -------------------------------------------------------------------------------- /src/tutorials/code/java/rest/3.java: -------------------------------------------------------------------------------- 1 | //... 2 | public static ShowChannelsResponse getChannelsPage(int page) throws ExecutionException,InterruptedException { 3 | SortOrderMap map = new SortOrderMap<>(); 4 | map.put(ShowChannelsResponse.Attributes.VIEWERS_TOTAL, ShowChannelsResponse.Ordering.DESCENDING); 5 | return mixer.use(ChannelsService.class).show(map,page,100).get(); 6 | } 7 | //... 8 | -------------------------------------------------------------------------------- /src/tutorials/code/java/rest/4.java: -------------------------------------------------------------------------------- 1 | public static int run(int page, int viewers, int rank) throws ExecutionException,InterruptedException { 2 | ShowChannelsResponse channels = getChannelsPage(page); 3 | for (int i = 0; i < channels.size(); i++) { 4 | MixerChannel channel = channels.get(i); 5 | if (channel.viewersTotal <= viewers) { 6 | System.out.format("Your rank on Mixer is %d!\n", rank); 7 | return rank; 8 | } 9 | System.out.format("Your rank is at least %d...\n", rank); 10 | rank++; 11 | } 12 | return run(page + 1, viewers, rank); 13 | } 14 | -------------------------------------------------------------------------------- /src/tutorials/code/java/rest/5.java: -------------------------------------------------------------------------------- 1 | import com.mixer.api.MixerAPI; 2 | import com.mixer.api.http.SortOrderMap; 3 | import com.mixer.api.resource.channel.MixerChannel; 4 | import com.mixer.api.response.channels.ShowChannelsResponse; 5 | import com.mixer.api.services.impl.ChannelsService; 6 | 7 | import java.util.concurrent.ExecutionException; 8 | 9 | public class Tutorial { 10 | public static MixerAPI mixer; 11 | 12 | public static void main(String[] args) throws ExecutionException, InterruptedException { 13 | mixer = new MixerAPI("CLIENT_ID"); 14 | 15 | MixerChannel channel = mixer.use(ChannelsService.class).findOneByToken(args[0]).get(); 16 | 17 | int viewers = channel.viewersTotal; 18 | System.out.format("You have %d total viewers...\n", viewers); 19 | 20 | run(0,viewers,1); 21 | 22 | 23 | } 24 | public static int run(int page, int viewers, int rank) throws ExecutionException,InterruptedException { 25 | ShowChannelsResponse channels = getChannelsPage(page); 26 | for (int i = 0; i < channels.size(); i++) { 27 | MixerChannel channel = channels.get(i); 28 | if (channel.viewersTotal <= viewers) { 29 | System.out.format("Your rank on Mixer is %d!\n", rank); 30 | return rank; 31 | } 32 | System.out.format("Your rank is at least %d...\n", rank); 33 | rank++; 34 | } 35 | return run(page + 1, viewers, rank); 36 | } 37 | public static ShowChannelsResponse getChannelsPage(int page) throws ExecutionException,InterruptedException { 38 | SortOrderMap map = new SortOrderMap<>(); 39 | map.put(ShowChannelsResponse.Attributes.VIEWERS_TOTAL, ShowChannelsResponse.Ordering.DESCENDING); 40 | return mixer.use(ChannelsService.class).show(map,page,100).get(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/tutorials/code/java/rest/5_response.txt: -------------------------------------------------------------------------------- 1 | You have 2279 total viewers... 2 | Your rank is at least 1... 3 | Your rank is at least 2... 4 | ... 5 | Your rank is at least 281... 6 | Your rank is at least 282... 7 | Your rank is at least 283... 8 | Your rank is at least 284... 9 | Your rank on Mixer is 285! 10 | -------------------------------------------------------------------------------- /src/tutorials/code/node/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": 0, 4 | "import/no-unresolved": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/tutorials/code/node/chatbot/1.js: -------------------------------------------------------------------------------- 1 | const Mixer = require('@mixer/client-node'); 2 | const ws = require('ws'); 3 | 4 | let userInfo; 5 | 6 | const client = new Mixer.Client(new Mixer.DefaultRequestRunner()); 7 | 8 | // With OAuth we don't need to log in. The OAuth Provider will attach 9 | // the required information to all of our requests after this call. 10 | client.use(new Mixer.OAuthProvider(client, { 11 | tokens: { 12 | access: 'AUTH_TOKEN', 13 | expires: Date.now() + (365 * 24 * 60 * 60 * 1000) 14 | }, 15 | })); 16 | 17 | // Gets the user that the Access Token we provided above belongs to. 18 | client.request('GET', 'users/current') 19 | .then(response => { 20 | console.log(response.body); 21 | 22 | // Store the logged in user's details for later reference 23 | userInfo = response.body; 24 | 25 | // Returns a promise that resolves with our chat connection details. 26 | return new Mixer.ChatService(client).join(response.body.channel.id); 27 | }) 28 | .then(response => { 29 | const body = response.body; 30 | console.log(body); 31 | // TODO: Connect to chat, we'll do this in the next tutorial step :)! 32 | }) 33 | .catch(error => { 34 | console.error('Something went wrong.'); 35 | console.error(error); 36 | }); 37 | -------------------------------------------------------------------------------- /src/tutorials/code/node/chatbot/1_response.txt: -------------------------------------------------------------------------------- 1 | { endpoints: 2 | [ 'wss://chat1-dal.mixer.com:443', 3 | 'wss://chat2-dal.mixer.com:443' ], 4 | authkey: '1c0e251998ac7112f42c71a23d4b67b3', 5 | permissions: 6 | [ 'change_ban', 7 | 'edit_options', 8 | 'change_role', 9 | 'bypass_links', 10 | 'bypass_slowchat', 11 | 'remove_message', 12 | 'clear_messages', 13 | 'timeout', 14 | 'giveaway_start', 15 | 'poll_vote', 16 | 'poll_start', 17 | 'connect', 18 | 'chat' ] } 19 | -------------------------------------------------------------------------------- /src/tutorials/code/node/chatbot/2.js: -------------------------------------------------------------------------------- 1 | const Mixer = require('@mixer/client-node'); 2 | const ws = require('ws'); 3 | 4 | let userInfo; 5 | 6 | const client = new Mixer.Client(new Mixer.DefaultRequestRunner()); 7 | 8 | // With OAuth we don't need to log in. The OAuth Provider will attach 9 | // the required information to all of our requests after this call. 10 | client.use(new Mixer.OAuthProvider(client, { 11 | tokens: { 12 | access: 'AUTH_TOKEN', 13 | expires: Date.now() + (365 * 24 * 60 * 60 * 1000) 14 | }, 15 | })); 16 | 17 | // Gets the user that the Access Token we provided above belongs to. 18 | client.request('GET', 'users/current') 19 | .then(response => { 20 | console.log(response.body); 21 | 22 | // Store the logged in user's details for later reference 23 | userInfo = response.body; 24 | 25 | // Returns a promise that resolves with our chat connection details. 26 | return new Mixer.ChatService(client).join(response.body.channel.id); 27 | }) 28 | .then(response => { 29 | const body = response.body; 30 | console.log(body); 31 | return createChatSocket(userInfo.id, userInfo.channel.id, body.endpoints, body.authkey); 32 | }) 33 | .catch(error => { 34 | console.error('Something went wrong.'); 35 | console.error(error); 36 | }); 37 | 38 | /** 39 | * Creates a Mixer chat socket and sets up listeners to various chat events. 40 | * @param {number} userId The user to authenticate as 41 | * @param {number} channelId The channel id to join 42 | * @param {string[]} endpoints An array of endpoints to connect to 43 | * @param {string} authkey An authentication key to connect with 44 | * @returns {Promise.<>} 45 | */ 46 | function createChatSocket (userId, channelId, endpoints, authkey) { 47 | const socket = new Mixer.Socket(ws, endpoints).boot(); 48 | 49 | // You don't need to wait for the socket to connect before calling 50 | // methods. We spool them and run them when connected automatically. 51 | socket.auth(channelId, userId, authkey) 52 | .then(() => { 53 | console.log('You are now authenticated!'); 54 | // Send a chat message 55 | return socket.call('msg', ['Hello world!']); 56 | }) 57 | .catch(error => { 58 | console.error('Oh no! An error occurred.'); 59 | console.error(error); 60 | }); 61 | 62 | // Listen for chat messages. Note you will also receive your own! 63 | socket.on('ChatMessage', data => { 64 | console.log('We got a ChatMessage packet!'); 65 | console.log(data); 66 | console.log(data.message); // Let's take a closer look 67 | }); 68 | 69 | // Listen for socket errors. You will need to handle these here. 70 | socket.on('error', error => { 71 | console.error('Socket error'); 72 | console.error(error); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /src/tutorials/code/node/chatbot/2_response.txt: -------------------------------------------------------------------------------- 1 | You are now authenticated! 2 | We got a ChatMessage packet! 3 | { channel: 131630, 4 | id: '9bc8a940-326a-11e6-9af9-8d8f189ce625', 5 | user_name: 'your_username', 6 | user_id: , 7 | user_roles: [ 'Owner' ], 8 | message: { message: [ [Object] ], meta: {} } } 9 | { message: [ { type: 'text', data: 'Hello world!', text: 'Hello world!' } ], 10 | meta: {} } 11 | -------------------------------------------------------------------------------- /src/tutorials/code/node/chatbot/3.js: -------------------------------------------------------------------------------- 1 | const Mixer = require('@mixer/client-node'); 2 | const ws = require('ws'); 3 | 4 | let userInfo; 5 | 6 | const client = new Mixer.Client(new Mixer.DefaultRequestRunner()); 7 | 8 | // With OAuth we don't need to log in. The OAuth Provider will attach 9 | // the required information to all of our requests after this call. 10 | client.use(new Mixer.OAuthProvider(client, { 11 | tokens: { 12 | access: 'AUTH_TOKEN', 13 | expires: Date.now() + (365 * 24 * 60 * 60 * 1000) 14 | }, 15 | })); 16 | 17 | // Gets the user that the Access Token we provided above belongs to. 18 | client.request('GET', 'users/current') 19 | .then(response => { 20 | userInfo = response.body; 21 | return new Mixer.ChatService(client).join(response.body.channel.id); 22 | }) 23 | .then(response => { 24 | const body = response.body; 25 | return createChatSocket(userInfo.id, userInfo.channel.id, body.endpoints, body.authkey); 26 | }) 27 | .catch(error => { 28 | console.error('Something went wrong.'); 29 | console.error(error); 30 | }); 31 | 32 | /** 33 | * Creates a Mixer chat socket and sets up listeners to various chat events. 34 | * @param {number} userId The user to authenticate as 35 | * @param {number} channelId The channel id to join 36 | * @param {string[]} endpoints An array of endpoints to connect to 37 | * @param {string} authkey An authentication key to connect with 38 | * @returns {Promise.<>} 39 | */ 40 | function createChatSocket (userId, channelId, endpoints, authkey) { 41 | // Chat connection 42 | const socket = new Mixer.Socket(ws, endpoints).boot(); 43 | 44 | // Greet a joined user 45 | socket.on('UserJoin', data => { 46 | socket.call('msg', [`Hi ${data.username}! I'm pingbot! Write !ping and I will pong back!`]); 47 | }); 48 | 49 | // React to our !pong command 50 | socket.on('ChatMessage', data => { 51 | if (data.message.message[0].data.toLowerCase().startsWith('!ping')) { 52 | socket.call('msg', [`@${data.user_name} PONG!`]); 53 | console.log(`Ponged ${data.user_name}`); 54 | } 55 | }); 56 | 57 | // Handle errors 58 | socket.on('error', error => { 59 | console.error('Socket error'); 60 | console.error(error); 61 | }); 62 | 63 | return socket.auth(channelId, userId, authkey) 64 | .then(() => { 65 | console.log('Login successful'); 66 | return socket.call('msg', ['Hi! I\'m pingbot! Write !ping and I will pong back!']); 67 | }); 68 | } 69 | -------------------------------------------------------------------------------- /src/tutorials/code/node/constellation/1.js: -------------------------------------------------------------------------------- 1 | const Carina = require('carina').Carina; 2 | const ws = require('ws'); 3 | 4 | Carina.WebSocket = ws; 5 | 6 | const channelId = 1234; 7 | -------------------------------------------------------------------------------- /src/tutorials/code/node/constellation/2.js: -------------------------------------------------------------------------------- 1 | const ca = new Carina({ 2 | queryString: { 3 | 'Client-ID': 'CLIENT_ID', 4 | }, 5 | isBot: true, 6 | }).open(); 7 | -------------------------------------------------------------------------------- /src/tutorials/code/node/constellation/3.js: -------------------------------------------------------------------------------- 1 | ca.subscribe(`channel:${channelId}:update`, data => { 2 | // ... 3 | }); 4 | -------------------------------------------------------------------------------- /src/tutorials/code/node/constellation/4.js: -------------------------------------------------------------------------------- 1 | ca.subscribe(`channel:${channelId}:update`, data => { 2 | console.log(data); 3 | }); 4 | -------------------------------------------------------------------------------- /src/tutorials/code/node/constellation/5.js: -------------------------------------------------------------------------------- 1 | const Carina = require('carina').Carina; 2 | const ws = require('ws'); 3 | 4 | Carina.WebSocket = ws; 5 | 6 | const channelId = 1234; 7 | 8 | const ca = new Carina({ 9 | queryString: { 10 | 'Client-ID': 'CLIENT_ID', 11 | }, 12 | isBot: true, 13 | }).open(); 14 | 15 | ca.subscribe(`channel:${channelId}:update`, data => { 16 | console.log(data); 17 | }); 18 | -------------------------------------------------------------------------------- /src/tutorials/code/node/constellation/example_data.js: -------------------------------------------------------------------------------- 1 | { name: 'test' } 2 | -------------------------------------------------------------------------------- /src/tutorials/code/node/interactive/1.js: -------------------------------------------------------------------------------- 1 | const Beam = require('@mixer/client-node'); 2 | const Interactive = require('beam-interactive-node'); 3 | const rjs = require('robotjs'); 4 | 5 | const channelId = 1234; 6 | -------------------------------------------------------------------------------- /src/tutorials/code/node/interactive/2.js: -------------------------------------------------------------------------------- 1 | const beam = new Beam(); 2 | 3 | beam.use('oauth', { 4 | tokens: { 5 | access: 'AUTH_TOKEN', 6 | expires: Date.now() + (365 * 24 * 60 * 60 * 1000) 7 | }, 8 | }); 9 | 10 | beam.request('GET', `users/current`) 11 | .then(() => beam.game.join(channelId)) 12 | .then(res => createRobot(res)) 13 | .then(robot => performRobotHandShake(robot)) 14 | .then(robot => setupRobotEvents(robot)) 15 | .catch(err => { 16 | if (err.res) { 17 | throw new Error('Error connecting to Interactive:' + err.res.body.message); 18 | } 19 | throw new Error('Error connecting to Interactive', err); 20 | }); 21 | -------------------------------------------------------------------------------- /src/tutorials/code/node/interactive/2_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "wss://tetris2-dal.mixer.com", 3 | "key": "3l39jjgnhqf4kv4" 4 | } -------------------------------------------------------------------------------- /src/tutorials/code/node/interactive/3.js: -------------------------------------------------------------------------------- 1 | function createRobot(res, stream) { 2 | return new Interactive.Robot({ 3 | remote: res.body.address, 4 | channel: channelId, 5 | key: res.body.key, 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /src/tutorials/code/node/interactive/4.js: -------------------------------------------------------------------------------- 1 | function performRobotHandShake (robot) { 2 | return new Promise((resolve, reject) => { 3 | robot.handshake(err => { 4 | if (err) { 5 | reject(err); 6 | } 7 | resolve(robot); 8 | }); 9 | }); 10 | } 11 | 12 | function setupRobotEvents (robot) { 13 | robot.on('report', report => { 14 | const mouse = rjs.getMousePos(); 15 | if (report.joystick.length > 0) { 16 | const mean = report.joystick[0].coordMean; 17 | if (!isNaN(mean.x) && !isNaN(mean.y)) { 18 | rjs.moveMouse( 19 | Math.round(mouse.x + 300 * mean.x), 20 | Math.round(mouse.y + 300 * mean.y) 21 | ); 22 | } 23 | } 24 | }); 25 | robot.on('error', err => { 26 | throw new Error('There was an error in the Interactive connection', err); 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /src/tutorials/code/node/interactive/5.js: -------------------------------------------------------------------------------- 1 | const Beam = require('@mixer/client-node'); 2 | const Interactive = require('beam-interactive-node'); 3 | const rjs = require('robotjs'); 4 | 5 | const channelId = 1234; 6 | 7 | const beam = new Beam(); 8 | 9 | beam.use('oauth', { 10 | tokens: { 11 | access: 'AUTH_TOKEN', 12 | expires: Date.now() + (365 * 24 * 60 * 60 * 1000) 13 | }, 14 | }); 15 | 16 | beam.request('GET', `users/current`) 17 | .then(() => beam.game.join(channelId)) 18 | .then(res => createRobot(res)) 19 | .then(robot => performRobotHandShake(robot)) 20 | .then(robot => setupRobotEvents(robot)) 21 | .catch(err => { 22 | if (err.res) { 23 | throw new Error('Error connecting to Interactive:' + err.res.body.message); 24 | } 25 | throw new Error('Error connecting to Interactive', err); 26 | }); 27 | 28 | function createRobot (res) { 29 | return new Interactive.Robot({ 30 | remote: res.body.address, 31 | channel: channelId, 32 | key: res.body.key, 33 | }); 34 | } 35 | 36 | function performRobotHandShake (robot) { 37 | return new Promise((resolve, reject) => { 38 | robot.handshake(err => { 39 | if (err) { 40 | reject(err); 41 | } 42 | resolve(robot); 43 | }); 44 | }); 45 | } 46 | 47 | function setupRobotEvents (robot) { 48 | robot.on('report', report => { 49 | const mouse = rjs.getMousePos(); 50 | if (report.joystick.length > 0) { 51 | const mean = report.joystick[0].coordMean; 52 | if (!isNaN(mean.x) && !isNaN(mean.y)) { 53 | rjs.moveMouse( 54 | Math.round(mouse.x + 300 * mean.x), 55 | Math.round(mouse.y + 300 * mean.y) 56 | ); 57 | } 58 | } 59 | }); 60 | robot.on('error', err => { 61 | throw new Error('There was an error in the Interactive connection', err); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /src/tutorials/code/node/rest/1.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Mixer = require('@mixer/client-node'); 4 | 5 | const client = new Mixer.Client(new Mixer.DefaultRequestRunner()); 6 | -------------------------------------------------------------------------------- /src/tutorials/code/node/rest/2.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Mixer = require('@mixer/client-node'); 4 | 5 | const client = new Mixer.Client(new Mixer.DefaultRequestRunner()); 6 | 7 | const channelName = process.argv[2]; 8 | 9 | client.use(new Mixer.OAuthProvider(client, { 10 | clientId: 'CLIENT_ID', 11 | })); 12 | 13 | client.request('GET', `channels/${channelName}`) 14 | .then(res => { 15 | const viewers = res.body.viewersTotal; 16 | console.log(`You have ${viewers} total viewers...`); 17 | }); 18 | -------------------------------------------------------------------------------- /src/tutorials/code/node/rest/2_response.txt: -------------------------------------------------------------------------------- 1 | $ node rank.js connor4312 2 | You have 595 total viewers... 3 | -------------------------------------------------------------------------------- /src/tutorials/code/node/rest/3.js: -------------------------------------------------------------------------------- 1 | // ... 2 | let rank = 1; 3 | const run = (page) => { 4 | return getChannelsDescending(page).then(res => { 5 | for (let i = 0; i < res.body.length; i++) { 6 | const channel = res.body[i]; 7 | if (channel.viewersTotal <= viewers) { 8 | console.log(`Your rank on Mixer is ${rank}!`); 9 | return; 10 | } 11 | 12 | rank++; 13 | } 14 | 15 | console.log(`Your rank is at least ${rank}...`); 16 | return run(page + 1); 17 | }); 18 | }; 19 | 20 | return run(0); 21 | // ... 22 | -------------------------------------------------------------------------------- /src/tutorials/code/node/rest/4.js: -------------------------------------------------------------------------------- 1 | // ... 2 | const run = (page) => { 3 | return client.request('GET', '/channels', { 4 | qs: { 5 | page, 6 | fields: 'viewersTotal', 7 | order: 'viewersTotal:DESC', 8 | }, 9 | }).then(res => { 10 | // ... 11 | -------------------------------------------------------------------------------- /src/tutorials/code/node/rest/5.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Mixer = require('@mixer/client-node'); 4 | 5 | const client = new Mixer.Client(new Mixer.DefaultRequestRunner()); 6 | 7 | const channelName = process.argv[2]; 8 | 9 | client.use(new Mixer.OAuthProvider(client, { 10 | clientId: 'CLIENT_ID', 11 | })); 12 | 13 | client.request('GET', `channels/${channelName}`) 14 | .then(res => { 15 | const viewers = res.body.viewersTotal; 16 | console.log(`You have ${viewers} total viewers...`); 17 | 18 | let rank = 1; 19 | const run = (page) => { 20 | return client.request('GET', '/channels', { 21 | qs: { 22 | page, 23 | fields: 'viewersTotal', 24 | order: 'viewersTotal:DESC', 25 | }, 26 | }).then(res => { 27 | for (let i = 0; i < res.body.length; i++) { 28 | const channel = res.body[i]; 29 | if (channel.viewersTotal <= viewers) { 30 | console.log(`Your rank on Mixer is ${rank}!`); 31 | return; 32 | } 33 | 34 | rank++; 35 | } 36 | 37 | console.log(`Your rank is at least ${rank}...`); 38 | return run(page + 1); 39 | }); 40 | }; 41 | 42 | return run(0); 43 | }); 44 | -------------------------------------------------------------------------------- /src/tutorials/code/node/rest/5_response.txt: -------------------------------------------------------------------------------- 1 | $ node rank.js connor4312 l337hax0r 2 | You have 595 total viewers... 3 | Your rank is at least 51... 4 | Your rank is at least 101... 5 | Your rank is at least 151... 6 | Your rank is at least 201... 7 | Your rank is at least 251... 8 | Your rank is at least 301... 9 | Your rank is at least 351... 10 | Your rank is at least 401... 11 | Your rank is at least 451... 12 | Your rank is at least 501... 13 | Your rank is at least 551... 14 | Your rank is at least 601... 15 | Your rank is at least 651... 16 | Your rank is at least 701... 17 | Your rank is at least 751... 18 | Your rank on Mixer is 761! 19 | -------------------------------------------------------------------------------- /src/tutorials/code/python/rest/1.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | 4 | s = requests.Session() 5 | s.headers.update({'Client-ID': 'CLIENT_ID'}) 6 | 7 | channel_response = s.get('https://mixer.com/api/v1/channels/{}'.format(sys.argv[1])) 8 | -------------------------------------------------------------------------------- /src/tutorials/code/python/rest/2.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | 4 | s = requests.Session() 5 | s.headers.update({'Client-ID': 'CLIENT_ID'}) 6 | 7 | channel_response = s.get('https://mixer.com/api/v1/channels/{}'.format(sys.argv[1])) 8 | 9 | viewers = channel_response.json()['viewersTotal'] 10 | print("You have {} viewers...".format(viewers)) 11 | -------------------------------------------------------------------------------- /src/tutorials/code/python/rest/2_response.py: -------------------------------------------------------------------------------- 1 | $ python rank.py connor4312 2 | You have 595 total viewers... 3 | -------------------------------------------------------------------------------- /src/tutorials/code/python/rest/3.py: -------------------------------------------------------------------------------- 1 | def channels_with_more_viewers(viewers): 2 | rank = 0 3 | page = 0 4 | while True: 5 | channels_response = s.get('https://mixer.com/api/v1/channels', params={ 6 | 'fields': 'viewersTotal', 7 | 'order': 'viewersTotal:DESC', 8 | 'page': page 9 | }) 10 | 11 | for channel in channels_response.json(): 12 | if channel['viewersTotal'] <= viewers: 13 | return rank 14 | else: 15 | rank += 1 16 | 17 | print("Your rank is at least {}...".format(rank)) 18 | page += 1 19 | -------------------------------------------------------------------------------- /src/tutorials/code/python/rest/4.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | 4 | s = requests.Session() 5 | s.headers.update({'Client-ID': 'CLIENT_ID'}) 6 | 7 | def channels_with_more_viewers(viewers): 8 | """Returns the number of channels that have more than `viewers` viewers. 9 | """ 10 | 11 | rank = 0 12 | page = 0 13 | while True: 14 | channels_response = s.get('https://mixer.com/api/v1/channels', params={ 15 | 'fields': 'viewersTotal', 16 | 'order': 'viewersTotal:DESC', 17 | 'page': page 18 | }) 19 | 20 | for channel in channels_response.json(): 21 | if channel['viewersTotal'] <= viewers: 22 | return rank 23 | else: 24 | rank += 1 25 | 26 | print("Your rank is at least {}...".format(rank)) 27 | page += 1 28 | 29 | 30 | channel_response = s.get('https://mixer.com/api/v1/channels/{}'.format(sys.argv[1])) 31 | 32 | viewers = channel_response.json()['viewersTotal'] 33 | print("You have {} viewers...".format(viewers)) 34 | 35 | rank = channels_with_more_viewers(viewers) 36 | print("Your rank on Mixer is {}!".format(rank)) 37 | 38 | -------------------------------------------------------------------------------- /src/tutorials/code/python/rest/4_response.txt: -------------------------------------------------------------------------------- 1 | $ python rank.py connor4312 2 | You have 595 total viewers... 3 | Your rank is at least 51... 4 | Your rank is at least 101... 5 | Your rank is at least 151... 6 | Your rank is at least 201... 7 | Your rank is at least 251... 8 | Your rank is at least 301... 9 | Your rank is at least 351... 10 | Your rank is at least 401... 11 | Your rank is at least 451... 12 | Your rank is at least 501... 13 | Your rank is at least 551... 14 | Your rank is at least 601... 15 | Your rank is at least 651... 16 | Your rank is at least 701... 17 | Your rank is at least 751... 18 | Your rank on Mixer is 761! 19 | -------------------------------------------------------------------------------- /src/tutorials/code/raw/chat/1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # request user details 4 | userInfo=$(curl \ 5 | -s \ 6 | -H "Content-Type: application/json" \ 7 | -H "Authorization: Bearer AUTH_TOKEN" \ 8 | https://mixer.com/api/v1/users/current ) 9 | 10 | # jq . <<< $userInfo # use for inspection of data 11 | 12 | channelId=$(jq .channel.id <<< $userInfo); # extract channel id 13 | userId=$(jq .id <<< $userInfo); # extract user id 14 | echo $channelId $userId; 15 | -------------------------------------------------------------------------------- /src/tutorials/code/raw/chat/2.sh: -------------------------------------------------------------------------------- 1 | # get server connection info 2 | # use cookie we got 3 | chatJoinInfo=$(curl \ 4 | -s \ 5 | -H "Content-Type: application/json" \ 6 | -H "Authorization: Bearer AUTH_TOKEN" \ 7 | https://mixer.com/api/v1/chats/$channelId ) 8 | 9 | wsServer=$(jq -r .endpoints[0] <<< $chatJoinInfo) 10 | authKey=$(jq .authkey <<< $chatJoinInfo) 11 | 12 | echo $wsServer $authKey; 13 | -------------------------------------------------------------------------------- /src/tutorials/code/raw/chat/3.sh: -------------------------------------------------------------------------------- 1 | $ wscat --connect "$wsServer" 2 | connected (press CTRL+C to quit) 3 | > {"type": "method","method": "auth","arguments":[CHANNELID, USERID,"AUTHKEY"],"id":0} 4 | < {"type":"event","event":"UserJoin","data":{"username":"USERNAME","roles":[,"User"],"id":345}} 5 | < {"type":"reply","error":null,"id":0,"data":{"authenticated":true,"roles":["User"]}} 6 | -------------------------------------------------------------------------------- /src/tutorials/constellation.pug: -------------------------------------------------------------------------------- 1 | extends ./tutorial_layout.pug 2 | 3 | block append scripts 4 | +clientIdLinks() 5 | 6 | block title 7 | | Constellation Tutorial | Mixer Developers 8 | 9 | block tutorialTitle 10 | h1 Constellation Tutorial 11 | 12 | block tutorialContent 13 | h2#intro Introduction 14 | p. 15 | Constellation is our daemon responsible for stateful aspects of Mixer. One of its features is Live Loading. Live Loading let's you receive realtime updates of models and resources as they change on Mixer. 16 | p. 17 | In this tutorial we're going to connect to Constellation and subscribe to Live Loading updates of your channel. If your viewer count or channel title changes, you'll receive an update within your code that you can respond to. 18 | 19 | h2#code Writing the Code 20 | p Select a language below. 21 | 22 | +bsTabs('constellation_guide', { 23 | node: 'Node & TypeScript' 24 | }) 25 | +bsTabItem('constellation_guide', 'node') 26 | h3 Prerequisites 27 | ol 28 | li. 29 | Get #[a(href='https://nodejs.org/en/' target='_blank') NodeJS and NPM] for your platform. 30 | li. 31 | Create a #[a(href='https://docs.npmjs.com/cli/init' target='_blank') new project] with npm. 32 | li. 33 | Run #[code npm install --save carina ws] 34 | h3 Usage 35 | p. 36 | #[a(href='https://github.com/mixer/carina') Carina] is Mixer's Node Constellation client. It makes talking to Constellation super easy. 37 | p Let's start by importing and setting up all of the modules which we'll need and defining our channel id. 38 | include ./channelid.pug 39 | 40 | +highlightFile('javascript','./tutorials/code/node/constellation/1.js') 41 | 42 | p Next we'll create an instance of Carina to use for our code. We pass an object of options to the constructor. #[code isBot] must be set to true if you're writing an automated bot. 43 | 44 | +highlightFile('javascript','./tutorials/code/node/constellation/2.js') 45 | 46 | p To receive Live Loading events you need to subscribe to them. We'll just need #[code channel:{id}:update]. For a full list of events check our #[a(href='/reference/constellation/index.html#events_live_events') Constellation reference guide]. You subscribe to events within Carina by using the #[code subscribe] method. 47 | 48 | +highlightFile('javascript','./tutorials/code/node/constellation/3.js') 49 | 50 | p The second argument to the subscribe method is a function which will be called with the data when the event is sent. #[strong Inside] the function you can do a lot with the data but for now we'll just log it to the console. 51 | 52 | +highlightFile('javascript','./tutorials/code/node/constellation/4.js') 53 | 54 | p. 55 | That's it! Save the file as 'constellation.js' and run it with #[code node constellation.js] from your terminal. Try updating your channel details. 56 | p. 57 | For example, if you update your channel title to 'test', you'll get an object from the event that looks like this: 58 | +highlightFile('javascript','./tutorials/code/node/constellation/example_data.js') 59 | p. 60 | Try updating your channel description, age rating or selected game for more examples. 61 | 62 | p The final code can be found below: 63 | 64 | +highlightFile('javascript','./tutorials/code/node/constellation/5.js') 65 | 66 | p Carina also works with TypeScript and your Browser. For more information, check out its #[a(href='https://github.com/mixer/carina') GitHub page]. 67 | h2 Further Ideas 68 | ul 69 | li Why not combine Constellation with our #[a(href='/tutorials/rest.html') REST API] and make something super cool? 70 | li Use some of the other #[a(href='/reference/constellation/index.html#events_live_events') Constellation events] to get events about your stream. 71 | h2 Want More Info? 72 | p. 73 | If you'd like more information on Constellation system, check out the #[a(href='/reference/constellation/index.html') reference guide]. 74 | include ../snippets/help.pug 75 | block tutorialMenu 76 | li: a(href='#intro') Introduction 77 | li: a(href='#code') Writing The Code 78 | -------------------------------------------------------------------------------- /src/tutorials/java_prereqs.pug: -------------------------------------------------------------------------------- 1 | ol 2 | li 3 | a(href='http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html' target='_blank') Java 1.8 or above 4 | li 5 | | A Java IDE such as: 6 | ol 7 | li 8 | a(href='https://www.eclipse.org/downloads/' target='_blank') Eclipse 9 | li 10 | a(href='https://www.jetbrains.com/idea/' target='_blank') IntelliJ 11 | li 12 | a(href='https://netbeans.org/downloads/' target='_blank') NetBeans 13 | li 14 | | A Java Project Manager such as: 15 | ol 16 | li 17 | a(href='https://maven.apache.org/' target='_blank') Maven 18 | li 19 | a(href='http://gradle.org' target='_blank') Gradle 20 | -------------------------------------------------------------------------------- /src/tutorials/rest_intro.pug: -------------------------------------------------------------------------------- 1 | p If your friend was going to the store and you needed something, you might ask them to "get" something for you. After they get back, they might "put" that item away for you. These are the constructs we use in everyday life, and REST APIs attempt to build on those, but, rather than operating on physical items, you're dealing with objects on the computer. 2 | 3 | p When you send HTTP requests to webpages, each request contains a verb. Most browsing you do involves your web browser making #[code GET] requests—asking for this page, for instance! But there are several other verbs your browser uses: 4 | 5 | ul 6 | li #[code GET] looks something up, as we've said 7 | li #[code POST] creates something 8 | li #[code PUT] updates something 9 | li #[code PATCH] patch something 10 | li #[code DELETE] destroys something 11 | 12 | p On our RESTful API, you make requests by calling #[code VERB https://mixer.com/api/v1/resources]. We'll abbreviate this as #[code VERB /resources] from now on. For instance, to create a new user, you would call #[code POST /users] (the endpoints are always plural). If you want to operate on a particular user object, you would append their ID to that URL, such as #[code GET /users/344] to return information about user ID #[code 344]. 13 | 14 | p You can also run actions on a particular user by chaining on "actions", such as #[code PUT /users/344/confirm], which is used to verify an account. Here's a full blueprint with some examples: 15 | 16 | table.table 17 | thead 18 | tr 19 | th Endpoint 20 | th Description 21 | th Example 22 | tbody 23 | tr 24 | td: code GET /resources 25 | td Returns a list of 'resource' objects. 26 | td: code GET /users 27 | tr 28 | td: code POST /resources 29 | td Creates a new 'resource' object. 30 | td: code POST /users 31 | tr 32 | td: code GET /resources/{id} 33 | td Returns information about the 'resource' with the provided ID. 34 | td: code GET /users/314 35 | tr 36 | td: code PUT /resources/{id} 37 | td Updates a resource with the provided ID. 38 | td: code PUT /users/314 39 | tr 40 | td: code POST /resources/{id}/action 41 | td Runs some action on a 'resource'. 42 | td: code POST /users/314/action 43 | tr 44 | td: code GET /resources/{id}/data 45 | td Gets some nested information about a 'resource'. 46 | td: code GET /users/314/avatar 47 | tr 48 | td: code DELETE /resources/{id} 49 | td Delete a resource from the server. 50 | td: code DELETE /channels/314/streamKey 51 | -------------------------------------------------------------------------------- /src/tutorials/run_config.pug: -------------------------------------------------------------------------------- 1 | p. 2 | To run your project you can use your Java IDE to create a run configuration that supplies the username as #[code Program Arguments]. If you're using IntelliJ it should look like this: 3 | img(src='/img/tutorials/rest/java/runconfig.png' alt='IntelliJ Run Configuration') 4 | -------------------------------------------------------------------------------- /src/tutorials/snippets/java/chat_project_manager.pug: -------------------------------------------------------------------------------- 1 | include ../../../mixins.pug 2 | +bsTabs('chat_project_setup', { 3 | maven: 'Maven', 4 | gradle:'Gradle' 5 | }) 6 | +bsTabItem('chat_project_setup', 'maven') 7 | p To set up #[code beam-client-java], first add the Mixer repo to your pom.xml as a #[code repository] as follows: 8 | +highlightFile('xml','./tutorials/snippets/java/maven_chat_repo.xml') 9 | 10 | p And secondly, add this project as a #[code dependency] in your pom.xml: 11 | +highlightFile('xml','./tutorials/snippets/java/maven_chat_dependancy.xml') 12 | +bsTabItem('chat_project_setup', 'gradle') 13 | p To set up #[code beam-client-java], first add the Mixer repo to your build.gradle as a #[code repository] as follows: 14 | +highlightFile('xml','./tutorials/snippets/java/gradle_chat_repo.gradle') 15 | p And secondly, add this project as a #[code dependency] in your build.gradle: 16 | +highlightFile('xml','./tutorials/snippets/java/gradle_chat_dependancy.gradle') 17 | -------------------------------------------------------------------------------- /src/tutorials/snippets/java/gradle_chat_dependancy.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile "com.mixer:api:6.0.0-SNAPSHOT" 3 | } 4 | -------------------------------------------------------------------------------- /src/tutorials/snippets/java/gradle_chat_repo.gradle: -------------------------------------------------------------------------------- 1 | repositories { 2 | maven { 3 | name = "beam" 4 | url = "https://maven.mixer.com/content/repositories/snapshots" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/tutorials/snippets/java/gradle_interactive_dependancy.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile "pro.beam:api:3.0.3-SNAPSHOT" 3 | compile "pro.beam:interactive:1.5.1-SNAPSHOT" 4 | } 5 | -------------------------------------------------------------------------------- /src/tutorials/snippets/java/interactive_project_manager.pug: -------------------------------------------------------------------------------- 1 | include ../../../mixins.pug 2 | +bsTabs('interactive_project_setup', { 3 | maven: 'Maven', 4 | gradle:'Gradle' 5 | }) 6 | +bsTabItem('interactive_project_setup', 'maven') 7 | p To setup #[code beam-client-java] and #[code beam-interactive-java], first add the Mixer repo to your pom.xml as a #[code repository] as follows: 8 | +highlightFile('xml','./tutorials/snippets/java/maven_chat_repo.xml') 9 | 10 | p And secondly, add this project as a #[code dependency] in your pom.xml: 11 | +highlightFile('xml','./tutorials/snippets/java/maven_interactive_dependancy.xml') 12 | +bsTabItem('interactive_project_setup', 'gradle') 13 | p To setup #[code beam-client-java] and #[code beam-interactive-java], first add the Mixer repo to your build.gradle as a #[code repository] as follows: 14 | +highlightFile('xml','./tutorials/snippets/java/gradle_chat_repo.gradle') 15 | p And secondly, add this project as a #[code dependency] in your build.gradle: 16 | +highlightFile('xml','./tutorials/snippets/java/gradle_interactive_dependancy.gradle') 17 | -------------------------------------------------------------------------------- /src/tutorials/snippets/java/maven_chat_dependancy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.mixer 4 | api 5 | 6.0.0-SNAPSHOT 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/tutorials/snippets/java/maven_chat_repo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | beam-snapshots 4 | https://maven.mixer.com/content/repositories/snapshots/ 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/tutorials/snippets/java/maven_interactive_dependancy.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | pro.beam 4 | api 5 | 3.0.3-SNAPSHOT 6 | 7 | 8 | pro.beam 9 | interactive 10 | 1.5.1-SNAPSHOT 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/tutorials/snippets/oauth_introduction.pug: -------------------------------------------------------------------------------- 1 | p We'll be using OAuth for authentication. In the tutorial code below, click "Click here to get your token" to grab a token for the tutorial. You can read more about how OAuth works on our #[a(href='/reference/oauth/index.html') OAuth reference page] 2 | -------------------------------------------------------------------------------- /src/tutorials/tutorial_layout.pug: -------------------------------------------------------------------------------- 1 | extends ../layout.pug 2 | block navbar 3 | .navbar-wrapper 4 | .container 5 | nav.navbar.navbar-inverse: +navbar() 6 | block tutorialTitle 7 | block content 8 | .container: .row 9 | .col-md-9(role='main') 10 | block tutorialContent 11 | .col-md-3 12 | .hidden-print.rest-sidebar(role='complementary') 13 | nav 14 | ul.nav.nav-stacked 15 | block tutorialMenu 16 | --------------------------------------------------------------------------------