<commit_before>/**
 * Welcome to RendererGL Immediate Mode.
 * Immediate mode is used for drawing custom shapes
 * from a set of vertices.  Immediate Mode is activated
 * when you call beginShape() & de-activated when you call endShape().
 * Immediate mode is a style of programming borrowed
 * from OpenGL's (now-deprecated) immediate mode.
 * It differs from p5.js' default, Retained Mode, which caches
 * geometries and buffers on the CPU to reduce the number of webgl
 * draw calls. Retained mode is more efficient & performative,
 * however, Immediate Mode is useful for sketching quick
 * geometric ideas.
 */
'use strict';

var p5 = require('../core/main');
var constants = require('../core/constants');

/**
 * Begin shape drawing.  This is a helpful way of generating
 * custom shapes quickly.  However in WEBGL mode, application
 * performance will likely drop as a result of too many calls to
 * beginShape() / endShape().  As a high performance alternative,
 * please use p5.js geometry primitives.
 * @private
 * @method beginShape
 * @param  {Number} mode webgl primitives mode.  beginShape supports the
 *                       following modes:
 *                       POINTS,LINES,LINE_STRIP,LINE_LOOP,TRIANGLES,
 *                       TRIANGLE_STRIP,and TRIANGLE_FAN.
 * @chainable
 */
p5.RendererGL.prototype.beginShape = function(mode) {
  //default shape mode is line_strip
  this.immediateMode.shapeMode =
    mode !== undefined ? mode : constants.LINE_STRIP;
  //if we haven't yet initialized our
  //immediateMode vertices & buffers, create them now!
  if (this.immediateMode.vertices === undefined) {
    this.immediateMode.vertices = [];
    this.immediateMode.edges = [];
    this.immediateMode.lineVertices = [];
    this.immediateMode.vertexColors = [];
    this.immediateMode.lineNormals = [];
    this.immediateMode.uvCoords = [];
    this.immediateMode.vertexBuffer = this.GL.createBuffer();
    this.immediateMode.colorBuffer = this.GL.createBuffer();
    this.immediateMode.uvBuffer = this.GL.createBuffer();
    this.immediateMode.lineVertexBuffer = this.GL.createBuffer();
    this.immediateMode.lineNormalBuffer = this.GL.createBuffer();
    this.immediateMode.pointVertexBuffer = this.GL.createBuffer();
    this.immediateMode._bezierVertex = [];
    this.immediateMode._quadraticVertex = [];
    this.immediateMode._curveVertex = [];
    this.immediateMode._isCoplanar = true;
    this.immediateMode._testIfCoplanar = null;
  } else {
    this.immediateMode.vertices.length = 0;
    this.immediateMode.edges.length = 0;
    this.immediateMode.lineVertices.length = 0;
    this.immediateMode.lineNormals.length = 0;
    this.immediateMode.vertexColors.length = 0;
    this.immediateMode.uvCoords.length = 0;
  }
  this.isImmediateDrawing = true;
  return this;
};
/**
 * adds a vertex to be drawn in a custom Shape.
 * @private
 * @method vertex
 * @param  {Number} x x-coordinate of vertex
 * @param  {Number} y y-coordinate of vertex
 * @param  {Number} z z-coordinate of vertex
 * @chainable
 * @TODO implement handling of p5.Vector args
 */
p5.RendererGL.prototype.vertex = function(x, y) {
  var z, u, v;

  // default to (x, y) mode: all other arugments assumed to be 0.
  z = u = v = 0;

  if (arguments.length === 3) {
    // (x, y, z) mode: (u, v) assumed to be 0.
    z = arguments[2];
  } else if (arguments.length === 4) {
    // (x, y, u, v) mode: z assumed to be 0.
    u = arguments[2];
    v = arguments[3];
  } else if (arguments.length === 5) {
    // (x, y, z, u, v) mode
    z = arguments[2];
    u = arguments[3];
    v = arguments[4];
  }
  if (this.immediateMode._testIfCoplanar == null) {
    this.immediateMode._testIfCoplanar = z;
  } else if (this.immediateMode._testIfCoplanar !== z) {
    this.immediateMode._isCoplanar = false;
  }
  var vert = new p5.Vector(x, y, z);
  this.immediateMode.vertices.push(vert);
  var vertexColor = this.curFillColor || [0.5, 0.5, 0.5, 1.0];
  this.immediateMode.vertexColors.push(
    vertexColor[0],
    vertexColor[1],
    vertexColor[2],
    vertexColor[3]
  );

  if (this.textureMode === constants.IMAGE) {
    if (this._tex !== null) {
      if (this._tex.width > 0 && this._tex.height > 0) {
        u /= this._tex.width;
        v /= this._tex.height;
      }
    } else if (this._tex === null && arguments.length >= 4) {
      // Only throw this warning if custom uv's have  been provided
      console.warn(
        'You must first call texture() before using' +
          ' vertex() with image based u and v coordinates'
      );
    }
  }

  this.immediateMode.uvCoords.push(u, v);

  this.immediateMode._bezierVertex[0] = x;
  this.immediateMode._bezierVertex[1] = y;
  this.immediateMode._bezierVertex[2] = z;

  this.immediateMode._quadraticVertex[0] = x;
  this.immediateMode._quadraticVertex[1] = y;
  this.immediateMode._quadraticVertex[2] = z;

  return this;
};

/**
 * End shape drawing and render vertices to screen.
 * @chainable
 */
p5.RendererGL.prototype.endShape = function(
  mode,
  isCurve,
  isBezier,
  isQuadratic,
  isContour,
  shapeKind
) {
  if (this.immediateMode.shapeMode === constants.POINTS) {
    this._drawPoints(
      this.immediateMode.vertices,
      this.immediateMode.pointVertexBuffer
    );
  } else if (this.immediateMode.vertices.length > 1) {
    if (this._doStroke && this.drawMode !== constants.TEXTURE) {
      if (this.immediateMode.shapeMode === constants.TRIANGLE_STRIP) {
        var i;
        for (i = 0; i < this.immediateMode.vertices.length - 2; i++) {
          this.immediateMode.edges.push([i, i + 1]);
          this.immediateMode.edges.push([i, i + 2]);
        }
        this.immediateMode.edges.push([i, i + 1]);
      } else if (this.immediateMode.shapeMode === constants.TRIANGLES) {
        for (i = 0; i < this.immediateMode.vertices.length - 2; i = i + 3) {
          this.immediateMode.edges.push([i, i + 1]);
          this.immediateMode.edges.push([i + 1, i + 2]);
          this.immediateMode.edges.push([i + 2, i]);
        }
      } else if (this.immediateMode.shapeMode === constants.LINES) {
        for (i = 0; i < this.immediateMode.vertices.length - 1; i = i + 2) {
          this.immediateMode.edges.push([i, i + 1]);
        }
      } else {
        for (i = 0; i < this.immediateMode.vertices.length - 1; i++) {
          this.immediateMode.edges.push([i, i + 1]);
        }
      }
      if (mode === constants.CLOSE) {
        this.immediateMode.edges.push([
          this.immediateMode.vertices.length - 1,
          0
        ]);
      }

      p5.Geometry.prototype._edgesToVertices.call(this.immediateMode);
      this._drawStrokeImmediateMode();
    }

    if (this._doFill && this.immediateMode.shapeMode !== constants.LINES) {
      if (
        this.isBezier ||
        this.isQuadratic ||
        this.isCurve ||
        (this.immediateMode.shapeMode === constants.LINE_STRIP &&
          this.drawMode === constants.FILL &&
          this.immediateMode._isCoplanar === true)
      ) {
        var contours = [
          new Float32Array(this._vToNArray(this.immediateMode.vertices))
        ];
        var polyTriangles = this._triangulate(contours);
        this.immediateMode.vertices = [];
        for (
          var j = 0, polyTriLength = polyTriangles.length;
          j < polyTriLength;
          j = j + 3
        ) {
          this.vertex(
            polyTriangles[j],
            polyTriangles[j + 1],
            polyTriangles[j + 2]
          );
        }
      }
      if (this.immediateMode.vertices.length > 0) {
        this._drawFillImmediateMode(
          mode,
          isCurve,
          isBezier,
          isQuadratic,
          isContour,
          shapeKind
        );
      }
    }
  }
  //clear out our vertexPositions & colors arrays
  //after rendering
  this.immediateMode.vertices.length = 0;
  this.immediateMode.vertexColors.length = 0;
  this.immediateMode.uvCoords.length = 0;
  this.isImmediateDrawing = false;
  this.isBezier = false;
  this.isQuadratic = false;
  this.isCurve = false;
  this.immediateMode._bezierVertex.length = 0;
  this.immediateMode._quadraticVertex.length = 0;
  this.immediateMode._curveVertex.length = 0;
  this.immediateMode._isCoplanar = true;
  this.immediateMode._testIfCoplanar = null;

  return this;
};

p5.RendererGL.prototype._drawFillImmediateMode = function(
  mode,
  isCurve,
  isBezier,
  isQuadratic,
  isContour,
  shapeKind
) {
  var gl = this.GL;
  var shader = this._getImmediateFillShader();
  this._setFillUniforms(shader);

  // initialize the fill shader's 'aPosition' buffer
  if (shader.attributes.aPosition) {
    //vertex position Attribute
    this._bindBuffer(
      this.immediateMode.vertexBuffer,
      gl.ARRAY_BUFFER,
      this._vToNArray(this.immediateMode.vertices),
      Float32Array,
      gl.DYNAMIC_DRAW
    );

    shader.enableAttrib(shader.attributes.aPosition, 3);
  }

  // initialize the fill shader's 'aVertexColor' buffer
  if (this.drawMode === constants.FILL && shader.attributes.aVertexColor) {
    this._bindBuffer(
      this.immediateMode.colorBuffer,
      gl.ARRAY_BUFFER,
      this.immediateMode.vertexColors,
      Float32Array,
      gl.DYNAMIC_DRAW
    );

    shader.enableAttrib(shader.attributes.aVertexColor, 4);
  }

  // initialize the fill shader's 'aTexCoord' buffer
  if (this.drawMode === constants.TEXTURE && shader.attributes.aTexCoord) {
    //texture coordinate Attribute
    this._bindBuffer(
      this.immediateMode.uvBuffer,
      gl.ARRAY_BUFFER,
      this.immediateMode.uvCoords,
      Float32Array,
      gl.DYNAMIC_DRAW
    );

    shader.enableAttrib(shader.attributes.aTexCoord, 2);
  }

  //if (true || mode) {
  if (this.drawMode === constants.FILL || this.drawMode === constants.TEXTURE) {
    switch (this.immediateMode.shapeMode) {
      case constants.LINE_STRIP:
      case constants.LINES:
      case constants.TRIANGLES:
        this.immediateMode.shapeMode =
          this.isBezier ||
          this.isQuadratic ||
          this.isCurve ||
          this.immediateMode.shapeMode === constants.LINE_STRIP ||
          this.immediateMode.shapeMode === constants.TRIANGLES
            ? constants.TRIANGLES
            : constants.TRIANGLE_FAN;
        break;
    }
  } else {
    switch (this.immediateMode.shapeMode) {
      case constants.LINE_STRIP:
      case constants.LINES:
        this.immediateMode.shapeMode = constants.LINE_LOOP;
        break;
    }
  }
  //}
  //QUADS & QUAD_STRIP are not supported primitives modes
  //in webgl.
  if (
    this.immediateMode.shapeMode === constants.QUADS ||
    this.immediateMode.shapeMode === constants.QUAD_STRIP
  ) {
    throw new Error(
      'sorry, ' +
        this.immediateMode.shapeMode +
        ' not yet implemented in webgl mode.'
    );
  } else {
    this._applyColorBlend(this.curFillColor);
    gl.enable(gl.BLEND);
    gl.drawArrays(
      this.immediateMode.shapeMode,
      0,
      this.immediateMode.vertices.length
    );

    this._pixelsState._pixelsDirty = true;
  }
  // todo / optimizations? leave bound until another shader is set?
  shader.unbindShader();
};

p5.RendererGL.prototype._drawStrokeImmediateMode = function() {
  var gl = this.GL;
  var shader = this._getImmediateStrokeShader();
  this._setStrokeUniforms(shader);

  // initialize the stroke shader's 'aPosition' buffer
  if (shader.attributes.aPosition) {
    this._bindBuffer(
      this.immediateMode.lineVertexBuffer,
      gl.ARRAY_BUFFER,
      this._flatten(this.immediateMode.lineVertices),
      Float32Array,
      gl.STATIC_DRAW
    );

    shader.enableAttrib(shader.attributes.aPosition, 3);
  }

  // initialize the stroke shader's 'aDirection' buffer
  if (shader.attributes.aDirection) {
    this._bindBuffer(
      this.immediateMode.lineNormalBuffer,
      gl.ARRAY_BUFFER,
      this._flatten(this.immediateMode.lineNormals),
      Float32Array,
      gl.STATIC_DRAW
    );
    shader.enableAttrib(shader.attributes.aDirection, 4);
  }

  this._applyColorBlend(this.curStrokeColor);
  gl.drawArrays(gl.TRIANGLES, 0, this.immediateMode.lineVertices.length);

  this._pixelsState._pixelsDirty = true;

  shader.unbindShader();
};

module.exports = p5.RendererGL;
<commit_msg>shapeMode TRIANGLES never forced to shapeMode TRIANGLE_FAN with WebGL(#3825)<commit_after>/**
 * Welcome to RendererGL Immediate Mode.
 * Immediate mode is used for drawing custom shapes
 * from a set of vertices.  Immediate Mode is activated
 * when you call beginShape() & de-activated when you call endShape().
 * Immediate mode is a style of programming borrowed
 * from OpenGL's (now-deprecated) immediate mode.
 * It differs from p5.js' default, Retained Mode, which caches
 * geometries and buffers on the CPU to reduce the number of webgl
 * draw calls. Retained mode is more efficient & performative,
 * however, Immediate Mode is useful for sketching quick
 * geometric ideas.
 */
'use strict';

var p5 = require('../core/main');
var constants = require('../core/constants');

/**
 * Begin shape drawing.  This is a helpful way of generating
 * custom shapes quickly.  However in WEBGL mode, application
 * performance will likely drop as a result of too many calls to
 * beginShape() / endShape().  As a high performance alternative,
 * please use p5.js geometry primitives.
 * @private
 * @method beginShape
 * @param  {Number} mode webgl primitives mode.  beginShape supports the
 *                       following modes:
 *                       POINTS,LINES,LINE_STRIP,LINE_LOOP,TRIANGLES,
 *                       TRIANGLE_STRIP,and TRIANGLE_FAN.
 * @chainable
 */
p5.RendererGL.prototype.beginShape = function(mode) {
  //default shape mode is line_strip
  this.immediateMode.shapeMode =
    mode !== undefined ? mode : constants.LINE_STRIP;
  //if we haven't yet initialized our
  //immediateMode vertices & buffers, create them now!
  if (this.immediateMode.vertices === undefined) {
    this.immediateMode.vertices = [];
    this.immediateMode.edges = [];
    this.immediateMode.lineVertices = [];
    this.immediateMode.vertexColors = [];
    this.immediateMode.lineNormals = [];
    this.immediateMode.uvCoords = [];
    this.immediateMode.vertexBuffer = this.GL.createBuffer();
    this.immediateMode.colorBuffer = this.GL.createBuffer();
    this.immediateMode.uvBuffer = this.GL.createBuffer();
    this.immediateMode.lineVertexBuffer = this.GL.createBuffer();
    this.immediateMode.lineNormalBuffer = this.GL.createBuffer();
    this.immediateMode.pointVertexBuffer = this.GL.createBuffer();
    this.immediateMode._bezierVertex = [];
    this.immediateMode._quadraticVertex = [];
    this.immediateMode._curveVertex = [];
    this.immediateMode._isCoplanar = true;
    this.immediateMode._testIfCoplanar = null;
  } else {
    this.immediateMode.vertices.length = 0;
    this.immediateMode.edges.length = 0;
    this.immediateMode.lineVertices.length = 0;
    this.immediateMode.lineNormals.length = 0;
    this.immediateMode.vertexColors.length = 0;
    this.immediateMode.uvCoords.length = 0;
  }
  this.isImmediateDrawing = true;
  return this;
};
/**
 * adds a vertex to be drawn in a custom Shape.
 * @private
 * @method vertex
 * @param  {Number} x x-coordinate of vertex
 * @param  {Number} y y-coordinate of vertex
 * @param  {Number} z z-coordinate of vertex
 * @chainable
 * @TODO implement handling of p5.Vector args
 */
p5.RendererGL.prototype.vertex = function(x, y) {
  var z, u, v;

  // default to (x, y) mode: all other arugments assumed to be 0.
  z = u = v = 0;

  if (arguments.length === 3) {
    // (x, y, z) mode: (u, v) assumed to be 0.
    z = arguments[2];
  } else if (arguments.length === 4) {
    // (x, y, u, v) mode: z assumed to be 0.
    u = arguments[2];
    v = arguments[3];
  } else if (arguments.length === 5) {
    // (x, y, z, u, v) mode
    z = arguments[2];
    u = arguments[3];
    v = arguments[4];
  }
  if (this.immediateMode._testIfCoplanar == null) {
    this.immediateMode._testIfCoplanar = z;
  } else if (this.immediateMode._testIfCoplanar !== z) {
    this.immediateMode._isCoplanar = false;
  }
  var vert = new p5.Vector(x, y, z);
  this.immediateMode.vertices.push(vert);
  var vertexColor = this.curFillColor || [0.5, 0.5, 0.5, 1.0];
  this.immediateMode.vertexColors.push(
    vertexColor[0],
    vertexColor[1],
    vertexColor[2],
    vertexColor[3]
  );

  if (this.textureMode === constants.IMAGE) {
    if (this._tex !== null) {
      if (this._tex.width > 0 && this._tex.height > 0) {
        u /= this._tex.width;
        v /= this._tex.height;
      }
    } else if (this._tex === null && arguments.length >= 4) {
      // Only throw this warning if custom uv's have  been provided
      console.warn(
        'You must first call texture() before using' +
          ' vertex() with image based u and v coordinates'
      );
    }
  }

  this.immediateMode.uvCoords.push(u, v);

  this.immediateMode._bezierVertex[0] = x;
  this.immediateMode._bezierVertex[1] = y;
  this.immediateMode._bezierVertex[2] = z;

  this.immediateMode._quadraticVertex[0] = x;
  this.immediateMode._quadraticVertex[1] = y;
  this.immediateMode._quadraticVertex[2] = z;

  return this;
};

/**
 * End shape drawing and render vertices to screen.
 * @chainable
 */
p5.RendererGL.prototype.endShape = function(
  mode,
  isCurve,
  isBezier,
  isQuadratic,
  isContour,
  shapeKind
) {
  if (this.immediateMode.shapeMode === constants.POINTS) {
    this._drawPoints(
      this.immediateMode.vertices,
      this.immediateMode.pointVertexBuffer
    );
  } else if (this.immediateMode.vertices.length > 1) {
    if (this._doStroke && this.drawMode !== constants.TEXTURE) {
      if (this.immediateMode.shapeMode === constants.TRIANGLE_STRIP) {
        var i;
        for (i = 0; i < this.immediateMode.vertices.length - 2; i++) {
          this.immediateMode.edges.push([i, i + 1]);
          this.immediateMode.edges.push([i, i + 2]);
        }
        this.immediateMode.edges.push([i, i + 1]);
      } else if (this.immediateMode.shapeMode === constants.TRIANGLES) {
        for (i = 0; i < this.immediateMode.vertices.length - 2; i = i + 3) {
          this.immediateMode.edges.push([i, i + 1]);
          this.immediateMode.edges.push([i + 1, i + 2]);
          this.immediateMode.edges.push([i + 2, i]);
        }
      } else if (this.immediateMode.shapeMode === constants.LINES) {
        for (i = 0; i < this.immediateMode.vertices.length - 1; i = i + 2) {
          this.immediateMode.edges.push([i, i + 1]);
        }
      } else {
        for (i = 0; i < this.immediateMode.vertices.length - 1; i++) {
          this.immediateMode.edges.push([i, i + 1]);
        }
      }
      if (mode === constants.CLOSE) {
        this.immediateMode.edges.push([
          this.immediateMode.vertices.length - 1,
          0
        ]);
      }

      p5.Geometry.prototype._edgesToVertices.call(this.immediateMode);
      this._drawStrokeImmediateMode();
    }

    if (this._doFill && this.immediateMode.shapeMode !== constants.LINES) {
      if (
        this.isBezier ||
        this.isQuadratic ||
        this.isCurve ||
        (this.immediateMode.shapeMode === constants.LINE_STRIP &&
          this.drawMode === constants.FILL &&
          this.immediateMode._isCoplanar === true)
      ) {
        this.immediateMode.shapeMode = constants.TRIANGLES;
        var contours = [
          new Float32Array(this._vToNArray(this.immediateMode.vertices))
        ];
        var polyTriangles = this._triangulate(contours);
        this.immediateMode.vertices = [];
        for (
          var j = 0, polyTriLength = polyTriangles.length;
          j < polyTriLength;
          j = j + 3
        ) {
          this.vertex(
            polyTriangles[j],
            polyTriangles[j + 1],
            polyTriangles[j + 2]
          );
        }
      }
      if (this.immediateMode.vertices.length > 0) {
        this._drawFillImmediateMode(
          mode,
          isCurve,
          isBezier,
          isQuadratic,
          isContour,
          shapeKind
        );
      }
    }
  }
  //clear out our vertexPositions & colors arrays
  //after rendering
  this.immediateMode.vertices.length = 0;
  this.immediateMode.vertexColors.length = 0;
  this.immediateMode.uvCoords.length = 0;
  this.isImmediateDrawing = false;
  this.isBezier = false;
  this.isQuadratic = false;
  this.isCurve = false;
  this.immediateMode._bezierVertex.length = 0;
  this.immediateMode._quadraticVertex.length = 0;
  this.immediateMode._curveVertex.length = 0;
  this.immediateMode._isCoplanar = true;
  this.immediateMode._testIfCoplanar = null;

  return this;
};

p5.RendererGL.prototype._drawFillImmediateMode = function(
  mode,
  isCurve,
  isBezier,
  isQuadratic,
  isContour,
  shapeKind
) {
  var gl = this.GL;
  var shader = this._getImmediateFillShader();
  this._setFillUniforms(shader);

  // initialize the fill shader's 'aPosition' buffer
  if (shader.attributes.aPosition) {
    //vertex position Attribute
    this._bindBuffer(
      this.immediateMode.vertexBuffer,
      gl.ARRAY_BUFFER,
      this._vToNArray(this.immediateMode.vertices),
      Float32Array,
      gl.DYNAMIC_DRAW
    );

    shader.enableAttrib(shader.attributes.aPosition, 3);
  }

  // initialize the fill shader's 'aVertexColor' buffer
  if (this.drawMode === constants.FILL && shader.attributes.aVertexColor) {
    this._bindBuffer(
      this.immediateMode.colorBuffer,
      gl.ARRAY_BUFFER,
      this.immediateMode.vertexColors,
      Float32Array,
      gl.DYNAMIC_DRAW
    );

    shader.enableAttrib(shader.attributes.aVertexColor, 4);
  }

  // initialize the fill shader's 'aTexCoord' buffer
  if (this.drawMode === constants.TEXTURE && shader.attributes.aTexCoord) {
    //texture coordinate Attribute
    this._bindBuffer(
      this.immediateMode.uvBuffer,
      gl.ARRAY_BUFFER,
      this.immediateMode.uvCoords,
      Float32Array,
      gl.DYNAMIC_DRAW
    );

    shader.enableAttrib(shader.attributes.aTexCoord, 2);
  }

  //if (true || mode) {
  if (this.drawMode === constants.FILL || this.drawMode === constants.TEXTURE) {
    switch (this.immediateMode.shapeMode) {
      case constants.LINE_STRIP:
      case constants.LINES:
        this.immediateMode.shapeMode = constants.TRIANGLE_FAN;
        break;
    }
  } else {
    switch (this.immediateMode.shapeMode) {
      case constants.LINE_STRIP:
      case constants.LINES:
        this.immediateMode.shapeMode = constants.LINE_LOOP;
        break;
    }
  }
  //}
  //QUADS & QUAD_STRIP are not supported primitives modes
  //in webgl.
  if (
    this.immediateMode.shapeMode === constants.QUADS ||
    this.immediateMode.shapeMode === constants.QUAD_STRIP
  ) {
    throw new Error(
      'sorry, ' +
        this.immediateMode.shapeMode +
        ' not yet implemented in webgl mode.'
    );
  } else {
    this._applyColorBlend(this.curFillColor);
    gl.enable(gl.BLEND);
    gl.drawArrays(
      this.immediateMode.shapeMode,
      0,
      this.immediateMode.vertices.length
    );

    this._pixelsState._pixelsDirty = true;
  }
  // todo / optimizations? leave bound until another shader is set?
  shader.unbindShader();
};

p5.RendererGL.prototype._drawStrokeImmediateMode = function() {
  var gl = this.GL;
  var shader = this._getImmediateStrokeShader();
  this._setStrokeUniforms(shader);

  // initialize the stroke shader's 'aPosition' buffer
  if (shader.attributes.aPosition) {
    this._bindBuffer(
      this.immediateMode.lineVertexBuffer,
      gl.ARRAY_BUFFER,
      this._flatten(this.immediateMode.lineVertices),
      Float32Array,
      gl.STATIC_DRAW
    );

    shader.enableAttrib(shader.attributes.aPosition, 3);
  }

  // initialize the stroke shader's 'aDirection' buffer
  if (shader.attributes.aDirection) {
    this._bindBuffer(
      this.immediateMode.lineNormalBuffer,
      gl.ARRAY_BUFFER,
      this._flatten(this.immediateMode.lineNormals),
      Float32Array,
      gl.STATIC_DRAW
    );
    shader.enableAttrib(shader.attributes.aDirection, 4);
  }

  this._applyColorBlend(this.curStrokeColor);
  gl.drawArrays(gl.TRIANGLES, 0, this.immediateMode.lineVertices.length);

  this._pixelsState._pixelsDirty = true;

  shader.unbindShader();
};

module.exports = p5.RendererGL;
<|endoftext|>


<commit_before>/*
 *  Project: SKT HTML5 Framework
 *  CodeName : CornerStone
 *  FileName : plugin-spinner.js
 *  Description: 스피너는 화면전환 등의 로딩이 필요한 컨텐츠에 대해 UI적으로 로딩 중이라는 것을 표현하기 위한 플러그인이다.
 *  Author: 김우섭
 *  License :
 */

(function (root, doc, factory) {
    factory(root.jQuery, root, doc);
}(this, document, function (jQuery, window, document, undefined) {

    var hasTouch = ('ontouchstart' in window);

    $.fn.spinner = function (opts) {
        opts = $.extend({}, {
            lines: 13, // The number of lines to draw
            length: 7, // The length of each line
            width: 4, // The line thickness
            radius: 10, // The radius of the inner circle
            corners: 1, // Corner roundness (0..1)
            rotate: 0, // The rotation offset
            color: '#FFF', // #rgb or #rrggbb
            speed: 1, // Rounds per second
            trail: 60, // Afterglow percentage
            shadow: true, // Whether to render a shadow
            hwaccel: true, // Whether to use hardware acceleration
            className: 'spinner', // The CSS class to assign to the spinner
            zIndex: 2e9, // The z-index (defaults to 2000000000)
            top: 'auto', // Top position relative to parent in px
            left: 'auto' // Left position relative to parent in px
        }, opts);

        this.each(function () {
            var $this = $(this),
                data = $this.data();
                console.log('$this',$this);
            if (data.spinner && typeof opts === "string") {
                data.spinner[opts]();
            } else {
                if (data.spinner) {
                    $this.trigger($.Event('hide.cs.spinner'));
                    // 존재한 상태에서 spinner 플러그인을 재호출하면 플러그인을 제거시킨다.
                    setTimeout(function () {
                        data.spinner.stop();
                        delete data.spinner;
                        $this.toggleClass("spinner-outer-bg");
                        $this.trigger($.Event('hidden.cs.spinner'));
                    }, 150);
                } else {
                    $this.trigger($.Event('show.cs.spinner'));
                    $(this).toggleClass("spinner-outer-bg");
                    var style = $this.attr("style");
                    data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this);
                    ($this.height() > $(window).height()) && $this.find(".spinner:first-child").attr("style", style);
                    $this.trigger($.Event('shown.cs.spinner'));
                }
            }
        });
        return this;
    };

    $.fn.spinner.presets = {
        tiny: { lines: 8, length: 2, width: 2, radius: 3 },
        small: { lines: 8, length: 4, width: 3, radius: 5 },
        large: { lines: 10, length: 8, width: 4, radius: 8 }
    };

    $(function () {
        $(document).off("click.Spinner.data-api").on("click.Spinner.data-api",
            "[data-plugin^=spinner], .spinner-outer-bg", function (e) {
                console.log(e);
            var $btn = $(e.target);
            var target = $btn.data("spinnerTarget");
            $(target).length ? $(target).spinner() : $(this).spinner();
        });

    });
}));

<commit_msg>console 출력 코드 삭제<commit_after>/*
 *  Project: SKT HTML5 Framework
 *  CodeName : CornerStone
 *  FileName : plugin-spinner.js
 *  Description: 스피너는 화면전환 등의 로딩이 필요한 컨텐츠에 대해 UI적으로 로딩 중이라는 것을 표현하기 위한 플러그인이다.
 *  Author: 김우섭
 *  License :
 */

(function (root, doc, factory) {
    factory(root.jQuery, root, doc);
}(this, document, function (jQuery, window, document, undefined) {

    var hasTouch = ('ontouchstart' in window);

    $.fn.spinner = function (opts) {
        opts = $.extend({}, {
            lines: 13, // The number of lines to draw
            length: 7, // The length of each line
            width: 4, // The line thickness
            radius: 10, // The radius of the inner circle
            corners: 1, // Corner roundness (0..1)
            rotate: 0, // The rotation offset
            color: '#FFF', // #rgb or #rrggbb
            speed: 1, // Rounds per second
            trail: 60, // Afterglow percentage
            shadow: true, // Whether to render a shadow
            hwaccel: true, // Whether to use hardware acceleration
            className: 'spinner', // The CSS class to assign to the spinner
            zIndex: 2e9, // The z-index (defaults to 2000000000)
            top: 'auto', // Top position relative to parent in px
            left: 'auto' // Left position relative to parent in px
        }, opts);

        this.each(function () {
            var $this = $(this),
                data = $this.data();
            if (data.spinner && typeof opts === "string") {
                data.spinner[opts]();
            } else {
                if (data.spinner) {
                    $this.trigger($.Event('hide.cs.spinner'));
                    // 존재한 상태에서 spinner 플러그인을 재호출하면 플러그인을 제거시킨다.
                    setTimeout(function () {
                        data.spinner.stop();
                        delete data.spinner;
                        $this.toggleClass("spinner-outer-bg");
                        $this.trigger($.Event('hidden.cs.spinner'));
                    }, 150);
                } else {
                    $this.trigger($.Event('show.cs.spinner'));
                    $(this).toggleClass("spinner-outer-bg");
                    var style = $this.attr("style");
                    data.spinner = new Spinner($.extend({color: $this.css('color')}, opts)).spin(this);
                    ($this.height() > $(window).height()) && $this.find(".spinner:first-child").attr("style", style);
                    $this.trigger($.Event('shown.cs.spinner'));
                }
            }
        });
        return this;
    };

    $.fn.spinner.presets = {
        tiny: { lines: 8, length: 2, width: 2, radius: 3 },
        small: { lines: 8, length: 4, width: 3, radius: 5 },
        large: { lines: 10, length: 8, width: 4, radius: 8 }
    };

    $(function () {
        $(document).off("click.Spinner.data-api").on("click.Spinner.data-api",
            "[data-plugin^=spinner], .spinner-outer-bg", function (e) {
            var $btn = $(e.target);
            var target = $btn.data("spinnerTarget");
            $(target).length ? $(target).spinner() : $(this).spinner();
        });

    });
}));

<|endoftext|>


<commit_before>/*
    Copyright 2013-2015 appPlant UG

    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
*/

var Notifications = Windows.UI.Notifications,
    applicationData = Windows.Storage.ApplicationData.current,
    localSettings = applicationData.localSettings;

exports.core = {

    /**
     * Executes all queued events.
     */
    deviceready: function () {
        var plugin = cordova.plugins.notification.local,
            events = this.eventQueue;

        this.isReady = true;

        for (var i = 0; i < events.length; i++) {
            plugin.fireEvent.apply(plugin, events[i]);
        }

        this.eventQueue = [];
    },

    /**
     * Schedules new local notifications.
     *
     * @param {Object[]} notifications
     *      Array of local notifications
     * @param {String} event
     *      'schedule' or 'update'
     */
    schedule: function (notifications) {
        var triggerFn = function (notification) {
            this.updateBadge(notification.badge);
            this.fireEvent('trigger', notification);
        };

        for (var i = 0; i < notifications.length; i++) {
            var options = notifications[i],
                notification = this.build(options);

            this.cancelLocalNotification(options.id);
            this.scheduleLocalNotification(notification, options);
            this.scheduleBackupNotification(notification, options);
            this.fireEvent('schedule', options);
            this.callOnTrigger(options, triggerFn);
        }
    },

    /**
     * Schedules a single local notification.
     *
     * @param {Windows.Data.Xml.Dom.XmlDocument} notification
     *      The local notification
     * @param {Object} options
     *      Local notification properties
     */
    scheduleLocalNotification: function (notification, options) {
        var interval = this.getRepeatInterval(options.every),
            triggerTime = new Date((options.at * 1000)),
            now = new Date().getTime(),
            toast;

        if (triggerTime <= now) {
            triggerTime = new Date(now + 10);
        }

        try {
            if (interval !== 0 && interval < 360001 && interval > 59999) {
                toast = new Notifications.ScheduledToastNotification(
                    notification, triggerTime, interval, 5);
            } else {
                toast = new Notifications.ScheduledToastNotification(
                    notification, triggerTime);
            }
        } catch (e) {
            console.error(e);
            return;
        }

        toast.id = options.id;
        toast.tag = 'Toast' + toast.id;

        Notifications.ToastNotificationManager
            .createToastNotifier()
            .addToSchedule(toast);
    },

    /**
     * Schedules a backup local notification 10 years later.
     *
     * @param {Object} notification
     *      The local notification
     */
    scheduleBackupNotification: function (notification, options) {
        var properties = Object.create(options);

        properties.id = options.id + '-2';
        properties.at = options.at + 315360000; // 10 years later

        this.scheduleLocalNotification(notification, properties);
    },

    /**
     * Updates the badge number of the active tile.
     *
     * @param {Number} badge
     *      The badge number. Zero will clean the badge.
     */
    updateBadge: function (badge) {
        var notifications = Windows.UI.Notifications,
            type = notifications.BadgeTemplateType.badgeNumber,
            xml = notifications.BadgeUpdateManager.getTemplateContent(type),
            attrs = xml.getElementsByTagName('badge'),
            notification = new notifications.BadgeNotification(xml);

        attrs[0].setAttribute('value', badge);

        notifications.BadgeUpdateManager.createBadgeUpdaterForApplication()
            .update(notification);
    },

    /**
     * Updates existing notifications specified by IDs in options.
     *
     * @param {Object[]} notifications
     *      Array of local notifications
     */
    update: function (notifications) {
        for (var i = 0; i < notifications.length; i++) {
            var updates = notifications[i],
                options = getAll(updates.id || '0')[0];

            this.updateLocalNotification(options, updates);
            this.fireEvent('update', options);
        }
    },

    /**
     * Updates a single local notification.
     *
     * @param {Object} notification
     *      The local notification
     * @param {Object} updates
     *      Updated properties
     */
    updateLocalNotification: function (notification, updates) {
        for (var key in updates) {
            notification[key] = updates[key];
        }

        this.cancelLocalNotification(notification.id);
        this.scheduleLocalNotification(notification);
    },

    /**
     * Clears the specified notifications.
     *
     * @param {int[]} ids
     *      List of local notification IDs
     */
    clear: function (ids) {
        for (var i = 0; i < ids.length; i++) {
            var id = ids[i],
                notification = this.getAll([id])[0];

            this.clearLocalNotification(id);
            this.fireEvent('clear', notification);
        }
    },

    /**
     * Clears the local notification with the given ID.
     *
     * @param {String} id
     *      Local notification ID
     */
    clearLocalNotification: function (id) {
        var notification = this.getAll([id])[0];

        this.getToastHistory().remove('Toast' + id);

        if (this.isRepeating(notification))
            return;

        if (this.isTriggered(id) && !this.isScheduled(id)) {
            this.cancelLocalNotification(id);
        }
    },

    /**
     * Clears all notifications.
     */
    clearAll: function () {
        var ids = this.getTriggeredIds();

        for (var i = 0; i < ids.length; i++) {
            this.clearLocalNotification(ids[i]);
        }

        this.getToastHistory().clear();
        this.fireEvent('clearall');
    },

    /**
     * Cancels all specified notifications.
     *
     * @param {int[]} ids
     *      List of local notification IDs
     */
    cancel: function (ids) {
        for (var i = 0; i < ids.length; i++) {
            var id = ids[i],
                notification = this.getAll([id])[0];

            this.cancelLocalNotification(ids[i]);
            this.fireEvent('cancel', notification);
        }
    },

    /**
     * Cancels the local notification with the given ID.
     *
     * @param {String} id
     *      Local notification ID
     */
    cancelLocalNotification: function (id) {
        var notifier = this.getToastNotifier(),
            history = this.getToastHistory(),
            toasts = this.getScheduledToasts();

        history.remove('Toast' + id);

        for (var i = 0; i < toasts.length; i++) {
            var toast = toasts[i];

            if (toast.id == id || toast.id == id + '-2') {
                notifier.removeFromSchedule(toast);
            }
        }
    },

    /**
     * Cancels all notifications.
     */
    cancelAll: function () {
        var ids = this.getAllIds();

        for (var i = 0; i < ids.length; i++) {
            this.cancelLocalNotification(ids[i]);
        }

        this.getToastHistory().clear();
        this.fireEvent('cancelall');
    },

    /**
     * Checks if a notification with an ID is present.
     *
     * @param {int} id
     *      Local notification ID
     */
    isPresent: function (id) {
        return !!this.findToastById(id);
    },

    /**
     * Checks if a notification with an ID was scheduled.
     *
     * @param {int} id
     *      Local notification ID
     */
    isScheduled: function (id) {
        var toast = this.findToastById(id);

        return toast && this.isToastScheduled(toast);
    },

    /**
     * Checks if a notification with an ID was triggered.
     *
     * @param {int} id
     *      Local notification ID
     */
    isTriggered: function (id) {
        var toast = this.findToastById(id);

        return toast && this.isToastTriggered(toast);
    },

    /**
     * Lists all local notification IDs.
     */
    getAllIds: function () {
        var toasts = this.getScheduledToasts(),
            ids = [];

        for (var i = 0; i < toasts.length; i++) {
            var toast = toasts[i];

            ids.push(this.getToastId(toast));
        }

        return ids;
    },

    /**
     * Lists all scheduled notification IDs.
     */
    getScheduledIds: function () {
        var toasts = this.getScheduledToasts(),
            ids = [];

        for (var i = 0; i < toasts.length; i++) {
            var toast = toasts[i];

            if (!this.isToastScheduled(toast))
                continue;

            ids.push(this.getToastId(toast));
        }

        return ids;
    },

    /**
     * Lists all scheduled notification IDs.
     */
    getTriggeredIds: function () {
        var toasts = this.getScheduledToasts(),
            ids = [];

        for (var i = 0; i < toasts.length; i++) {
            var toast = toasts[i];

            if (!this.isToastTriggered(toast))
                continue;

            ids.push(this.getToastId(toast));
        }

        return ids;
    },

    /**
     * Property list for given notifications.
     * If called without IDs, all notification will be returned.
     *
     * @param {int[]} ids
     *      List of local notification IDs.
     * @param {String?} type
     *      Local notification life cycle type
     */
    getAll: function (ids, type) {
        var toasts = this.getScheduledToasts(),
            notifications = [];

        if (!ids || ids.length === 0) {
            ids = this.getAllIds();
        }

        for (var index = 0; index < ids.length; index++) {
            var id = ids[index],
                toast = this.findToastById(id);

            if (!toast || type && this.getToastType(toast) != type)
                continue;

            var json = toast.content.lastChild.lastChild.innerText;

            notifications.push(JSON.parse(json));
        }

        return notifications;
    },

    /**
     * Property list for given notifications.
     * If called without IDs, all notification will be returned.
     *
     * @param {int[]} ids
     *      List of local notification IDs
     */
    getScheduled: function (ids) {
        if (!ids || ids.length === 0) {
            ids = this.getAllIds();
        }

        return this.getAll(ids, 'scheduled');
    },

    /**
     * Property list for given notifications.
     * If called without IDs, all notification will be returned.
     *
     * @param {int[]} ids
     *      List of local notification IDs
     */
    getTriggered: function (ids) {
        if (!ids || ids.length === 0) {
            ids = this.getAllIds();
        }

        return this.getAll(ids, 'triggered');
    },
};
<commit_msg>Fixed Bug with NotificationHistory in Windows (desktop)<commit_after>/*
    Copyright 2013-2015 appPlant UG

    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
*/

var Notifications = Windows.UI.Notifications,
    applicationData = Windows.Storage.ApplicationData.current,
    localSettings = applicationData.localSettings;

exports.core = {

    /**
     * Executes all queued events.
     */
    deviceready: function () {
        var plugin = cordova.plugins.notification.local,
            events = this.eventQueue;

        this.isReady = true;

        for (var i = 0; i < events.length; i++) {
            plugin.fireEvent.apply(plugin, events[i]);
        }

        this.eventQueue = [];
    },

    /**
     * Schedules new local notifications.
     *
     * @param {Object[]} notifications
     *      Array of local notifications
     * @param {String} event
     *      'schedule' or 'update'
     */
    schedule: function (notifications) {
        var triggerFn = function (notification) {
            this.updateBadge(notification.badge);
            this.fireEvent('trigger', notification);
        };

        for (var i = 0; i < notifications.length; i++) {
            var options = notifications[i],
                notification = this.build(options);

            this.cancelLocalNotification(options.id);
            this.scheduleLocalNotification(notification, options);
            this.scheduleBackupNotification(notification, options);
            this.fireEvent('schedule', options);
            this.callOnTrigger(options, triggerFn);
        }
    },

    /**
     * Schedules a single local notification.
     *
     * @param {Windows.Data.Xml.Dom.XmlDocument} notification
     *      The local notification
     * @param {Object} options
     *      Local notification properties
     */
    scheduleLocalNotification: function (notification, options) {
        var interval = this.getRepeatInterval(options.every),
            triggerTime = new Date((options.at * 1000)),
            now = new Date().getTime(),
            toast;

        if (triggerTime <= now) {
            triggerTime = new Date(now + 10);
        }

        try {
            if (interval !== 0 && interval < 360001 && interval > 59999) {
                toast = new Notifications.ScheduledToastNotification(
                    notification, triggerTime, interval, 5);
            } else {
                toast = new Notifications.ScheduledToastNotification(
                    notification, triggerTime);
            }
        } catch (e) {
            console.error(e);
            return;
        }

        toast.id = options.id;
        toast.tag = 'Toast' + toast.id;

        Notifications.ToastNotificationManager
            .createToastNotifier()
            .addToSchedule(toast);
    },

    /**
     * Schedules a backup local notification 10 years later.
     *
     * @param {Object} notification
     *      The local notification
     */
    scheduleBackupNotification: function (notification, options) {
        var properties = Object.create(options);

        properties.id = options.id + '-2';
        properties.at = options.at + 315360000; // 10 years later

        this.scheduleLocalNotification(notification, properties);
    },

    /**
     * Updates the badge number of the active tile.
     *
     * @param {Number} badge
     *      The badge number. Zero will clean the badge.
     */
    updateBadge: function (badge) {
        var notifications = Windows.UI.Notifications,
            type = notifications.BadgeTemplateType.badgeNumber,
            xml = notifications.BadgeUpdateManager.getTemplateContent(type),
            attrs = xml.getElementsByTagName('badge'),
            notification = new notifications.BadgeNotification(xml);

        attrs[0].setAttribute('value', badge);

        notifications.BadgeUpdateManager.createBadgeUpdaterForApplication()
            .update(notification);
    },

    /**
     * Updates existing notifications specified by IDs in options.
     *
     * @param {Object[]} notifications
     *      Array of local notifications
     */
    update: function (notifications) {
        for (var i = 0; i < notifications.length; i++) {
            var updates = notifications[i],
                options = getAll(updates.id || '0')[0];

            this.updateLocalNotification(options, updates);
            this.fireEvent('update', options);
        }
    },

    /**
     * Updates a single local notification.
     *
     * @param {Object} notification
     *      The local notification
     * @param {Object} updates
     *      Updated properties
     */
    updateLocalNotification: function (notification, updates) {
        for (var key in updates) {
            notification[key] = updates[key];
        }

        this.cancelLocalNotification(notification.id);
        this.scheduleLocalNotification(notification);
    },

    /**
     * Clears the specified notifications.
     *
     * @param {int[]} ids
     *      List of local notification IDs
     */
    clear: function (ids) {
        for (var i = 0; i < ids.length; i++) {
            var id = ids[i],
                notification = this.getAll([id])[0];

            this.clearLocalNotification(id);
            this.fireEvent('clear', notification);
        }
    },

    /**
     * Clears the local notification with the given ID.
     *
     * @param {String} id
     *      Local notification ID
     */
    clearLocalNotification: function (id) {
        var notification = this.getAll([id])[0];

        try {
            this.getToastHistory().remove('Toast' + id);
        } catch (e) {/*Only Phones support the NotificationHistory*/ }

        if (this.isRepeating(notification))
            return;

        if (this.isTriggered(id) && !this.isScheduled(id)) {
            this.cancelLocalNotification(id);
        }
    },

    /**
     * Clears all notifications.
     */
    clearAll: function () {
        var ids = this.getTriggeredIds();

        for (var i = 0; i < ids.length; i++) {
            this.clearLocalNotification(ids[i]);
        }

        try {
            this.getToastHistory().clear();
        } catch (e) {/*Only Phones support the NotificationHistory*/ }
        this.fireEvent('clearall');
    },

    /**
     * Cancels all specified notifications.
     *
     * @param {int[]} ids
     *      List of local notification IDs
     */
    cancel: function (ids) {
        for (var i = 0; i < ids.length; i++) {
            var id = ids[i],
                notification = this.getAll([id])[0];

            this.cancelLocalNotification(ids[i]);
            this.fireEvent('cancel', notification);
        }
    },

    /**
     * Cancels the local notification with the given ID.
     *
     * @param {String} id
     *      Local notification ID
     */
    cancelLocalNotification: function (id) {
        var notifier = this.getToastNotifier(),
            history = this.getToastHistory(),
            toasts = this.getScheduledToasts();

        try {
            history.remove('Toast' + id);
        } catch (e) {/*Only Phones support the NotificationHistory*/ }

        for (var i = 0; i < toasts.length; i++) {
            var toast = toasts[i];

            if (toast.id == id || toast.id == id + '-2') {
                notifier.removeFromSchedule(toast);
            }
        }
    },

    /**
     * Cancels all notifications.
     */
    cancelAll: function () {
        var ids = this.getAllIds();

        for (var i = 0; i < ids.length; i++) {
            this.cancelLocalNotification(ids[i]);
        }

        try {
            this.getToastHistory().clear();
        } catch (e) {/*Only Phones support the NotificationHistory*/ }
        this.fireEvent('cancelall');
    },

    /**
     * Checks if a notification with an ID is present.
     *
     * @param {int} id
     *      Local notification ID
     */
    isPresent: function (id) {
        return !!this.findToastById(id);
    },

    /**
     * Checks if a notification with an ID was scheduled.
     *
     * @param {int} id
     *      Local notification ID
     */
    isScheduled: function (id) {
        var toast = this.findToastById(id);

        return toast && this.isToastScheduled(toast);
    },

    /**
     * Checks if a notification with an ID was triggered.
     *
     * @param {int} id
     *      Local notification ID
     */
    isTriggered: function (id) {
        var toast = this.findToastById(id);

        return toast && this.isToastTriggered(toast);
    },

    /**
     * Lists all local notification IDs.
     */
    getAllIds: function () {
        var toasts = this.getScheduledToasts(),
            ids = [];

        for (var i = 0; i < toasts.length; i++) {
            var toast = toasts[i];

            ids.push(this.getToastId(toast));
        }

        return ids;
    },

    /**
     * Lists all scheduled notification IDs.
     */
    getScheduledIds: function () {
        var toasts = this.getScheduledToasts(),
            ids = [];

        for (var i = 0; i < toasts.length; i++) {
            var toast = toasts[i];

            if (!this.isToastScheduled(toast))
                continue;

            ids.push(this.getToastId(toast));
        }

        return ids;
    },

    /**
     * Lists all scheduled notification IDs.
     */
    getTriggeredIds: function () {
        var toasts = this.getScheduledToasts(),
            ids = [];

        for (var i = 0; i < toasts.length; i++) {
            var toast = toasts[i];

            if (!this.isToastTriggered(toast))
                continue;

            ids.push(this.getToastId(toast));
        }

        return ids;
    },

    /**
     * Property list for given notifications.
     * If called without IDs, all notification will be returned.
     *
     * @param {int[]} ids
     *      List of local notification IDs.
     * @param {String?} type
     *      Local notification life cycle type
     */
    getAll: function (ids, type) {
        var toasts = this.getScheduledToasts(),
            notifications = [];

        if (!ids || ids.length === 0) {
            ids = this.getAllIds();
        }

        for (var index = 0; index < ids.length; index++) {
            var id = ids[index],
                toast = this.findToastById(id);

            if (!toast || type && this.getToastType(toast) != type)
                continue;

            var json = toast.content.lastChild.lastChild.innerText;

            notifications.push(JSON.parse(json));
        }

        return notifications;
    },

    /**
     * Property list for given notifications.
     * If called without IDs, all notification will be returned.
     *
     * @param {int[]} ids
     *      List of local notification IDs
     */
    getScheduled: function (ids) {
        if (!ids || ids.length === 0) {
            ids = this.getAllIds();
        }

        return this.getAll(ids, 'scheduled');
    },

    /**
     * Property list for given notifications.
     * If called without IDs, all notification will be returned.
     *
     * @param {int[]} ids
     *      List of local notification IDs
     */
    getTriggered: function (ids) {
        if (!ids || ids.length === 0) {
            ids = this.getAllIds();
        }

        return this.getAll(ids, 'triggered');
    },
};
<|endoftext|>


<commit_before>WYMeditor.SKINS.seamless = {
    init: function (wym) {
        // The classes and containers sections are dropdowns to the right of
        // the toolbar at the top
        jQuery(
            wym._options.containersSelector + ', ' +
            wym._options.classesSelector,
            wym._box
        ).appendTo(
            jQuery("div.wym_area_top", wym._box)
        ).addClass("wym_dropdown")
          .css(
            {
                "margin-right": "10px",
                "width": "120px",
                "float": "left"
            }
          );

        // The toolbar uses buttons
        jQuery(wym._options.toolsSelector, wym._box)
          .addClass("wym_buttons")
          .css({"margin-right": "10px", "float": "left"});
    }
};
<commit_msg>Use the postIframeInit event in seamless/skin.js for resizing the iframe.<commit_after>WYMeditor.SKINS.seamless = {
    init: function (wym) {
        var This = WYMeditor.SKINS.seamless;

        This.initChrome(wym);

        // The Iframe isn't initialized at this point, so we'll need to wait
        // until it is before attempting to use it.
        jQuery(wym._element).bind(
            WYMeditor.EVENTS.postIframeInitialization,
            This.postIframeInit
        );
    },
    postIframeInit: function (e, wym) {
        var This = WYMeditor.SKINS.seamless;

        // Perform an initial resize, if necessary
        jQuery(wym._doc).ready(function () {
            This.resizeIframe(wym);
        });

        // Detect possible Block creation so that we can always keep the iframe
        // properly resized and the current container in view
        jQuery(wym._element).bind(
            WYMeditor.EVENTS.postBlockMaybeCreated,
            This.resizeAndScrollIfNeeded
        );
    },
    initChrome: function (wym) {
        // Initialize the toolbar and classes/containers selectors

        // The classes and containers sections are dropdowns to the right of
        // the toolbar at the top
        jQuery(
            wym._options.containersSelector + ', ' +
            wym._options.classesSelector,
            wym._box
        ).appendTo(
            jQuery("div.wym_area_top", wym._box)
        ).addClass("wym_dropdown")
          .css(
            {
                "margin-right": "10px",
                "width": "120px",
                "float": "left"
            }
          );

        // The toolbar uses buttons
        jQuery(wym._options.toolsSelector, wym._box)
          .addClass("wym_buttons")
          .css({"margin-right": "10px", "float": "left"});
    },
    resizeIframe: function (wym) {
        var desiredHeight = wym._doc.body.scrollHeight,
            $iframe = jQuery(wym._iframe),
            currentHeight = $iframe.height();

        if (currentHeight !== desiredHeight) {
            $iframe.height(desiredHeight);
            return true;
        }
        return false;
    },
    scrollIfNeeded: function (wym) {
        var iframeOffset = jQuery(wym._iframe).offset(),
            iframeOffsetTop = iframeOffset.top,
            $container = jQuery(wym.selected()),
            containerOffset = $container.offset(),
            viewportLowestY,
            containerLowestY,
            newScrollTop,
            extraScroll = 20,
            scrollDiff,
            $window = jQuery(window),
            $body = jQuery(document.body);

        containerLowestY = iframeOffsetTop + containerOffset.top + $container.outerHeight();
        viewportLowestY = $window.scrollTop() + $window.height();
        scrollDiff = containerLowestY - viewportLowestY;
        if (scrollDiff > 0) {
            // Part of our selected container isn't
            // visible, so we need to scroll down.
            alert("containerLowestY: " + containerLowestY +
                    "\niframeOffsetTop: " + iframeOffsetTop +
                    "\ncontainerOffset.top: " + containerOffset.top +
                    "\nouterHeight: " + $container.outerHeight()
            );
            alert("--");
            alert("viewportLowestY: " + viewportLowestY +
                    "\nwindow scrollTop: " + $window.scrollTop() +
                    "\nwindow: " + $window.height()
            );
            newScrollTop = $body.scrollTop() + scrollDiff + extraScroll;
            $body.scrollTop(newScrollTop);
        }
    },
    resizeAndScrollIfNeeded: function (e, wym) {
        // Scroll the page so that our current selection
        // within the iframe is actually in view.
        var This = WYMeditor.SKINS.seamless,
            resizeOccurred = This.resizeIframe(wym);
        if (resizeOccurred !== true) {
            return;
        }
        This.scrollIfNeeded(wym);
    },
    WYM_OPTS: {
        iframeHtml: String() +
            '
' +
                '' +
                '' +
            "
",
    }
};
<|endoftext|>


<commit_before>/**
 * Module dependencies.
 */
var passport = require('passport')
  , url = require('url')
  , querystring= require('querystring')
  , util = require('util')
  , utils = require('./utils')
  , OAuth2 = require('oauth').OAuth2
  , setup = require('./setup')
  , InternalOAuthError = require('./errors/internaloautherror');

/**
 * `Strategy` constructor.
 *
 * The OpenID Connect authentication strategy authenticates requests using
 * OpenID Connect, which is an identity layer on top of the OAuth 2.0 protocol.
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {}
  passport.Strategy.call(this);
  this.name = options.name || 'openidconnect';
  this._verify = verify;

  // TODO: What's the recommended field name for OpenID Connect?
  this._identifierField = options.identifierField || 'openid_identifier';
  this._scope = options.scope;
  this._scopeSeparator = options.scopeSeparator || ' ';
  this._passReqToCallback = options.passReqToCallback;
  this._skipUserInfo = (options.skipUserInfo === undefined) ? false : options.skipUserInfo;

  this._configurers = [];

  if (options.authorizationURL && options.tokenURL) {
    // This OpenID Connect strategy is configured to work with a specific
    // provider.  Override the discovery process with pre-configured endpoints.
    this.configure(function(identifier, done) {
      return done(null, {
        authorizationURL: options.authorizationURL,
        tokenURL: options.tokenURL,
        userInfoURL: options.userInfoURL,
        clientID: options.clientID,
        clientSecret: options.clientSecret,
        callbackURL: options.callbackURL
      });
    });
  }
}

/**
 * Inherit from `passport.Strategy`.
 */
util.inherits(Strategy, passport.Strategy);

/**
 * Authenticate request by delegating to an OpenID Connect provider.
 *
 * @param {Object} req
 * @param {Object} options
 * @api protected
 */
Strategy.prototype.authenticate = function(req, options) {
  options = options || {};
  var self = this;

  if (req.query && req.query.error) {
    // TODO: Error information pertaining to OAuth 2.0 flows is encoded in the
    //       query parameters, and should be propagated to the application.
    return this.fail();
  }

  if (req.query && req.query.code) {
    var code = req.query.code;

    if (req.session && req.session.identifier) {
      identifier = req.session.identifier;
    }

    this.configure(identifier, function(err, config) {
      if (err) { return self.error(err); }

      var oauth2 = new OAuth2(config.clientID,  config.clientSecret,
                              '', config.authorizationURL, config.tokenURL);

      var callbackURL = options.callbackURL || config.callbackURL;
      if (callbackURL) {
        var parsed = url.parse(callbackURL);
        if (!parsed.protocol) {
          // The callback URL is relative, resolve a fully qualified URL from the
          // URL of the originating request.
          // !doesn't work behind reverse proxy!
          callbackURL = url.resolve(utils.originalURL(req), callbackURL);
        }
      }

      oauth2.getOAuthAccessToken(code, { grant_type: 'authorization_code', redirect_uri: callbackURL }, function(err, accessToken, refreshToken, params) {
        if (err) { return self.error(new InternalOAuthError('failed to obtain access token', err)); }

        console.log('TOKEN');
        console.log('AT: ' + accessToken);
        console.log('RT: ' + refreshToken);
        console.log(params);
        console.log('----');

        var idToken = params['id_token'];
        if (!idToken) { return self.error(new Error('ID Token not present in token response')); }

        var idTokenSegments = idToken.split('.')
          , jwtClaimsStr
          , jwtClaims;

        try {
          jwtClaimsStr = new Buffer(idTokenSegments[1], 'base64').toString();
          jwtClaims = JSON.parse(jwtClaimsStr);
        } catch (ex) {
          return self.error(ex);
        }

        console.log(jwtClaims);

        var iss = jwtClaims.iss;
        var sub = jwtClaims.sub;
        // Prior to OpenID Connect Basic Client Profile 1.0 - draft 22, the
        // "sub" claim was named "user_id".  Many providers still issue the
        // claim under the old field, so fallback to that.
        if (!sub) {
          sub = jwtClaims.user_id;
        }

        // TODO: Ensure claims are validated per:
        //       http://openid.net/specs/openid-connect-basic-1_0.html#id_token

        self._shouldLoadUserInfo(iss, sub, function(err, load) {
          if (err) { return self.error(err); };

          if (load) {
            var parsed = url.parse(config.userInfoURL, true);
            parsed.query['schema'] = 'openid';
            delete parsed.search;
            var userInfoURL = url.format(parsed);

            // NOTE: We are calling node-oauth's internal `_request` function (as
            //       opposed to `get`) in order to send the access token in the
            //       `Authorization` header rather than as a query parameter.
            //
            //       Additionally, the master branch of node-oauth (as of
            //       2013-02-16) will include the access token in *both* headers
            //       and query parameters, which is a violation of the spec.
            //       Setting the fifth argument of `_request` to `null` works
            //       around this issue.  I've noted this in comments here:
            //       https://github.com/ciaranj/node-oauth/issues/117

            //oauth2.get(userInfoURL, accessToken, function (err, body, res) {
            oauth2._request("GET", userInfoURL, { 'Authorization': "Bearer " + accessToken, 'Accept': "application/json" }, null, null, function (err, body, res) {
              if (err) { return self.error(new InternalOAuthError('failed to fetch user info', err)); }

              // TODO: verify the sub Claim in the UserInfo response...
              // Due to the possibility of token substitution attacks,
              // the UserInfo Response is not guaranteed to be about the End-User identified by the sub (subject) element of the ID Token.
              // The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token;
              // if they do not match, the UserInfo Response values MUST NOT be used.

              console.log('USER_INFO');
              console.log(body);
              console.log('-------');

              var userInfo = {};

              try {
                var json = JSON.parse(body);

                userInfo.sub = json.sub;
                // Prior to OpenID Connect Basic Client Profile 1.0 - draft 22, the
                // "sub" key was named "user_id".  Many providers still use the old
                // key, so fallback to that.
                if (!userInfo.sub) {
                  userInfo.sub = json.user_id;
                }

                userInfo.displayName = json.name;
                userInfo.name = { familyName: json.family_name,
                                 givenName: json.given_name,
                                 middleName: json.middle_name };

                userInfo.email = json.email;

                userInfo._raw = body;
                userInfo._json = json;

                onUserInfoLoaded(userInfo);
              } catch(ex) {
                return self.error(ex);
              }
            });
          } else {
            onUserInfoLoaded();
          }

          function onUserInfoLoaded(userInfo) {
            function verified(err, user, info) {
              if (err) { return self.error(err); }
              if (!user) { return self.fail(info); }
              self.success(user, info);
            }

            if (self._passReqToCallback) {
              var arity = self._verify.length;
              if (arity == 9) {
                self._verify(req, iss, sub, userInfo, jwtClaims, accessToken, refreshToken, params, verified);
              } else if (arity == 8) {
                self._verify(req, iss, sub, userInfo, accessToken, refreshToken, params, verified);
              } else if (arity == 7) {
                self._verify(req, iss, sub, userInfo, accessToken, refreshToken, verified);
              } else if (arity == 5) {
                self._verify(req, iss, sub, userInfo, verified);
              } else { // arity == 4
                self._verify(req, iss, sub, verified);
              }
            } else {
              var arity = self._verify.length;
              if (arity == 8) {
                self._verify(iss, sub, userInfo, jwtClaims, accessToken, refreshToken, params, verified);
              } else if (arity == 7) {
                self._verify(iss, sub, userInfo, accessToken, refreshToken, params, verified);
              } else if (arity == 6) {
                self._verify(iss, sub, userInfo, accessToken, refreshToken, verified);
              } else if (arity == 4) {
                self._verify(iss, sub, userInfo, verified);
              } else { // arity == 3
                self._verify(iss, sub, verified);
              }
            }
          }

        });
      });
    });
  } else {
    // The request being authenticated is initiating OpenID Connect
    // authentication.  Prior to redirecting to the provider, configuration will
    // be loaded.  The configuration is typically either pre-configured or
    // discovered dynamically.  When using dynamic discovery, a user supplies
    // their identifer as input.

    var identifier;
    if (req.body && req.body[this._identifierField]) {
      identifier = req.body[this._identifierField];
    } else if (req.query && req.query[this._identifierField]) {
      identifier = req.query[this._identifierField];
    }

    var level;
    if (req.body && req.body['bronze']) {
        level = "http://id.incommon.org/assurance/bronze";
    } else if (req.query && req.query['bronze']) {
        level = "http://id.incommon.org/assurance/bronze";
    }

    if (req.body && req.body['silver']) {
        level = "http://id.incommon.org/assurance/silver";
    } else if (req.query && req.query['silver']) {
        level = "http://id.incommon.org/assurance/silver";
    }

    if (req.session)
      req.session.identifier = identifier;

    this.configure(identifier, function(err, config) {
      if (err) { return self.error(err); }

      var callbackURL = options.callbackURL || config.callbackURL;
      if (callbackURL) {
        var parsed = url.parse(callbackURL);
        if (!parsed.protocol) {
          // The callback URL is relative, resolve a fully qualified URL from the
          // URL of the originating request.
          callbackURL = url.resolve(utils.originalURL(req), callbackURL);
        }
      }

      var params = self.authorizationParams(options);
      var params = {};
      params['response_type'] = 'code';
      params['client_id'] = config.clientID;
      params['redirect_uri'] = callbackURL;
      if (identifier) {
        params['login_hint'] = identifier;
      }
      if (level) {
        params['acr_values'] = level;
      }
      var scope = options.scope || self._scope;
      if (Array.isArray(scope)) { scope = scope.join(self._scopeSeparator); }
      if (scope) {
        params.scope = 'openid' + self._scopeSeparator + scope;
      } else {
        params.scope = 'openid';
      }
      // TODO: Add support for automatically generating a random state for verification.
      // TODO: Make state optional, if `config` disables it and supplies an alternative
      //       session key.
      //var state = options.state;
      //if (state) { params.state = state; }

      var state = utils.uid(16);

      // TODO: Implement support for standard OpenID Connect params (display, prompt, etc.)

      var location = config.authorizationURL + '?' + querystring.stringify(params);
      self.redirect(location);
    });
  }
}

/**
 * Register a function used to configure the strategy.
 *
 * OpenID Connect is an identity layer on top of OAuth 2.0.  OAuth 2.0 requires
 * knowledge of certain endpoints (authorization, token, etc.) as well as a
 * client identifier (and corresponding secret) registered at the authorization
 * server.
 *
 * Configuration functions are responsible for loading this information.  This
 * is typically done via one of two popular mechanisms:
 *
 *   - The configuration is known ahead of time, and pre-configured via options
 *     to the strategy.
 *   - The configuration is dynamically loaded, using optional discovery and
 *     registration specifications.  (Note: Providers are not required to
 *     implement support for dynamic discovery and registration.  As such, there
 *     is no guarantee that this will result in successfully initiating OpenID
 *     Connect authentication.)
 *
 * @param {Function} fn
 * @api public
 */
Strategy.prototype.configure = function(identifier, done) {
  if (typeof identifier === 'function') {
    return this._configurers.push(identifier);
  }

  // private implementation that traverses the chain of configurers, attempting
  // to load configuration
  var stack = this._configurers;
  (function pass(i, err, config) {
    // an error or configuration was obtained, done
    if (err || config) { return done(err, config); }

    var layer = stack[i];
    if (!layer) {
      // Strategy-specific functions did not result in obtaining configuration
      // details.  Proceed to protocol-defined mechanisms in an attempt
      // to discover the provider's configuration.
      return setup(identifier, done);
    }

    try {
      layer(identifier, function(e, c) { pass(i + 1, e, c); } )
    } catch (ex) {
      return done(ex);
    }
  })(0);
}

/**
 * Return extra parameters to be included in the authorization request.
 *
 * Some OpenID Connect providers allow additional, non-standard parameters to be
 * included when requesting authorization.  Since these parameters are not
 * standardized by the OpenID Connect specification, OpenID Connect-based
 * authentication strategies can overrride this function in order to populate
 * these parameters as required by the provider.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
Strategy.prototype.authorizationParams = function(options) {
  return {};
}

/**
 * Check if should load user userInfo, contingent upon options.
 *
 * @param {String} issuer
 * @param {String} subject
 * @param {Function} done
 * @api private
 */
Strategy.prototype._shouldLoadUserInfo = function(issuer, subject, done) {
  if (typeof this._skipUserInfo == 'function' && this._skipUserInfo.length > 1) {
    // async
    this._skipUserInfo(issuer, subject, function(err, skip) {
      if (err) { return done(err); }
      if (!skip) { return done(null, true); }
      return done(null, false);
    });
  } else {
    var skip = (typeof this._skipUserInfo == 'function') ? this._skipUserInfo(issuer, subject) : this._skipUserInfo;
    if (!skip) { return done(null, true); }
    return done(null, false);
  }
}

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<commit_msg>Adding userInfo.picture.<commit_after>/**
 * Module dependencies.
 */
var passport = require('passport')
  , url = require('url')
  , querystring= require('querystring')
  , util = require('util')
  , utils = require('./utils')
  , OAuth2 = require('oauth').OAuth2
  , setup = require('./setup')
  , InternalOAuthError = require('./errors/internaloautherror');

/**
 * `Strategy` constructor.
 *
 * The OpenID Connect authentication strategy authenticates requests using
 * OpenID Connect, which is an identity layer on top of the OAuth 2.0 protocol.
 *
 * @param {Object} options
 * @param {Function} verify
 * @api public
 */
function Strategy(options, verify) {
  options = options || {}
  passport.Strategy.call(this);
  this.name = options.name || 'openidconnect';
  this._verify = verify;

  // TODO: What's the recommended field name for OpenID Connect?
  this._identifierField = options.identifierField || 'openid_identifier';
  this._scope = options.scope;
  this._scopeSeparator = options.scopeSeparator || ' ';
  this._passReqToCallback = options.passReqToCallback;
  this._skipUserInfo = (options.skipUserInfo === undefined) ? false : options.skipUserInfo;

  this._configurers = [];

  if (options.authorizationURL && options.tokenURL) {
    // This OpenID Connect strategy is configured to work with a specific
    // provider.  Override the discovery process with pre-configured endpoints.
    this.configure(function(identifier, done) {
      return done(null, {
        authorizationURL: options.authorizationURL,
        tokenURL: options.tokenURL,
        userInfoURL: options.userInfoURL,
        clientID: options.clientID,
        clientSecret: options.clientSecret,
        callbackURL: options.callbackURL
      });
    });
  }
}

/**
 * Inherit from `passport.Strategy`.
 */
util.inherits(Strategy, passport.Strategy);

/**
 * Authenticate request by delegating to an OpenID Connect provider.
 *
 * @param {Object} req
 * @param {Object} options
 * @api protected
 */
Strategy.prototype.authenticate = function(req, options) {
  options = options || {};
  var self = this;

  if (req.query && req.query.error) {
    // TODO: Error information pertaining to OAuth 2.0 flows is encoded in the
    //       query parameters, and should be propagated to the application.
    return this.fail();
  }

  if (req.query && req.query.code) {
    var code = req.query.code;

    if (req.session && req.session.identifier) {
      identifier = req.session.identifier;
    }

    this.configure(identifier, function(err, config) {
      if (err) { return self.error(err); }

      var oauth2 = new OAuth2(config.clientID,  config.clientSecret,
                              '', config.authorizationURL, config.tokenURL);

      var callbackURL = options.callbackURL || config.callbackURL;
      if (callbackURL) {
        var parsed = url.parse(callbackURL);
        if (!parsed.protocol) {
          // The callback URL is relative, resolve a fully qualified URL from the
          // URL of the originating request.
          // !doesn't work behind reverse proxy!
          callbackURL = url.resolve(utils.originalURL(req), callbackURL);
        }
      }

      oauth2.getOAuthAccessToken(code, { grant_type: 'authorization_code', redirect_uri: callbackURL }, function(err, accessToken, refreshToken, params) {
        if (err) { return self.error(new InternalOAuthError('failed to obtain access token', err)); }

        console.log('TOKEN');
        console.log('AT: ' + accessToken);
        console.log('RT: ' + refreshToken);
        console.log(params);
        console.log('----');

        var idToken = params['id_token'];
        if (!idToken) { return self.error(new Error('ID Token not present in token response')); }

        var idTokenSegments = idToken.split('.')
          , jwtClaimsStr
          , jwtClaims;

        try {
          jwtClaimsStr = new Buffer(idTokenSegments[1], 'base64').toString();
          jwtClaims = JSON.parse(jwtClaimsStr);
        } catch (ex) {
          return self.error(ex);
        }

        console.log(jwtClaims);

        var iss = jwtClaims.iss;
        var sub = jwtClaims.sub;
        // Prior to OpenID Connect Basic Client Profile 1.0 - draft 22, the
        // "sub" claim was named "user_id".  Many providers still issue the
        // claim under the old field, so fallback to that.
        if (!sub) {
          sub = jwtClaims.user_id;
        }

        // TODO: Ensure claims are validated per:
        //       http://openid.net/specs/openid-connect-basic-1_0.html#id_token

        self._shouldLoadUserInfo(iss, sub, function(err, load) {
          if (err) { return self.error(err); };

          if (load) {
            var parsed = url.parse(config.userInfoURL, true);
            parsed.query['schema'] = 'openid';
            delete parsed.search;
            var userInfoURL = url.format(parsed);

            // NOTE: We are calling node-oauth's internal `_request` function (as
            //       opposed to `get`) in order to send the access token in the
            //       `Authorization` header rather than as a query parameter.
            //
            //       Additionally, the master branch of node-oauth (as of
            //       2013-02-16) will include the access token in *both* headers
            //       and query parameters, which is a violation of the spec.
            //       Setting the fifth argument of `_request` to `null` works
            //       around this issue.  I've noted this in comments here:
            //       https://github.com/ciaranj/node-oauth/issues/117

            //oauth2.get(userInfoURL, accessToken, function (err, body, res) {
            oauth2._request("GET", userInfoURL, { 'Authorization': "Bearer " + accessToken, 'Accept': "application/json" }, null, null, function (err, body, res) {
              if (err) { return self.error(new InternalOAuthError('failed to fetch user info', err)); }

              // TODO: verify the sub Claim in the UserInfo response...
              // Due to the possibility of token substitution attacks,
              // the UserInfo Response is not guaranteed to be about the End-User identified by the sub (subject) element of the ID Token.
              // The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token;
              // if they do not match, the UserInfo Response values MUST NOT be used.

              console.log('USER_INFO');
              console.log(body);
              console.log('-------');

              var userInfo = {};

              try {
                var json = JSON.parse(body);

                userInfo.sub = json.sub;
                // Prior to OpenID Connect Basic Client Profile 1.0 - draft 22, the
                // "sub" key was named "user_id".  Many providers still use the old
                // key, so fallback to that.
                if (!userInfo.sub) {
                  userInfo.sub = json.user_id;
                }

                userInfo.displayName = json.name;
                userInfo.name = { familyName: json.family_name,
                                 givenName: json.given_name,
                                 middleName: json.middle_name };

                userInfo.email = json.email;
                userInfo.picture = json.picture;

                userInfo._raw = body;
                userInfo._json = json;

                onUserInfoLoaded(userInfo);
              } catch(ex) {
                return self.error(ex);
              }
            });
          } else {
            onUserInfoLoaded();
          }

          function onUserInfoLoaded(userInfo) {
            function verified(err, user, info) {
              if (err) { return self.error(err); }
              if (!user) { return self.fail(info); }
              self.success(user, info);
            }

            if (self._passReqToCallback) {
              var arity = self._verify.length;
              if (arity == 9) {
                self._verify(req, iss, sub, userInfo, jwtClaims, accessToken, refreshToken, params, verified);
              } else if (arity == 8) {
                self._verify(req, iss, sub, userInfo, accessToken, refreshToken, params, verified);
              } else if (arity == 7) {
                self._verify(req, iss, sub, userInfo, accessToken, refreshToken, verified);
              } else if (arity == 5) {
                self._verify(req, iss, sub, userInfo, verified);
              } else { // arity == 4
                self._verify(req, iss, sub, verified);
              }
            } else {
              var arity = self._verify.length;
              if (arity == 8) {
                self._verify(iss, sub, userInfo, jwtClaims, accessToken, refreshToken, params, verified);
              } else if (arity == 7) {
                self._verify(iss, sub, userInfo, accessToken, refreshToken, params, verified);
              } else if (arity == 6) {
                self._verify(iss, sub, userInfo, accessToken, refreshToken, verified);
              } else if (arity == 4) {
                self._verify(iss, sub, userInfo, verified);
              } else { // arity == 3
                self._verify(iss, sub, verified);
              }
            }
          }

        });
      });
    });
  } else {
    // The request being authenticated is initiating OpenID Connect
    // authentication.  Prior to redirecting to the provider, configuration will
    // be loaded.  The configuration is typically either pre-configured or
    // discovered dynamically.  When using dynamic discovery, a user supplies
    // their identifer as input.

    var identifier;
    if (req.body && req.body[this._identifierField]) {
      identifier = req.body[this._identifierField];
    } else if (req.query && req.query[this._identifierField]) {
      identifier = req.query[this._identifierField];
    }

    var level;
    if (req.body && req.body['bronze']) {
        level = "http://id.incommon.org/assurance/bronze";
    } else if (req.query && req.query['bronze']) {
        level = "http://id.incommon.org/assurance/bronze";
    }

    if (req.body && req.body['silver']) {
        level = "http://id.incommon.org/assurance/silver";
    } else if (req.query && req.query['silver']) {
        level = "http://id.incommon.org/assurance/silver";
    }

    if (req.session)
      req.session.identifier = identifier;

    this.configure(identifier, function(err, config) {
      if (err) { return self.error(err); }

      var callbackURL = options.callbackURL || config.callbackURL;
      if (callbackURL) {
        var parsed = url.parse(callbackURL);
        if (!parsed.protocol) {
          // The callback URL is relative, resolve a fully qualified URL from the
          // URL of the originating request.
          callbackURL = url.resolve(utils.originalURL(req), callbackURL);
        }
      }

      var params = self.authorizationParams(options);
      var params = {};
      params['response_type'] = 'code';
      params['client_id'] = config.clientID;
      params['redirect_uri'] = callbackURL;
      if (identifier) {
        params['login_hint'] = identifier;
      }
      if (level) {
        params['acr_values'] = level;
      }
      var scope = options.scope || self._scope;
      if (Array.isArray(scope)) { scope = scope.join(self._scopeSeparator); }
      if (scope) {
        params.scope = 'openid' + self._scopeSeparator + scope;
      } else {
        params.scope = 'openid';
      }
      // TODO: Add support for automatically generating a random state for verification.
      // TODO: Make state optional, if `config` disables it and supplies an alternative
      //       session key.
      //var state = options.state;
      //if (state) { params.state = state; }

      var state = utils.uid(16);

      // TODO: Implement support for standard OpenID Connect params (display, prompt, etc.)

      var location = config.authorizationURL + '?' + querystring.stringify(params);
      self.redirect(location);
    });
  }
}

/**
 * Register a function used to configure the strategy.
 *
 * OpenID Connect is an identity layer on top of OAuth 2.0.  OAuth 2.0 requires
 * knowledge of certain endpoints (authorization, token, etc.) as well as a
 * client identifier (and corresponding secret) registered at the authorization
 * server.
 *
 * Configuration functions are responsible for loading this information.  This
 * is typically done via one of two popular mechanisms:
 *
 *   - The configuration is known ahead of time, and pre-configured via options
 *     to the strategy.
 *   - The configuration is dynamically loaded, using optional discovery and
 *     registration specifications.  (Note: Providers are not required to
 *     implement support for dynamic discovery and registration.  As such, there
 *     is no guarantee that this will result in successfully initiating OpenID
 *     Connect authentication.)
 *
 * @param {Function} fn
 * @api public
 */
Strategy.prototype.configure = function(identifier, done) {
  if (typeof identifier === 'function') {
    return this._configurers.push(identifier);
  }

  // private implementation that traverses the chain of configurers, attempting
  // to load configuration
  var stack = this._configurers;
  (function pass(i, err, config) {
    // an error or configuration was obtained, done
    if (err || config) { return done(err, config); }

    var layer = stack[i];
    if (!layer) {
      // Strategy-specific functions did not result in obtaining configuration
      // details.  Proceed to protocol-defined mechanisms in an attempt
      // to discover the provider's configuration.
      return setup(identifier, done);
    }

    try {
      layer(identifier, function(e, c) { pass(i + 1, e, c); } )
    } catch (ex) {
      return done(ex);
    }
  })(0);
}

/**
 * Return extra parameters to be included in the authorization request.
 *
 * Some OpenID Connect providers allow additional, non-standard parameters to be
 * included when requesting authorization.  Since these parameters are not
 * standardized by the OpenID Connect specification, OpenID Connect-based
 * authentication strategies can overrride this function in order to populate
 * these parameters as required by the provider.
 *
 * @param {Object} options
 * @return {Object}
 * @api protected
 */
Strategy.prototype.authorizationParams = function(options) {
  return {};
}

/**
 * Check if should load user userInfo, contingent upon options.
 *
 * @param {String} issuer
 * @param {String} subject
 * @param {Function} done
 * @api private
 */
Strategy.prototype._shouldLoadUserInfo = function(issuer, subject, done) {
  if (typeof this._skipUserInfo == 'function' && this._skipUserInfo.length > 1) {
    // async
    this._skipUserInfo(issuer, subject, function(err, skip) {
      if (err) { return done(err); }
      if (!skip) { return done(null, true); }
      return done(null, false);
    });
  } else {
    var skip = (typeof this._skipUserInfo == 'function') ? this._skipUserInfo(issuer, subject) : this._skipUserInfo;
    if (!skip) { return done(null, true); }
    return done(null, false);
  }
}

/**
 * Expose `Strategy`.
 */
module.exports = Strategy;
<|endoftext|>