1.1.38 • Published 3 years ago

caccl-kevin v1.1.38

Weekly downloads
29
License
MIT
Repository
github
Last release
3 years ago

CACCL

The Canvas App Complete Connection Library (CACCL) is an all-in-one library for building Canvas-integrated apps. By handling LTI, authorization, and API for you, CACCL makes building Canvas-integrated tools quick and easy.

Support and Notices:

This project is in Beta

This project is still in Beta. Breaking changes may occur at any time. Please be careful when updating your version of CACCL.

No Windows support

This project was developed to be run in a Mac or Linux environment. If you must develop in Windows, try installing bash, but understand that we do not test CACCL in Windows...so no guarantees this will work.

Node version requirement

This project works best with Node v12 and higher.

Quickstart

Initialize your Project

1. Create a new project:

In an empty directory or npm project directory, run:

npm init caccl

You'll be prompted with a list of project types. Choose a type and follow instructions.

Project TypeClientDescription
React + Express AppReactReact front-end with a simple Express back-end
Node.js ScriptTerminalA simple Node.js script that runs in terminal
EJS + Express Server-side AppEJS TemplatesA server-side app with an Express server and UI templating with EJS

Project type not listed? If your type of project isn't listed above or you are creating a tool that only needs access to the Canvas API, see the Manual Set Up section.

2. Read the docs for your project type:

Once you've chosen from the list, follow instructions and jump to the corresponding docs:

If you chose React + Express App...

Table of Contents

Developer Mode

To start your app in developer mode, open three terminal windows in the project root directory. Run each of the following commands, one in each window:

  • npm run dev:canvas – starts a Canvas launch simulator
  • npm run dev:server – starts the app server
  • npm run dev:client – starts React's live dev environment

Launch: to simulate an LTI launch for your app, see instructions in the first window (Canvas simulator).

FAQ: Which port will my app listen to?

By default, we use port 443.

To choose a specific port, either set the "PORT" environment variable or add a port configuration option when calling initCACCL (see Configuring CACCL on the Server)

Back-end

To edit the back-end, edit server.js. The server is an express app. Visit the expressjs.com app docs for instructions on how to add routes, etc.

Canvas API:

If the user is authorized, then req.api will be defined.

Use req.api to access Canvas from within a server route. req.api is an instance of caccl-api. See the full list of functions at the caccl-api-docs.

Example:

app.get('/name', async (req, res) => {
  const profile = await req.api.user.self.getProfile();
  return res.send(profile.name);
});

Get Info on Status, Auth, and LTI Launch:

CACCL stores status, auth, and LTI launch info in the user's session. See the following properties of req.session:

PropertyTypeDescription
launchedbooleanif true, the user successfully launched the app via LTI
authorizedbooleanif true, we have authorization to access the Canvas API
authFailedbooleantrue if authorization failed
authFailureReasonstringthe reason authorization failed if authFailed is true (see reasons list below)
launchInfoobjectincluded if launched is true, see launchInfo docs for full list of properties

Note: see launchInfo docs for more on the launchInfo property.

Possible values of authFailureReason:

  • "error" - a Canvas error occurred: Canvas responded erratically during the authorization process
  • "internal_error" - an internal error occurred on the server while attempting to process authorization
  • "denied" - the user denied the app access to Canvas when they were prompted
  • "invalid_client" - the app's client_id is invalid: the app is not approved to interact with Canvas

Grade Passback:

CACCL supports LTI-based grade passback when the user was launched through an external assignment. If the user launched this way, you will be able to use the sendPassback function on the server to pass grade, timestamp, and/or submission data back to Canvas:

In any server route, use the req.sendPassback function with the following parameters:

PropertyTypeDescription
scorenumberthe number of points to give the student. Either this or percent can be included, but not both
percentnumberthe percent of the points possible to give the student. Either this or score can be included, but not both
textstringthe student's text submission. Either this or url can be included, but not both
urlstringthe student's url submission. Either this or text can be included, but not both
submittedAtDate or ISO 8601 stringthe submittedAt timestamp for the submission

Example 1: on this 20 point assignment, give the student 15 points and send their text submission

await req.sendPassback({
  score: 15,
  text: 'This is my submission',
});

Example 2: on this 20 point assignment, give the student 15 points and send their url submission

await req.sendPassback({
  percent: 75,
  url: 'https://student.sub/is/this/link',
});

Front-end

To edit the front-end, edit your React project in the /client folder. Start by editing /client/src/App.js. To integrate any component with the server or with Canvas, use the following:

Adding CACCL to a React Component:

// Import CACCL
import initCACCL from 'caccl/client/cached';

// Initialize CACCL
const {
  api,
  getStatus,
  sendRequest,
  sendPassback,
} = initCACCL();

See each section below on how to use api, getStatus, sendRequest, and sendPassback.

Canvas API:

An instance of caccl-api is passed back from initCACCL(). See the full list of functions at the caccl-api-docs.

Example:

const { api } = initCACCL();

const students = await api.course.listStudents({ courseId: 532894 });

We recommend handling errors with try-catch:

try {
  const students = await api.course.listStudents({ courseId: 532894 });
  ...
} catch (err) {
  // Update app to show error:
  this.setState({
    status: 'error',
    message: err.message,
    code: err.code,
  });
}

Get Info on Status, Auth, and LTI Launch:

Calling getStatus fetches many useful status variables from the server, as well as gets LTI launch information.

const { getStatus } = initCACCL();

const status = await getStatus();

Properties of status:

PropertyTypeDescription
launchedbooleanif true, the user successfully launched the app via LTI
authorizedbooleanif true, we have authorization to access the Canvas API
authFailedbooleantrue if authorization failed
authFailureReasonstringthe reason authorization failed if authFailed is true (see reasons list below)
launchInfoobjectincluded if launched is true, see launchInfo docs for full list of properties

Note: see launchInfo docs for more on the launchInfo property.

Possible values of authFailureReason:

  • "error" - a Canvas error occurred: Canvas responded erratically during the authorization process
  • "internal_error" - an internal error occurred on the server while attempting to process authorization
  • "denied" - the user denied the app access to Canvas when they were prompted
  • "invalid_client" - the app's client_id is invalid: the app is not approved to interact with Canvas

Sending requests to the server:

Use sendRequest to send requests to the server. See caccl-send-request docs for more information.

Example:

const { sendRequest } = initCACCL();

const { body, status, headers } = await sendRequest({
  path: '/add-user',
  method: 'POST',
  params: {
    name: 'Divardo Calicci',
    age: 19,
  },
});

Why use sendRequest instead of other request senders? Our sendRequest function works cross-domain with our development environment (dev server runs on one port, dev client runs on another)

Grade Passback:

CACCL supports LTI-based grade passback on the front-end when the user was launched through an external assignment and when the server has disableClientSidePassback set to false (this is the default). Use the sendPassback function provided by initCACCL to pass grade, timestamp, and/or submission data back to Canvas:

In any React component, use sendPassback with the following parameters:

PropertyTypeDescription
scorenumberthe number of points to give the student. Either this or percent can be included, but not both
percentnumberthe percent of the points possible to give the student. Either this or score can be included, but not both
textstringthe student's text submission. Either this or url can be included, but not both
urlstringthe student's url submission. Either this or text can be included, but not both
submittedAtDate or ISO 8601 stringthe submittedAt timestamp for the submission

Example 1: on this 20 point assignment, give the student 15 points and send their text submission

await sendPassback({
  score: 15,
  text: 'This is my submission',
});

Example 2: on this 20 point assignment, give the student 15 points and send their url submission

await sendPassback({
  percent: 75,
  url: 'https://student.sub/is/this/link',
});

Configuring CACCL on the Server

To change the default canvasHost in your dev environment, edit the value in config/devEnvironment.js. To change this in your production environment, see the section on deploying your app.

To customize other aspects of how CACCl functions on the server, edit the configuration options being passed into initCACCL(...) in index.js:

Configuration for Express server:

Config OptionTypeDescriptionDefault
portnumberthe port to listen to"PORT" environment var or 443
sessionSecretstringthe session secret to use when encrypting sessionsrandom string
cookieNamestringthe cookie name to sent to client's browser"CACCL-based-app-session-timestamp-random str"
sessionMinsnumberthe number of minutes the session should last for360 (6 hours)
onListenSuccessfunctionfunction to call when server starts listeningconsole.log
onListenFailfunctionfunction to call if server can't start listeningconsole.log
sslKeystringssl key or filename where key is storedself-signed certificate key
sslCertificatestringssl certificate or filename where certificate is storedself-signed certificate
sslCAstring[] or stringcertificate chain linking a certificate authority to our ssl certificate. If type is string, certificates will automatically be splitnone
clientOriginstringthe origin host of the client (to allow CORS), if different from server hostnone

If for any reason you want to create the express server yourself, just pass it in (see below). Note: If you pass in your own express server, all customization options above will be ignored. When creating your express server, make sure you initialize body parsing and express-session.

Config OptionTypeDescriptionDefault
appexpress server appthe express app to add routes tooptionalnew express app

Configuration for server API access:

If your app server doesn't need to access the Canvas API, set disableServerSideAPI: true.

Config OptionTypeDescriptionDefault
disableServerSideAPIbooleanif false, adds req.api to routes encapsulated by routesWithAPIfalse
routesWithAPIstring[]list of routes to add api support to, * wildcard supportedall routes
cacheTypestringif 'memory', cache is stored in memory. If 'session', cache is stored in the express session. To include a custom cache, include it using the "cache" config optionnone
cacheCachea custom cache instance (Not required if using 'memory' or 'session' cacheType (those caches are built-in)none
dontUseLaunchCanvasHostbooleanif false, when a user launches the app via LTI, we use the LTI launch host as the canvasHostfalse
sendRequestSendRequesta function that sends an http request. We recommend leaving this as iscaccl-send-request

The following config options apply only to API requests made from the server via req.api:

Config OptionTypeDescriptionDefault
accessTokenstringa default access token to apply to all requests, overridden by user's access tokennone
defaultNumRetriesnumberthe number of times to retry failed requests3
itemsPerPagenumberthe number of items to request on a get request100

Configuration for client-side API forwarding:

Your React client sends Canvas API requests to the Express server, which forwards them to Canvas. If your React client doesn't need to access the Canvas API, set disableClientSideAPI: true.

Config OptionTypeDescriptionDefault
disableClientSideAPIbooleanif false, server forwards Canvas API requestsfalse
apiForwardPathPrefixstringAPI forwarding path prefix to add to all forwarded api requests. This is the prefix we use to listen for forwarded requests (ex: GET /api/v1/courses is forwarded through the server's /canvas/api/v1/courses route if this is set to "/canvas")"/canvas"

Note: if you change apiForwardPathPrefix on the server, you need to change it on the client as well! We recommend not changing this.

Configuration for Canvas authorization:

To access the Canvas API, we need an access token. CACCL gets the user's access token through Canvas' OAuth 2 authorization process. All you need to do is redirect the user to the launchPath and CACCL will perform the authorization process. If disableAuthorizeOnLaunch is false (see config for LTI launch), we authorize the user on launch.

Config OptionTypeDescriptionDefault
disableAuthorizationbooleanif false, sets up automatic authorization when the user visits the launchPathfalse
developerCredentialsobjectCanvas app developer credentials in the form { client_id, client_secret }. No need to include this in your dev environment (the default value is what we expect){ client_id: 'client_id', client_secret: 'client_secret' } (our dummy vals for dev environment)
defaultAuthorizedRedirectstringthe default route to redirect the user to after authorization is complete (you can override this for a specific authorization call by including next=/path as a query or body parameter when sending user to the launchPath)"/"
tokenStoreTokenStoreinclude a custom token store (see TokenStore docs for specs)memory token store
simulateLaunchOnAuthorizebooleanif true, simulates an LTI launch upon successful authorization (if user hasn't already launched via LTI), essentially allowing users to launc the tool by visiting the launchPath (GET). Note: simulateLaunchOnAuthorize is not valid unless disableAuthorization, disableLTI, and disableServerSideAPI are all false.false

Configuration for LTI launches:

CACCL automatically accepts LTI launch requests and parses the launch request body. If your app is not launched via LTI, you can turn off this feature using disableLTI: true.

Config OptionTypeDescriptionDefault
disableLTIbooleanif false, CACCL listens for and parses LTI launchesfalse
installationCredentialsobjectinstallation consumer credentials to use to verify LTI launch requests in the form { consumer_key, consumer_secret }. No need to include this in your dev environment (the default value is what we expect){ consumer_key: 'consumer_key', consumer_secret: 'consumer_secret' } (our dummy vals for dev environment)
redirectToAfterLaunchstringthe path to redirect to after a successful launch"/"
nonceStoreobjecta nonce store instance to use for keeping track of nonces of the form { check } where check is a function: (nonce, timestamp) => Promise that resolves if valid, rejects if invalid
disableAuthorizeOnLaunchbooleanif false, user is automatically authorized upon launch. Note: disableAuthorizeOnLaunch is not valid unless disableAuthorization and disableServerSideAPI are false.false
disableClientSidePassbackbooleanif falsy, the client app cannot send grade passback to Canvas. If this is set to true, grade passback requests must be made from the server. Note: leaving this as false is convenient but does make it possible for clever users to spoof a grade passback requestfalse

Configuration for API Scopes:

CACCL apps support scopes. Just add a scopes.js file to the root folder of your project.

Your scopes.js file should export a scopes array. The scopes array can contain API functions like api.course.listStudents (recommended), or it can contain scope strings like url:GET|/api/v1/accounts (not recommended), or it can contain a mix of the two. There is one exception: the api.other.endpoint API function cannot be added to the list.

Example scopes.js file using API functions (recommended):

const api = require('caccl/API');

// All the API functions we use in our app:
module.exports = [
  api.course.listStudents,
  api.user.getProfile,
];

Example scopes.js file using manually copied scope strings (not recommended):

// List of scopes we use:
module.exports = [
  'url:DELETE|/api/v1/courses/:course_id/assignments/:id',
  'url:GET|/api/v1/users/:user_id/courses/:course_id/assignments',
];

Example scopes.js file mixing and matching API functions and scopes (we recommend listing API functions as much as possible):

const api = require('caccl/API');

// List of API functions and scopes we use:
module.exports = [
  api.course.listStudents,
  api.user.getProfile,
  // There's no API function for this scope, so we list it manually:
  'url:GET|/api/v1/users/:user_id/communication_channels/:communication_channel_id/notification_preferences',
];

Configuring CACCL on the Client:

When initializing CACCl within a React component, you can pass in configuration options to customize CACCL's behavior. Example:

// Import CACCL
import initCACCL from 'caccl/client/cached';

// Initialize CACCL
const {
  api,
  getStatus,
  sendRequest,
} = initCACCL({
  defaultNumRetries: 5,
  itemsPerPage: 200,
});

All configuration options are optional:

Config OptionTypeDescriptionDefault
serverHoststringthe hostname of the server if not the same as the clientsame as client
defaultNumRetriesnumberNumber of times to retry a request3
itemsPerPagenumberNumber of items to request on a get request100
cacheTypestringIf 'memory', cache is stored in memory. If 'session', cache is stored in express the session"memory"
cacheCacheCustom cache manager instance. Not required if using 'memory' or 'session' cacheType (those caches are built-in)none
sendRequestSendRequesta function that sends an http request. We recommend leaving this as iscaccl-send-request
apiForwardPathPrefixstringAPI forwarding path prefix to add to all forwarded API requests. This is the prefix we prepend to all requests when sending them to the server for forwarding to Canvas. This config option must be the same on the server and client/canvas

Adding Your App to Canvas

Once you've built your app and have finished tested simulating LTI launches using our developer mode tools, you can install your app into Canvas to test it out.

Just follow these steps:

1. Set up your installationCredentials

a. Generate your installationCredentials

Use a random string generator to create your app's consumer_key and consumer_secret.

Example:

consumer_key: '32789ramgps984t3n49t8ka0er9gsdflja' consumer_secret: 'sdfjklans8fn983b74n89t7b0qv9847b890cmtm3980ct7vlksjdf'

b. Save your installationCredentials to your production environment

Save your installationCredentials in a secure place. We highly recommend not checking these into git. You'll need both the consumer_key and consumer_secret when deploying your app (see the section on deploying your app)

2. Set up your developerCredentials

If your app does not access the Canvas API...

Make sure to set the following additional configuration options when calling initCACCL in your top-level index.js file:

initCACCL({
  ...
  disableAuthorization: true,
  disableClientSideAPI: true,
  disableServerSideAPI: true,
  ...
});

Since your app does not access the API, you have no need for developerCredentials. You are done with this step.

If your app requires access to the Canvas API...

a. Generate a developer key for your app Ask a Canvas account admin to generate a new "Developer Key" for your app, following the How do I add a developer key for an account? instructions. Note: your Redirect URI should be https://<apphostname>/launch.

Once finished, the admin will be able to find your app's client_id printed in plain text in the "details" column and they'll be able to get your app's client_secret by clicking the "Show Key" button directly below your client_id.

b. Keep your developerCredentials safe

Save your developerCredentials in a secure place. We highly recommend not checking these into git. You'll need both the client_id and client_secret when deploying your app (see the section on deploying your app)

3. Deploy your app

See the section on deploying your app.

4. Install your app into a Canvas course or account

a. Create your app's installation XML

We recommend using an online tool for this step. Try googling "LTI XML Generator" or just use the edu-apps xml generator.

Tips:

  • Set the launch URL to https://yourhost.com/launch unless you changed the launchPath config parameter
  • We recommend adding a "Course Navigation" extension (this is the launch type we support)

b. Install your app into Canvas

a. Visit your Canvas course or account b. Click "Settings"
c. Click the "Apps" tab
d. Click "View App Configurations"
e. Click "+ App"
f. Use the configuration type dropdown to select "Paste XML"
g. Fill in your app's name, consumer key, and consumer secret h. Paste the xml (generated in part a above) into the "XML Configuration" box
h. Click "Submit"
i. Refresh the page

Now, when visiting the course (or a course in the account) you just added your app to, you'll find that the app is installed. Note: if your XML wasn't configured to enable your app by default, you may need to go into Settings > Navigation and drag your app up so it's visible.

5. Launch your app via Canvas

Once you've installed your app into a course or account, visit that course (or a course in that account). If you just installed the app, you may need to refresh the course page.

If you set up your installation XML to include a navigation item and your app is enabled, your app will show up in the left-hand navigation menu. Just click your app to launch it.

Deploying your app:

Deploying on Heroku
  1. Create a new app on Heroku
  2. Set up your Deployment method:

    This is up to you, but here's what we think of as the easiest way to configure your Heroku app:

    a. Under the "Deploy" tab, choose GitHub as your "Deployment method"
    b. Follow instructions to search for your app's repository and connect to it
    c. We also recommend clicking "Enable Automatic Deploys" so your app re-deploys any time you push to master.

  3. Set up your Config Vars:

    a. Under the "Settings" tab, find the "Config Vars" section, and click "Reveal Config Vars"
    b. Add the following vars:

    KEYVALUE
    CONSUMER_KEYthe consumer_key from your installationCredentials
    CONSUMER_SECRETthe consumer_secret from your installationCredentials
    CANVAS_HOSTthe default canvasHost to use

    c. If you created developerCredentials while following the steps in Adding Your App to Canvas, add these vars as well:

    KEYVALUE
    CLIENT_IDthe client_id from your developerCredentials
    CLIENT_SECRETthe client_secret from your developerCredentials
  4. You're done! To deploy a new version of your app, just push to the master branch.

If you need more info on Heroku, check out Heroku's deployment guide.

Deploying on a server (e.g. Amazon EC2)
  1. Set up your server:

    We'll leave this up to you. The simplest way to do this is to add SSL certificates to your CACCL app and just upload your app code. Check out Configuring CACCL on the Server for info on adding SSL certificates.

    A more secure way of doing this is to set up nginx to securely listen to port 443 and to forward traffic to 8080. Then, your app doesn't need to have elevated privileges to listen to port 443.

  2. Add your installationCredentials:

    Save the consumer_key and consumer_secret to config/installationCredentials.js. Do not add this file in your developer environment.

    Example installationCredentials.js file:

    module.exports = {
      consumer_key: '32789ramgps984t3n49t8ka0er9gsdflja',
      consumer_secret: 'sdfjklans8fn983b74n89t7b0qv9847b890cmtm3980ct7vlksjdf',
    };
  3. Add your developerCredentials:

    Save the client_id and client_secret to config/developerCredentials.js. Do not add this file in your developer environment.

    Example developerCredentials.js file:

    module.exports = {
      client_id: '10810000000003',
      client_secret: '389andvn7849tb5sjd098fgk08490583409m54bt73948n980548',
    };
  4. Add your canvasDefaults:

    Save the default canvasHost value to config/canvasDefaults.js. Do not add this file in your developer environment.

    Example canvasDefaults.js file:

    module.exports = {
      canvasHost: 'canvas.harvard.edu',
    };
  5. Install your app's dependencies

    Run npm install on the server

  6. Build your app:

    Run npm run build on the server

    Alternatively, you can build your app before uploading it to the server. All up to you.

  7. Start your app:

    Run npm start on the server

    You may need to grant your app higher privileges by running sudo npm start instead.

If you chose Node.js Script...

Table of Contents

Run your script:

To run your script, use npm start in the project root directory

Edit your script:

To edit your script, edit script.js. The script's only argument, api, is an instance of caccl-api...see the full list of functions at the caccl-api-docs.

Canvas API:

Use api, the only argument of the function in script.js.

Example:

module.exports = async (api) => {
  // Get profile via Canvas API
  const profile = await api.user.self.getProfile();

  // Say "hello"
  console.log(`Hi ${profile.name}, it's great to meet you!`);
};

See the full list of supported API functions at the caccl-api-docs.

We recommend handling errors using try-catch:

try {
  const profile = await api.user.self.getProfile();
  ...
} catch (err) {
  console.log(`An error occurred (code: ${err.code}): ${err.message}`);
  process.exit(1);
}

Configuring CACCL:

Before your script in script.js runs, we initialize CACCL in index.js. To customize CACCL's behavior or turn on/off certain functionality, edit the configuration options passed into initCACCL(...):

Note: configuration options are optional unless otherwise stated. These configuration options only affect API requests made on the client, not those made via req.api on the server.

Config OptionTypeDescriptionDefault
defaultNumRetriesnumberthe number of times to retry failed requests3
itemsPerPagenumberthe number of items to request on a get request100
cacheTypestringif 'memory', cache is stored in memory. If 'session', cache is stored in the express session. To include a custom cache, include it using the "cache" config optionnone
cacheCachea custom cache instance (Not required if using 'memory' or 'session' cacheType: those caches are built-in)none

If you chose EJS + Express Server-side App...

Table of Contents

Developer Mode

To start your app in developer mode, open two terminal windows in the project root directory. Run each of the following commands, one in each window:

  • npm run dev:canvas – starts a Canvas launch simulator
  • npm run dev:server – starts the app server

Launch: to simulate an LTI launch for your app, see instructions in the first window (Canvas simulator).

Editing your app

To add routes to your Express server, edit routes.js.

Checking if we have authorization to use API:

Within a route, to check if we have authorization to use the API, simply check if req.api is defined:

app.get('/student-names', async (req, res) => {
  if (!req.api) {
    return res.send('Oops! You are not authorized.');
  }
  ...
});

Canvas API:

req.api is an instance of caccl-api. See the full list of functions at the caccl-api-docs.

Example:

app.get('/student-names', async (req, res) => {
  if (!req.api) {
    return res.send('Oops! You are not authorized.');
  }
  
  const students = await req.api.course.listStudents({ courseId: 58320 });

  const names = students.map(x => x.name).join(', ');

  return res.send(`Here are all your student's names: ${names}`);
});

We recommend handling errors with a try-catch statement:

app.get('/student-names', async (req, res) => {
  ...
  try {
    const students = await req.api.course.listStudents({ courseId: 58320 });
  } catch (err) {
    return res.status(500).send(err.message);
  }
  ...
});

Get Info on Status, Auth, and LTI Launch:

CACCL stores status, auth, and LTI launch info in the user's session. See the following properties of req.session:

PropertyTypeDescription
launchedbooleanif true, the user successfully launched the app via LTI
authorizedbooleanif true, we have authorization to access the Canvas API
authFailedbooleantrue if authorization failed
authFailureReasonstringthe reason authorization failed if authFailed is true (see reasons list below)
launchInfoobjectincluded if launched is true, see launchInfo docs for full list of properties

Note: see launchInfo docs for more on the launchInfo property.

Possible values of authFailureReason:

  • "error" - a Canvas error occurred: Canvas responded erratically during the authorization process
  • "internal_error" - an internal error occurred on the server while attempting to process authorization
  • "denied" - the user denied the app access to Canvas when they were prompted
  • "invalid_client" - the app's client_id is invalid: the app is not approved to interact with Canvas

Grade Passback:

CACCL supports LTI-based grade passback when the user was launched through an external assignment. If the user launched this way, you will be able to use the sendPassback function on the server to pass grade, timestamp, and/or submission data back to Canvas:

In any server route, use the req.sendPassback function with the following parameters:

PropertyTypeDescription
scorenumberthe number of points to give the student. Either this or percent can be included, but not both
percentnumberthe percent of the points possible to give the student. Either this or score can be included, but not both
textstringthe student's text submission. Either this or url can be included, but not both
urlstringthe student's url submission. Either this or text can be included, but not both
submittedAtDate or ISO 8601 stringthe submittedAt timestamp for the submission

Example 1: on this 20 point assignment, give the student 15 points and send their text submission

await req.sendPassback({
  score: 15,
  text: 'This is my submission',
});

Example 2: on this 20 point assignment, give the student 15 points and send their url submission

await req.sendPassback({
  percent: 75,
  url: 'https://student.sub/is/this/link',
});

Adding views:

Add EJS templates to the /views folder. See EJS docs for full documentation. Here's a brief overview:

Writing an EJS template: In an .ejs template file, use <%= ... %> to add placeholder text, use <%- ... %> to add placeholder html, and use <% ... %> to run javascript. See examples:

<div>
  <!-- Show app title (plain text) -->
  <h1>
    <%= title %>
  </h1>

  <!-- Show app description (html) -->
  <p>
    Description:&nbsp;
    <%- description %>
  </p>

  <!-- Display number of students with correct pluralization: -->
  <h2>
    <% const plural = (numStudents > 1); %>
    You have <%= numStudents %> student<%= plural ? '' : 's' %>.
  </h2>
</div>

Rendering an EJS template: Within an express route, use res.render to render an EJS template. In this example, we have a /views/home.ejs file

const path = require('path');
...
app.get('/student-names', async (req, res) => {
  return res.render(path.join(__dirname, 'views', 'home'), {
    title: 'My App',
    description: 'A <strong>fantastic</strong> app!',
    numStudents: 24,
  });
});

Configuring CACCL

To change the default canvasHost in your dev environment, edit the value in config/devEnvironment.js. To change this in your production environment, see the section on deploying your app.

To customize other aspects of how CACCl functions on the server, edit the configuration options being passed into initCACCL(...) in index.js:

Configuration for Express server:

Config OptionTypeDescriptionDefault
portnumberthe port to listen to"PORT" environment var or 443
sessionSecretstringthe session secret to use when encrypting sessionsrandom string
cookieNamestringthe cookie name to sent to client's browser"CACCL-based-app-session-timestamp-random str"
sessionMinsnumberthe number of minutes the session should last for360 (6 hours)
onListenSuccessfunctionfunction to call when server starts listeningconsole.log
onListenFailfunctionfunction to call if server can't start listeningconsole.log
sslKeystringssl key or filename where key is storedself-signed certificate key
sslCertificatestringssl certificate or filename where certificate is storedself-signed certificate
sslCAstring[] or stringcertificate chain linking a certificate authority to our ssl certificate. If type is string, certificates will automatically be splitnone
clientOriginstringthe origin host of the client (to allow CORS), if different from server hostnone

If for any reason you want to create the express server yourself, just pass it in (see below). Note: If you pass in your own express server, all customization options above will be ignored. When creating your express server, make sure you initialize body parsing and express-session.

Config OptionTypeDescriptionDefault
appexpress server appthe express app to add routes tooptionalnew express app

Configuration for API access:

If your app doesn't need to access the Canvas API, set disableServerSideAPI: true.

Config OptionTypeDescriptionDefault
disableServerSideAPIbooleanif false, adds req.api to routes encapsulated by routesWithAPIfalse
routesWithAPIstring[]list of routes to add API support to, * wildcard supportedall routes
cacheTypestringif 'memory', cache is stored in memory. If 'session', cache is stored in the express session. To include a custom cache, include it using the "cache" config optionnone
cacheCachea custom cache instance (Not required if using 'memory' or 'session' cacheType (those caches are built-in)none
dontUseLaunchCanvasHostbooleanif false, when a user launches the app via LTI, we use the LTI launch host as the canvasHostfalse
sendRequestSendRequesta function that sends an http request. We recommend leaving this as iscaccl-send-request
accessTokenstringa default access token to apply to all requests, overridden by user's access tokennone
defaultNumRetriesnumberthe number of times to retry failed requests3
itemsPerPagenumberthe number of items to request on a get request100

Configuration for Canvas authorization:

To access the Canvas API, we need an access token. CACCL gets the user's access token through Canvas' OAuth 2 authorization process. All you need to do is redirect the user to the launchPath and CACCL will perform the authorization process. If disableAuthorizeOnLaunch is false (see config for LTI launch), we authorize the user on launch.

Config OptionTypeDescriptionDefault
disableAuthorizationbooleanif false, sets up automatic authorization when the user visits the launchPathfalse
developerCredentialsobjectCanvas app developer credentials in the form { client_id, client_secret }. No need to include this in your dev environment (the default value is what we expect){ client_id: 'client_id', client_secret: 'client_secret' } (our dummy vals for dev environment)
defaultAuthorizedRedirectstringthe default route to redirect the user to after authorization is complete (you can override this for a specific authorization call by including next=/path as a query or body parameter when sending user to the launchPath)"/"
tokenStoreTokenStoreinclude a custom token store (see TokenStore docs for specs)memory token store
simulateLaunchOnAuthorizebooleanif true, simulates an LTI launch upon successful authorization (if user hasn't already launched via LTI), essentially allowing users to launc the tool by visiting the launchPath (GET). Note: simulateLaunchOnAuthorize is not valid unless disableAuthorization, disableLTI, and disableServerSideAPI are all false.false

Configuration for LTI launches:

CACCL automatically accepts LTI launch requests and parses the launch request body. If your app is not launched via LTI, you can turn off this feature using disableLTI: true.

Config OptionTypeDescriptionDefault
disableLTIbooleanif false, CACCL listens for and parses LTI launchesfalse
installationCredentialsobjectinstallation consumer credentials to use to verify LTI launch requests in the form { consumer_key, consumer_secret }. No need to include this in your dev environment (the default value is what we expect){ consumer_key: 'consumer_key', consumer_secret: 'consumer_secret' } (our dummy vals for dev environment)
redirectToAfterLaunchstringthe path to redirect to after a successful launch"/"
nonceStoreobjecta nonce store instance to use for keeping track of nonces of the form { check } where check is a function: (nonce, timestamp) => Promise that resolves if valid, rejects if invalid
disableAuthorizeOnLaunchbooleanif false, user is automatically authorized upon launch. Note: disableAuthorizeOnLaunch is not valid unless disableAuthorization and disableServerSideAPI are false.false
disableClientSidePassbackbooleanif falsy, the client app cannot send grade passback to Canvas. If this is set to true, grade passback requests must be made from the server. Note: leaving this as false is convenient but does make it possible for clever users to spoof a grade passback requestfalse

Adding Your App to Canvas

Once you've built your app and have finished tested simulating LTI launches using our developer mode tools, you can install your app into Canvas to test it out.

Just follow these steps:

1. Set up your installationCredentials

a. Generate your installationCredentials

Use a random string generator to create your app's consumer_key and consumer_secret.

Example:

consumer_key: '32789ramgps984t3n49t8ka0er9gsdflja' consumer_secret: 'sdfjklans8fn983b74n89t7b0qv9847b890cmtm3980ct7vlksjdf'

b. Save your installationCredentials to your production environment

Save the consumer_key and consumer_secret_ toconfig/installationCredentials.js` only in your production environment

Example:

module.exports = {
  consumer_key: '32789ramgps984t3n49t8ka0er9gsdflja',
  consumer_secret: 'sdfjklans8fn983b74n89t7b0qv9847b890cmtm3980ct7vlksjdf',
};

Do not edit this file in your development environment! In your development environment, your installationCredentials.js file should have consumer_key: 'consumer_key' and consumer_secret: 'consumer_secret' (our dummy developer environment values)

2. Set up your developerCredentials

If your app does not access the Canvas API...

Make sure to set the following additional configuration options when calling initCACCL in your top-level index.js file:

initCACCL({
  ...
  disableAuthorization: true,
  disableClientSideAPI: true,
  disableServerSideAPI: true,
  ...
});

Since your app does not access the API, you have no need for developerCredentials. You are done with this step.

If your app requires access to the Canvas API...

a. Generate a developer key for your app

Ask a Canvas account admin to generate a new "Developer Key" for your app, following the How do I add a developer key for an account? instructions. Note: your Redirect URI should be https://<apphostname>/launch.

Once finished, the admin will be able to find your app's client_id printed in plain text in the "details" column and they'll be able to get your app's client_secret by clicking the "Show Key" button directly below your client_id.

b. Save your developerCredentials to your production environment

In your production environment only, edit your config/developerCredentials.js file and add your client_id and client_secret.

Example:

module.exports = {
  client_id: '10810000000003',
  client_secret: '389andvn7849tb5sjd098fgk08490583409m54bt73948n980548',
};

Do not edit this file in your developer environment! In your development environment, your developerCredentials.js file should have client_id: 'client_id' and client_secret: 'client_secret' (our dummy developer environment values)

3. Install your app into a Canvas course or account

a. Create your app's installation XML

We recommend using an online tool for this step. Try googling "LTI XML Generator" or just use the edu-apps xml generator.

Tips:

  • Set the launch URL to https://yourhost.com/launch unless you changed the launchPath config parameter
  • We recommend adding a "Course Navigation" extension (this is the launch type we support)

b. Install your app into Canvas

a. Visit your Canvas course or account b. Click "Settings"
c. Click the "Apps" tab
d. Click "View App Configurations"
e. Click "+ App"
f. Use the configuration type dropdown to select "Paste XML"
g. Fill in your app's name, consumer key, and consumer secret h. Paste the xml (generated in part a above) into the "XML Configuration" box
h. Click "Submit"
i. Refresh the page

Now, when visiting the course (or a course in the account) you just added your app to, you'll find that the app is installed. Note: if your XML wasn't configured to enable your app by default, you may need to go into Settings > Navigation and drag your app up so it's visible.

4. Deploy your app

See the section on deploying your app.

5. Launch your app via Canvas

Once you've installed your app into a course or account, visit that course (or a course in that account). If you just installed the app, you may need to refresh the course page.

If you set up your installation XML to include a navigation item and your app is enabled, your app will show up in the left-hand navigation menu. Just click your app to launch it.

Deploying your app:

Deploying on Heroku
  1. Create a new app on Heroku
  2. Set up your Deployment method:

    This is up to you, but here's what we think of as the easiest way to configure your Heroku app:

    a. Under the "Deploy" tab, choose GitHub as your "Deployment method"
    b. Follow instructions to search for your app's repository and connect to it
    c. We also recommend clicking "Enable Automatic Deploys" so your app re-deploys any time you push to master.

  3. Set up your Config Vars:

    a. Under the "Settings" tab, find the "Config Vars" section, and click "Reveal Config Vars"
    b. Add the following vars:

    KEYVALUE
    CONSUMER_KEYthe consumer_key from your installationCredentials
    CONSUMER_SECRETthe consumer_secret from your installationCredentials
    CANVAS_HOSTthe default canvasHost to use

    c. If you created developerCredentials while following the steps in Adding Your App to Canvas, add these vars as well:

    KEYVALUE
    CLIENT_IDthe client_id from your developerCredentials
    CLIENT_SECRETthe client_secret from your developerCredentials
  4. You're done! To deploy a new version of your app, just push to the master branch.

If you need more info on Heroku, check out Heroku's deployment guide.

Deploying on a server (e.g. Amazon EC2)
  1. Set up your server:

    We'll leave this up to you. The simplest way to do this is to add SSL certificates to your CACCL app and just upload your app code. Check out Configuring CACCL for info on adding SSL certificates.

    A more secure way of doing this is to set up nginx to securely listen to port 443 and to forward traffic to 8080. Then, your app doesn't need to have elevated privileges to listen to port 443.

  2. Add your installationCredentials:

    Save the consumer_key and consumer_secret to config/installationCredentials.js. Do not add this file in your developer environment.

    Example installationCredentials.js file:

    module.exports = {
      consumer_key: '32789ramgps984t3n49t8ka0er9gsdflja',
      consumer_secret: 'sdfjklans8fn983b74n89t7b0qv9847b890cmtm3980ct7vlksjdf',
    };
  3. Add your developerCredentials:

    Save the client_id and client_secret to config/developerCredentials.js. Do not add this file in your developer environment.

    Example developerCredentials.js file:

    module.exports = {
      client_id: '10810000000003',
      client_secret: '389andvn7849tb5sjd098fgk08490583409m54bt73948n980548',
    };
  4. Add your canvasDefaults:

    Save the default canvasHost value to config/canvasDefaults.js. Do not add this file in your developer environment.

    Example canvasDefaults.js file:

    module.exports = {
      canvasHost: 'canvas.harvard.edu',
    };
  5. Install your app's dependencies

    Run npm install on the server

  6. Build your app:

    Run npm run build on the server

    Alternatively, you can build your app before uploading it to the server. All up to you.

  7. Start your app:

    Run npm start on the server

    You may need to grant your app higher privileges by running sudo npm start instead.

Manual Set Up:

I'm Creating a Tool that Only Needs Access to the Canvas API...

This section is only relevant if your tool already has a Canvas access token. In other words, your tool either doesn't need to handle LTI launches or Canvas authorization to get users' access tokens, or your tool handles LTI and Canvas authorization on its own.

Your tool only needs to import caccl-api, one sub-component of CACCL. View the caccl-api docs and scroll down to the "Use CACCL API Manually" section.

I'm Setting up a Custom Project...

You'll need CACCL set up on your server and client (if you have a client). See the following guides:

Building your custom app:

Testing your custom app: