caccl-kevin

Canvas App Complete Connection Library: an all-in-one library for connecting your app to Canvas, handling lti, access tokens, and api.

Usage no npm install needed!

<script type="module">
  import cacclKevin from 'https://cdn.skypack.dev/caccl-kevin';
</script>

README

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 Type Client Description
React + Express App React React front-end with a simple Express back-end
Node.js Script Terminal A simple Node.js script that runs in terminal
EJS + Express Server-side App EJS Templates A 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:

Property Type Description
launched boolean if true, the user successfully launched the app via LTI
authorized boolean if true, we have authorization to access the Canvas API
authFailed boolean true if authorization failed
authFailureReason string the reason authorization failed if authFailed is true (see reasons list below)
launchInfo object included 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:

Property Type Description
score number the number of points to give the student. Either this or percent can be included, but not both
percent number the percent of the points possible to give the student. Either this or score can be included, but not both
text string the student's text submission. Either this or url can be included, but not both
url string the student's url submission. Either this or text can be included, but not both
submittedAt Date or ISO 8601 string the 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:

Property Type Description
launched boolean if true, the user successfully launched the app via LTI
authorized boolean if true, we have authorization to access the Canvas API
authFailed boolean true if authorization failed
authFailureReason string the reason authorization failed if authFailed is true (see reasons list below)
launchInfo object included 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:

Property Type Description
score number the number of points to give the student. Either this or percent can be included, but not both
percent number the percent of the points possible to give the student. Either this or score can be included, but not both
text string the student's text submission. Either this or url can be included, but not both
url string the student's url submission. Either this or text can be included, but not both
submittedAt Date or ISO 8601 string the 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 Option Type Description Default
port number the port to listen to "PORT" environment var or 443
sessionSecret string the session secret to use when encrypting sessions random string
cookieName string the cookie name to sent to client's browser "CACCL-based-app-session-[timestamp]-[random str]"
sessionMins number the number of minutes the session should last for 360 (6 hours)
onListenSuccess function function to call when server starts listening console.log
onListenFail function function to call if server can't start listening console.log
sslKey string ssl key or filename where key is stored self-signed certificate key
sslCertificate string ssl certificate or filename where certificate is stored self-signed certificate
sslCA string[] or string certificate chain linking a certificate authority to our ssl certificate. If type is string, certificates will automatically be split none
clientOrigin string the origin host of the client (to allow CORS), if different from server host none

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 Option Type Description Default
app express server app the express app to add routes to optional

Configuration for server API access:

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

Config Option Type Description Default
disableServerSideAPI boolean if false, adds req.api to routes encapsulated by routesWithAPI false
routesWithAPI string[] list of routes to add api support to, * wildcard supported all routes
cacheType string if '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 option none
cache Cache a custom cache instance (Not required if using 'memory' or 'session' cacheType (those caches are built-in) none
dontUseLaunchCanvasHost boolean if false, when a user launches the app via LTI, we use the LTI launch host as the canvasHost false
sendRequest SendRequest a function that sends an http request. We recommend leaving this as is caccl-send-request

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

Config Option Type Description Default
accessToken string a default access token to apply to all requests, overridden by user's access token none
defaultNumRetries number the number of times to retry failed requests 3
itemsPerPage number the number of items to request on a get request 100

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 Option Type Description Default
disableClientSideAPI boolean if false, server forwards Canvas API requests false
apiForwardPathPrefix string API 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 Option Type Description Default
disableAuthorization boolean if false, sets up automatic authorization when the user visits the launchPath false
developerCredentials object Canvas 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)
defaultAuthorizedRedirect string the 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) "/"
tokenStore TokenStore include a custom token store (see TokenStore docs for specs) memory token store
simulateLaunchOnAuthorize boolean if 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 Option Type Description Default
disableLTI boolean if false, CACCL listens for and parses LTI launches false
installationCredentials object installation 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)
redirectToAfterLaunch string the path to redirect to after a successful launch "/"
nonceStore object a 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
disableAuthorizeOnLaunch boolean if false, user is automatically authorized upon launch. Note: disableAuthorizeOnLaunch is not valid unless disableAuthorization and disableServerSideAPI are false. false
disableClientSidePassback boolean if 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 request false

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 Option Type Description Default
serverHost string the hostname of the server if not the same as the client same as client
defaultNumRetries number Number of times to retry a request 3
itemsPerPage number Number of items to request on a get request 100
cacheType string If 'memory', cache is stored in memory. If 'session', cache is stored in express the session "memory"
cache Cache Custom cache manager instance. Not required if using 'memory' or 'session' cacheType (those caches are built-in) none
sendRequest SendRequest a function that sends an http request. We recommend leaving this as is caccl-send-request
apiForwardPathPrefix string API 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.

  1. 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:

KEY VALUE
CONSUMER_KEY the consumer_key from your installationCredentials
CONSUMER_SECRET the consumer_secret from your installationCredentials
CANVAS_HOST the default canvasHost to use

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

KEY VALUE
CLIENT_ID the client_id from your developerCredentials
CLIENT_SECRET the client_secret from your developerCredentials
  1. 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.

  1. 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',
};
  1. 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',
};
  1. 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',
};
  1. Install your app's dependencies

Run npm install on the server

  1. 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.

  1. 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 Option Type Description Default
defaultNumRetries number the number of times to retry failed requests 3
itemsPerPage number the number of items to request on a get request 100
cacheType string if '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 option none
cache Cache a 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:

Property Type Description
launched boolean if true, the user successfully launched the app via LTI
authorized boolean if true, we have authorization to access the Canvas API
authFailed boolean true if authorization failed
authFailureReason string the reason authorization failed if authFailed is true (see reasons list below)
launchInfo object included 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:

Property Type Description
score number the number of points to give the student. Either this or percent can be included, but not both
percent number the percent of the points possible to give the student. Either this or score can be included, but not both
text string the student's text submission. Either this or url can be included, but not both
url string the student's url submission. Either this or text can be included, but not both
submittedAt Date or ISO 8601 string the 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 Option Type Description Default
port number the port to listen to "PORT" environment var or 443
sessionSecret string the session secret to use when encrypting sessions random string
cookieName string the cookie name to sent to client's browser "CACCL-based-app-session-[timestamp]-[random str]"
sessionMins number the number of minutes the session should last for 360 (6 hours)
onListenSuccess function function to call when server starts listening console.log
onListenFail function function to call if server can't start listening console.log
sslKey string ssl key or filename where key is stored self-signed certificate key
sslCertificate string ssl certificate or filename where certificate is stored self-signed certificate
sslCA string[] or string certificate chain linking a certificate authority to our ssl certificate. If type is string, certificates will automatically be split none
clientOrigin string the origin host of the client (to allow CORS), if different from server host none

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 Option Type Description Default
app express server app the express app to add routes to optional

Configuration for API access:

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

Config Option Type Description Default
disableServerSideAPI boolean if false, adds req.api to routes encapsulated by routesWithAPI false
routesWithAPI string[] list of routes to add API support to, * wildcard supported all routes
cacheType string if '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 option none
cache Cache a custom cache instance (Not required if using 'memory' or 'session' cacheType (those caches are built-in) none
dontUseLaunchCanvasHost boolean if false, when a user launches the app via LTI, we use the LTI launch host as the canvasHost false
sendRequest SendRequest a function that sends an http request. We recommend leaving this as is caccl-send-request
accessToken string a default access token to apply to all requests, overridden by user's access token none
defaultNumRetries number the number of times to retry failed requests 3
itemsPerPage number the number of items to request on a get request 100

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 Option Type Description Default
disableAuthorization boolean if false, sets up automatic authorization when the user visits the launchPath false
developerCredentials object Canvas 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)
defaultAuthorizedRedirect string the 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) "/"
tokenStore TokenStore include a custom token store (see TokenStore docs for specs) memory token store
simulateLaunchOnAuthorize boolean if 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 Option Type Description Default
disableLTI boolean if false, CACCL listens for and parses LTI launches false
installationCredentials object installation 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)
redirectToAfterLaunch string the path to redirect to after a successful launch "/"
nonceStore object a 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
disableAuthorizeOnLaunch boolean if false, user is automatically authorized upon launch. Note: disableAuthorizeOnLaunch is not valid unless disableAuthorization and disableServerSideAPI are false. false
disableClientSidePassback boolean if 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 request false

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_ to config/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.

  1. 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:

KEY VALUE
CONSUMER_KEY the consumer_key from your installationCredentials
CONSUMER_SECRET the consumer_secret from your installationCredentials
CANVAS_HOST the default canvasHost to use

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

KEY VALUE
CLIENT_ID the client_id from your developerCredentials
CLIENT_SECRET the client_secret from your developerCredentials
  1. 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.

  1. 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',
};
  1. 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',
};
  1. 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',
};
  1. Install your app's dependencies

Run npm install on the server

  1. 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.

  1. 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: